2023-01-26 20:50:15 +00:00
import { requestPVE , goToPage , instances } from "./utils.js" ;
2022-12-16 07:03:50 +00:00
2022-12-18 00:34:40 +00:00
const waitFor = delay => new Promise ( resolve => setTimeout ( resolve , delay ) ) ;
2023-01-30 22:21:05 +00:00
export class Instance extends HTMLElement {
2022-12-12 04:20:32 +00:00
constructor ( ) {
super ( ) ;
let shadowRoot = this . attachShadow ( { mode : "open" } ) ;
2023-01-26 19:33:39 +00:00
shadowRoot . innerHTML = `
2023-01-27 21:49:50 +00:00
< link rel = "stylesheet" href = "css/style.css" type = "text/css" >
< link rel = "stylesheet" href = "css/instance.css" type = "text/css" >
2023-01-26 19:33:39 +00:00
< article >
< div >
< div >
< img id = "instance-type" >
< p id = "instance-id" > < / p >
< p id = "instance-name" > < / p >
< / d i v >
< div >
< img id = "node-status" alt = "instance node" >
< p id = "node-name" > < / p >
< / d i v >
< / d i v >
< hr >
< div class = "btn-group" >
< img id = "power-btn" >
< img id = "configure-btn" alt = "change instance configuration" >
< / d i v >
< / a r t i c l e >
` ;
2022-12-12 04:20:32 +00:00
this . shadowElement = shadowRoot ;
2022-12-19 00:53:07 +00:00
this . actionLock = false ;
2022-12-12 04:20:32 +00:00
}
set data ( data ) {
2023-01-17 20:36:34 +00:00
if ( data . status === "unknown" ) {
data . status = "stopped" ;
}
2022-12-16 07:03:50 +00:00
this . type = data . type ;
this . status = data . status ;
2023-01-26 20:50:15 +00:00
this . vmid = data . vmid ;
this . name = data . name ;
this . node = data . node ;
this . update ( ) ;
}
update ( ) {
let typeImg = this . shadowElement . querySelector ( "#instance-type" ) ;
typeImg . src = ` images/instances/ ${ this . type } / ${ this . status } .svg ` ;
typeImg . alt = ` ${ this . status } instance ` ;
2022-12-14 23:28:22 +00:00
2022-12-15 00:10:28 +00:00
let vmidParagraph = this . shadowElement . querySelector ( "#instance-id" ) ;
2023-01-26 20:50:15 +00:00
vmidParagraph . innerText = this . vmid ;
2022-12-14 23:28:22 +00:00
2022-12-15 00:10:28 +00:00
let nameParagraph = this . shadowElement . querySelector ( "#instance-name" ) ;
2023-01-26 20:50:15 +00:00
nameParagraph . innerText = this . name ? this . name : "" ;
2022-12-14 23:38:13 +00:00
2022-12-15 00:10:28 +00:00
let nodeImg = this . shadowElement . querySelector ( "#node-status" ) ;
2023-01-26 20:50:15 +00:00
nodeImg . src = ` images/nodes/ ${ this . node . status } .svg ` ;
2022-12-14 23:28:22 +00:00
2022-12-15 00:10:28 +00:00
let nodeParagraph = this . shadowElement . querySelector ( "#node-name" ) ;
2023-01-26 20:50:15 +00:00
nodeParagraph . innerText = this . node . name ;
2022-12-15 01:20:31 +00:00
2022-12-16 02:04:20 +00:00
let powerButton = this . shadowElement . querySelector ( "#power-btn" ) ;
2023-01-26 20:50:15 +00:00
powerButton . src = instances [ this . status ] . powerButtonSrc ;
powerButton . alt = instances [ this . status ] . powerButtonAlt ;
2023-02-01 00:02:10 +00:00
powerButton . title = instances [ this . status ] . powerButtonAlt ;
2023-01-26 20:50:15 +00:00
powerButton . addEventListener ( "click" , this . handlePowerButton . bind ( this ) ) ;
2022-12-19 00:53:07 +00:00
2023-01-26 20:50:15 +00:00
let configButton = this . shadowElement . querySelector ( "#configure-btn" ) ;
configButton . src = instances [ this . status ] . configButtonSrc ;
configButton . alt = instances [ this . status ] . configButtonAlt ;
2023-02-01 00:02:10 +00:00
configButton . title = instances [ this . status ] . configButtonAlt ;
2023-01-27 21:49:50 +00:00
configButton . addEventListener ( "click" , this . handleConfigButton . bind ( this ) ) ;
2022-12-18 00:37:26 +00:00
2023-01-26 20:50:15 +00:00
if ( this . node . status !== "online" ) {
powerButton . classList . add ( "hidden" ) ;
configButton . classList . add ( "hidden" ) ;
}
}
2022-12-18 00:37:26 +00:00
2023-01-26 20:50:15 +00:00
async handlePowerButton ( ) {
if ( ! this . actionLock ) {
this . actionLock = true ;
let targetAction = this . status === "running" ? "shutdown" : "start" ;
let targetStatus = this . status === "running" ? "stopped" : "running" ;
let prevStatus = this . status ;
this . status = "loading" ;
2022-12-19 05:00:58 +00:00
2023-01-26 20:50:15 +00:00
this . update ( ) ;
2022-12-18 00:37:26 +00:00
2023-01-26 20:50:15 +00:00
let task ;
2022-12-19 01:11:35 +00:00
2023-01-26 20:50:15 +00:00
try {
task = await requestPVE ( ` /nodes/ ${ this . node . name } / ${ this . type } / ${ this . vmid } /status/ ${ targetAction } ` , "POST" , { node : this . node . name , vmid : this . vmid } ) ;
2022-12-19 00:53:07 +00:00
}
2023-01-26 20:50:15 +00:00
catch ( error ) {
this . status = prevStatus ;
this . update ( ) ;
this . actionLock = false ;
console . error ( error ) ;
return ;
2022-12-19 05:52:39 +00:00
}
2023-01-17 20:45:16 +00:00
2023-01-26 20:50:15 +00:00
while ( true ) {
let taskStatus = await requestPVE ( ` /nodes/ ${ this . node . name } /tasks/ ${ task . data } /status ` ) ;
if ( taskStatus . data . status === "stopped" && taskStatus . data . exitstatus === "OK" ) { // task stopped and was successful
this . status = targetStatus ;
this . update ( ) ;
this . actionLock = false ;
return ;
}
else if ( taskStatus . data . status === "stopped" ) { // task stopped but was not successful
this . status = prevStatus ;
console . error ( ` attempted to ${ targetAction } ${ this . vmid } but process returned stopped: ${ taskStatus . data . exitstatus } ` ) ;
this . update ( ) ;
this . actionLock = false ;
return ;
}
2023-01-27 21:49:50 +00:00
else { // task has not stopped
2023-01-26 20:50:15 +00:00
await waitFor ( 1000 ) ;
}
}
2023-01-17 20:45:16 +00:00
}
2022-12-12 04:20:32 +00:00
}
2023-01-27 21:49:50 +00:00
handleConfigButton ( ) {
if ( ! this . actionLock && this . status === "stopped" ) { // if the action lock is false, and the node is stopped, then navigate to the conig page with the node infor in the search query
goToPage ( "config.html" , { node : this . node . name , type : this . type , vmid : this . vmid } ) ;
}
}
2022-12-12 04:20:32 +00:00
}
2023-01-30 22:21:05 +00:00
export class Dialog extends HTMLElement {
2023-01-27 22:33:05 +00:00
constructor ( ) {
super ( ) ;
let shadowRoot = this . attachShadow ( { mode : "open" } ) ;
shadowRoot . innerHTML = `
< link rel = "stylesheet" href = "css/style.css" type = "text/css" >
2023-01-30 22:21:05 +00:00
< link rel = "stylesheet" href = "css/form.css" type = "text/css" >
2023-01-27 22:33:05 +00:00
< link rel = "stylesheet" href = "css/dialog.css" type = "text/css" >
< dialog >
2023-02-02 18:26:44 +00:00
< p id = "prompt" > < / p >
2023-02-01 23:42:30 +00:00
< hr >
2023-02-02 00:12:36 +00:00
< form method = "dialog" class = "input-grid" style = "grid-template-columns: auto 1fr;" id = "form" >
< / f o r m >
2023-02-01 23:42:30 +00:00
< hr id = "base-hr" >
2023-01-30 22:21:05 +00:00
< div class = "btn-group" >
2023-02-02 00:12:36 +00:00
< button value = "cancel" form = "form" > Cancel < / b u t t o n >
< button value = "confirm" form = "form" > Confirm < / b u t t o n >
2023-01-30 22:21:05 +00:00
< / d i v >
2023-02-02 00:12:36 +00:00
< / d i a l o g >
2023-01-27 22:33:05 +00:00
` ;
2023-02-02 00:12:36 +00:00
this . shadowElement = shadowRoot ;
this . dialog = shadowRoot . querySelector ( "dialog" ) ;
this . form = shadowRoot . querySelector ( "form" ) ;
2023-01-27 22:33:05 +00:00
}
2023-02-01 23:42:30 +00:00
set header ( header ) {
2023-02-02 18:26:44 +00:00
this . shadowElement . querySelector ( "#prompt" ) . innerText = header ;
2023-02-01 23:42:30 +00:00
}
append ( element ) {
2023-02-02 00:12:36 +00:00
this . form . append ( element ) ;
2023-02-01 23:42:30 +00:00
}
2023-01-27 22:33:05 +00:00
set callback ( callback ) {
2023-01-30 22:21:05 +00:00
this . dialog . addEventListener ( "close" , async ( ) => {
await callback ( this . dialog . returnValue , new FormData ( this . form ) ) ;
2023-01-27 22:33:05 +00:00
} ) ;
}
show ( ) {
this . dialog . showModal ( ) ;
}
}
customElements . define ( "instance-article" , Instance ) ;
customElements . define ( "dialog-form" , Dialog ) ;