implement user triggered backups
This commit is contained in:
@@ -35,6 +35,8 @@ func Run() {
|
|||||||
router.GET("/config/nets", routes.HandleGETConfigNetsFragment)
|
router.GET("/config/nets", routes.HandleGETConfigNetsFragment)
|
||||||
router.GET("/config/devices", routes.HandleGETConfigDevicesFragment)
|
router.GET("/config/devices", routes.HandleGETConfigDevicesFragment)
|
||||||
router.GET("/config/boot", routes.HandleGETConfigBootFragment)
|
router.GET("/config/boot", routes.HandleGETConfigBootFragment)
|
||||||
|
router.GET("/backups", routes.HandleGETBackups)
|
||||||
|
router.GET("/backups/backups", routes.HandleGETBackupsFragment)
|
||||||
router.GET("/login", routes.HandleGETLogin)
|
router.GET("/login", routes.HandleGETLogin)
|
||||||
router.GET("/settings", routes.HandleGETSettings)
|
router.GET("/settings", routes.HandleGETSettings)
|
||||||
|
|
||||||
|
110
app/routes/backups.go
Normal file
110
app/routes/backups.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"proxmoxaas-dashboard/app/common"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/go-viper/mapstructure/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InstanceBackup struct {
|
||||||
|
Volid string `json:"volid"`
|
||||||
|
Notes string `json:"notes"`
|
||||||
|
Size int64 `json:"size"`
|
||||||
|
CTime int64 `json:"ctime"`
|
||||||
|
SizeFormatted string
|
||||||
|
TimeFormatted string
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleGETBackups(c *gin.Context) {
|
||||||
|
auth, err := common.GetAuth(c)
|
||||||
|
if err == nil {
|
||||||
|
vm_path, err := common.ExtractVMPath(c)
|
||||||
|
if err != nil {
|
||||||
|
common.HandleNonFatalError(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
backups, err := GetInstanceBackups(vm_path, auth)
|
||||||
|
if err != nil {
|
||||||
|
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance backups: %s", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := GetInstanceConfig(vm_path, auth) // only used for the VM's name
|
||||||
|
if err != nil {
|
||||||
|
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("%+v", backups)
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "html/backups.html", gin.H{
|
||||||
|
"global": common.Global,
|
||||||
|
"page": "backups",
|
||||||
|
"backups": backups,
|
||||||
|
"config": config,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.Redirect(http.StatusFound, "/login")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleGETBackupsFragment(c *gin.Context) {
|
||||||
|
auth, err := common.GetAuth(c)
|
||||||
|
if err == nil { // user should be authed, try to return index with population
|
||||||
|
vm_path, err := common.ExtractVMPath(c)
|
||||||
|
if err != nil {
|
||||||
|
common.HandleNonFatalError(c, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
backups, err := GetInstanceBackups(vm_path, auth)
|
||||||
|
if err != nil {
|
||||||
|
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance backups: %s", err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Header("Content-Type", "text/plain")
|
||||||
|
common.TMPL.ExecuteTemplate(c.Writer, "html/backups-backups.go.tmpl", gin.H{
|
||||||
|
"backups": backups,
|
||||||
|
})
|
||||||
|
c.Status(http.StatusOK)
|
||||||
|
} else { // return 401
|
||||||
|
c.Status(http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetInstanceBackups(vm common.VMPath, auth common.Auth) ([]InstanceBackup, error) {
|
||||||
|
backups := []InstanceBackup{}
|
||||||
|
path := fmt.Sprintf("/cluster/%s/%s/%s/backup", vm.Node, vm.Type, vm.VMID)
|
||||||
|
ctx := common.RequestContext{
|
||||||
|
Cookies: map[string]string{
|
||||||
|
"username": auth.Username,
|
||||||
|
"PVEAuthCookie": auth.Token,
|
||||||
|
"CSRFPreventionToken": auth.CSRF,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
body := []any{}
|
||||||
|
res, code, err := common.RequestGetAPI(path, ctx, &body)
|
||||||
|
if err != nil {
|
||||||
|
return backups, err
|
||||||
|
}
|
||||||
|
if code != 200 {
|
||||||
|
return backups, fmt.Errorf("request to %s resulted in %+v", path, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mapstructure.Decode(body, &backups)
|
||||||
|
if err != nil {
|
||||||
|
return backups, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range backups {
|
||||||
|
size, prefix := common.FormatNumber(backups[i].Size, 1024)
|
||||||
|
backups[i].SizeFormatted = fmt.Sprintf("%.3g %sB", size, prefix)
|
||||||
|
|
||||||
|
t := time.Unix(backups[i].CTime, 0)
|
||||||
|
backups[i].TimeFormatted = t.Format("02-01-06 15:04:05")
|
||||||
|
}
|
||||||
|
|
||||||
|
return backups, nil
|
||||||
|
}
|
3
web/html/backups-backups.go.tmpl
Normal file
3
web/html/backups-backups.go.tmpl
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{{range $i, $x := .backups}}
|
||||||
|
{{template "backup-card" $x}}
|
||||||
|
{{end}}
|
38
web/html/backups.html
Normal file
38
web/html/backups.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{{template "head" .}}
|
||||||
|
<script src="scripts/backups.js" type="module"></script>
|
||||||
|
<link rel="modulepreload" href="scripts/utils.js">
|
||||||
|
<link rel="modulepreload" href="scripts/dialog.js">
|
||||||
|
<style>
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
{{template "header" .}}
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<h2><a href="index">Instances</a> / {{.config.Name}} / Backups</h2>
|
||||||
|
<section class="w3-card w3-padding">
|
||||||
|
<div class="w3-row" style="border-bottom: 1px solid;">
|
||||||
|
<p class="w3-col l2 m4 s8">Time</p>
|
||||||
|
<p class="w3-col l6 m6 w3-hide-small">Notes</p>
|
||||||
|
<p class="w3-col l2 w3-hide-medium w3-hide-small">Size</p>
|
||||||
|
<p class="w3-col l2 m2 s4">Actions</p>
|
||||||
|
</div>
|
||||||
|
<div id="backups-container">
|
||||||
|
{{range $i, $x := .backups}}
|
||||||
|
{{template "backup-card" $x}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="w3-container w3-center">
|
||||||
|
{{template "backups-add-backup" .}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div class="w3-container w3-center">
|
||||||
|
<a class="w3-button w3-margin" id="exit" href="index">EXIT</a>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -25,7 +25,7 @@
|
|||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<section>
|
<section>
|
||||||
<h2><a href="index">Instances</a> / {{.config.Name}}</h2>
|
<h2><a href="index">Instances</a> / {{.config.Name}} / Config</h2>
|
||||||
<form id="config-form">
|
<form id="config-form">
|
||||||
<fieldset class="w3-card w3-padding">
|
<fieldset class="w3-card w3-padding">
|
||||||
<legend>Resources</legend>
|
<legend>Resources</legend>
|
||||||
|
1
web/images/actions/instance/backup.svg
Normal file
1
web/images/actions/instance/backup.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg id="symb" role="img" aria-label="backup" viewBox="1 1 22 22" xmlns="http://www.w3.org/2000/svg"><style>:root{color:#fff}@media (prefers-color-scheme:light){:root{color:#000}}</style><path fill-rule="evenodd" clip-rule="evenodd" d="M18.172 1a2 2 0 011.414.586l2.828 2.828A2 2 0 0123 5.828V20a3 3 0 01-3 3H4a3 3 0 01-3-3V4a3 3 0 013-3h14.172zM4 3a1 1 0 00-1 1v16a1 1 0 001 1h1v-6a3 3 0 013-3h8a3 3 0 013 3v6h1a1 1 0 001-1V6.828a2 2 0 00-.586-1.414l-1.828-1.828A2 2 0 0017.172 3H17v2a3 3 0 01-3 3h-4a3 3 0 01-3-3V3H4zm13 18v-6a1 1 0 00-1-1H8a1 1 0 00-1 1v6h10zM9 3h6v2a1 1 0 01-1 1h-4a1 1 0 01-1-1V3z" fill="currentColor"/></svg>
|
After Width: | Height: | Size: 631 B |
122
web/scripts/backups.js
Normal file
122
web/scripts/backups.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
import { requestAPI, getURIData, setAppearance, requestDash } from "./utils.js";
|
||||||
|
import { alert, dialog } from "./dialog.js";
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", init);
|
||||||
|
|
||||||
|
let node;
|
||||||
|
let type;
|
||||||
|
let vmid;
|
||||||
|
|
||||||
|
async function init () {
|
||||||
|
setAppearance();
|
||||||
|
|
||||||
|
const uriData = getURIData();
|
||||||
|
node = uriData.node;
|
||||||
|
type = uriData.type;
|
||||||
|
vmid = uriData.vmid;
|
||||||
|
|
||||||
|
document.querySelector("#backup-add").addEventListener("click", handleBackupAddButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
class BackupCard extends HTMLElement {
|
||||||
|
shadowRoot = null;
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
const internals = this.attachInternals();
|
||||||
|
this.shadowRoot = internals.shadowRoot;
|
||||||
|
|
||||||
|
const editButton = this.shadowRoot.querySelector("#edit-btn");
|
||||||
|
if (editButton.classList.contains("clickable")) {
|
||||||
|
editButton.onclick = this.handleEditButton.bind(this);
|
||||||
|
editButton.onkeydown = (event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
this.editButton();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteButton = this.shadowRoot.querySelector("#delete-btn");
|
||||||
|
if (deleteButton.classList.contains("clickable")) {
|
||||||
|
deleteButton.onclick = this.handleDeleteButton.bind(this);
|
||||||
|
deleteButton.onkeydown = (event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
this.handleDeleteButton();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get volid () {
|
||||||
|
return this.dataset.volid;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleEditButton () {
|
||||||
|
const template = this.shadowRoot.querySelector("#edit-dialog");
|
||||||
|
dialog(template, async (result, form) => {
|
||||||
|
if (result === "confirm") {
|
||||||
|
const body = {
|
||||||
|
volid: this.volid,
|
||||||
|
notes: form.get("notes")
|
||||||
|
};
|
||||||
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/backup/notes`, "POST", body);
|
||||||
|
if (result.status !== 200) {
|
||||||
|
alert(`Attempted to edit backup but got: ${result.error}`);
|
||||||
|
}
|
||||||
|
refreshBackups();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleDeleteButton () {
|
||||||
|
const template = this.shadowRoot.querySelector("#delete-dialog");
|
||||||
|
dialog(template, async (result, form) => {
|
||||||
|
if (result === "confirm") {
|
||||||
|
const body = {
|
||||||
|
volid: this.volid
|
||||||
|
};
|
||||||
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/backup`, "DELETE", body);
|
||||||
|
if (result.status !== 200) {
|
||||||
|
alert(`Attempted to delete backup but got: ${result.error}`);
|
||||||
|
}
|
||||||
|
refreshBackups();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("backup-card", BackupCard);
|
||||||
|
|
||||||
|
async function getBackupsFragment () {
|
||||||
|
return await requestDash(`/backups/backups?node=${node}&type=${type}&vmid=${vmid}`, "GET");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshBackups () {
|
||||||
|
let backups = await getBackupsFragment();
|
||||||
|
if (backups.status !== 200) {
|
||||||
|
alert("Error fetching backups.");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
backups = backups.data;
|
||||||
|
const container = document.querySelector("#backups-container");
|
||||||
|
container.setHTMLUnsafe(backups);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleBackupAddButton () {
|
||||||
|
const template = document.querySelector("#create-backup-dialog");
|
||||||
|
dialog(template, async (result, form) => {
|
||||||
|
if (result === "confirm") {
|
||||||
|
const body = {
|
||||||
|
notes: form.get("notes")
|
||||||
|
};
|
||||||
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/backup`, "POST", body);
|
||||||
|
if (result.status !== 200) {
|
||||||
|
alert(`Attempted to create backup but got: ${result.error}`);
|
||||||
|
}
|
||||||
|
refreshBackups();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
@@ -81,7 +81,11 @@ async function request (url, content) {
|
|||||||
const response = await fetch(url, content);
|
const response = await fetch(url, content);
|
||||||
const contentType = response.headers.get("Content-Type");
|
const contentType = response.headers.get("Content-Type");
|
||||||
let data = null;
|
let data = null;
|
||||||
if (contentType.includes("application/json")) {
|
|
||||||
|
if (contentType === null) {
|
||||||
|
data = {};
|
||||||
|
}
|
||||||
|
else if (contentType.includes("application/json")) {
|
||||||
data = await response.json();
|
data = await response.json();
|
||||||
data.status = response.status;
|
data.status = response.status;
|
||||||
}
|
}
|
||||||
@@ -94,8 +98,9 @@ async function request (url, content) {
|
|||||||
data.status = response.status;
|
data.status = response.status;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
data = response;
|
data = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return { status: response.status, error: data ? data.error : response.status };
|
return { status: response.status, error: data ? data.error : response.status };
|
||||||
}
|
}
|
||||||
|
101
web/templates/backups.go.tmpl
Normal file
101
web/templates/backups.go.tmpl
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{{define "backup-card"}}
|
||||||
|
<backup-card data-volid="{{.Volid}}">
|
||||||
|
<template shadowrootmode="open">
|
||||||
|
<link rel="stylesheet" href="modules/w3.css">
|
||||||
|
<link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
height: 1em;
|
||||||
|
width: 1em;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
height: 1em;
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="w3-row" style="margin-top: 1em; margin-bottom: 1em;">
|
||||||
|
<p class="w3-col l2 m4 s8">{{.TimeFormatted}}</p>
|
||||||
|
<p class="w3-col l6 m6 w3-hide-small">{{.Notes}}</p>
|
||||||
|
<p class="w3-col l2 w3-hide-medium w3-hide-small">{{.SizeFormatted}}</p>
|
||||||
|
<div class="w3-col l2 m2 s4 flex row nowrap" style="height: 1lh;">
|
||||||
|
<svg id="edit-btn" class="clickable" aria-label="change notes"><use href="images/actions/instance/config-active.svg#symb"></svg>
|
||||||
|
<svg id="delete-btn" class="clickable" aria-label="delete backup" role="button" tabindex=0><use href="images/actions/instance/delete-active.svg#symb"></svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<template id="edit-dialog">
|
||||||
|
<link rel="stylesheet" href="modules/w3.css">
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
<link rel="stylesheet" href="css/form.css">
|
||||||
|
<dialog class="w3-container w3-card w3-border-0">
|
||||||
|
<p class="w3-large" id="prompt" style="text-align: center;">
|
||||||
|
Edit Backup
|
||||||
|
</p>
|
||||||
|
<div id="body">
|
||||||
|
<form method="dialog" class="input-grid" style="grid-template-columns: auto;" id="form">
|
||||||
|
<label for="rate">Notes</label>
|
||||||
|
<textarea id="notes" name="notes" class="w3-input w3-border">{{.Notes}}</textarea>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="controls" class="w3-center w3-container">
|
||||||
|
<button id="cancel" type="submit" value="cancel" form="form" class="w3-button w3-margin" style="background-color: var(--negative-color, #f00); color: var(--lightbg-text-color, black);" formnovalidate>CANCEL</button>
|
||||||
|
<button id="confirm" type="submit" value="confirm" form="form" class="w3-button w3-margin" style="background-color: var(--positive-color, #0f0); color: var(--lightbg-text-color, black);">CONFIRM</button>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</template>
|
||||||
|
<template id="delete-dialog">
|
||||||
|
<link rel="stylesheet" href="modules/w3.css">
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
<link rel="stylesheet" href="css/form.css">
|
||||||
|
<dialog class="w3-container w3-card w3-border-0">
|
||||||
|
<p class="w3-large" id="prompt" style="text-align: center;">
|
||||||
|
Delete Backup
|
||||||
|
</p>
|
||||||
|
<div id="body">
|
||||||
|
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||||
|
<p>
|
||||||
|
Are you sure you want to <strong>delete</strong> the backup made at {{.TimeFormatted}}?
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="controls" class="w3-center w3-container">
|
||||||
|
<button id="cancel" value="cancel" form="form" class="w3-button w3-margin" style="background-color: var(--negative-color, #f00); color: var(--lightbg-text-color, black);" formnovalidate>CANCEL</button>
|
||||||
|
<button id="confirm" value="confirm" form="form" class="w3-button w3-margin" style="background-color: var(--positive-color, #0f0); color: var(--lightbg-text-color, black);">CONFIRM</button>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</backup-card>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "backups-add-backup"}}
|
||||||
|
<button type="button" id="backup-add" class="w3-button" aria-label="Create Backup">
|
||||||
|
<span class="large" style="margin: 0;">Create Backup</span>
|
||||||
|
<svg class="small" role="img" style="height: 1lh; width: 1lh;" aria-label="Create Backup"><use href="images/actions/network/add.svg#symb"></use></svg>
|
||||||
|
</button>
|
||||||
|
<template id="create-backup-dialog">
|
||||||
|
<link rel="stylesheet" href="modules/w3.css">
|
||||||
|
<link rel="stylesheet" href="css/style.css">
|
||||||
|
<link rel="stylesheet" href="css/form.css">
|
||||||
|
<dialog class="w3-container w3-card w3-border-0">
|
||||||
|
<p class="w3-large" id="prompt" style="text-align: center;">
|
||||||
|
Create Backup
|
||||||
|
</p>
|
||||||
|
<div id="body">
|
||||||
|
<form method="dialog" class="input-grid" style="grid-template-columns: auto;" id="form">
|
||||||
|
<label for="rate">Notes</label>
|
||||||
|
<textarea id="notes" name="notes" class="w3-input w3-border">{{.Notes}}</textarea>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="controls" class="w3-center w3-container">
|
||||||
|
<button id="cancel" type="submit" value="cancel" form="form" class="w3-button w3-margin" style="background-color: var(--negative-color, #f00); color: var(--lightbg-text-color, black);" formnovalidate>CANCEL</button>
|
||||||
|
<button id="confirm" type="submit" value="confirm" form="form" class="w3-button w3-margin" style="background-color: var(--positive-color, #0f0); color: var(--lightbg-text-color, black);">CONFIRM</button>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
</template>
|
||||||
|
{{end}}
|
@@ -8,6 +8,16 @@
|
|||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
a {
|
||||||
|
height: 1em;
|
||||||
|
width: 1em;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
height: 1em;
|
||||||
|
width: 1em;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="w3-row" style="margin-top: 1em; margin-bottom: 1em;">
|
<div class="w3-row" style="margin-top: 1em; margin-bottom: 1em;">
|
||||||
<hr class="w3-show-small w3-hide-medium w3-hide-large" style="margin: 0; margin-bottom: 1em;">
|
<hr class="w3-show-small w3-hide-medium w3-hide-large" style="margin: 0; margin-bottom: 1em;">
|
||||||
@@ -42,6 +52,9 @@
|
|||||||
{{if and (eq .NodeStatus "online") (eq .Status "running")}}
|
{{if and (eq .NodeStatus "online") (eq .Status "running")}}
|
||||||
<svg id="power-btn" class="clickable" aria-label="shutdown instance" role="button" tabindex=0><use href="images/actions/instance/stop.svg#symb"></svg>
|
<svg id="power-btn" class="clickable" aria-label="shutdown instance" role="button" tabindex=0><use href="images/actions/instance/stop.svg#symb"></svg>
|
||||||
<svg id="configure-btn" aria-disabled="true" role="none"><use href="images/actions/instance/config-inactive.svg#symb"></svg>
|
<svg id="configure-btn" aria-disabled="true" role="none"><use href="images/actions/instance/config-inactive.svg#symb"></svg>
|
||||||
|
<a href="{{.BackupsPath}}">
|
||||||
|
<svg id="backup-btn" class="clickable" aria-label="manage backups"><use href="images/actions/instance/backup.svg#symb"></svg>
|
||||||
|
</a>
|
||||||
<a href="{{.ConsolePath}}" target="_blank">
|
<a href="{{.ConsolePath}}" target="_blank">
|
||||||
<svg id="console-btn" class="clickable" aria-label="open console"><use href="images/actions/instance/console-active.svg#symb"></svg>
|
<svg id="console-btn" class="clickable" aria-label="open console"><use href="images/actions/instance/console-active.svg#symb"></svg>
|
||||||
</a>
|
</a>
|
||||||
@@ -51,11 +64,17 @@
|
|||||||
<a href="{{.ConfigPath}}">
|
<a href="{{.ConfigPath}}">
|
||||||
<svg id="configure-btn" class="clickable" aria-label="change configuration"><use href="images/actions/instance/config-active.svg#symb"></svg>
|
<svg id="configure-btn" class="clickable" aria-label="change configuration"><use href="images/actions/instance/config-active.svg#symb"></svg>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{{.BackupsPath}}">
|
||||||
|
<svg id="backup-btn" class="clickable" aria-label="manage backups"><use href="images/actions/instance/backup.svg#symb"></svg>
|
||||||
|
</a>
|
||||||
<svg id="console-btn" aria-disabled="true" role="none"><use href="images/actions/instance/console-inactive.svg#symb"></svg>
|
<svg id="console-btn" aria-disabled="true" role="none"><use href="images/actions/instance/console-inactive.svg#symb"></svg>
|
||||||
<svg id="delete-btn" class="clickable" aria-label="delete instance" role="button" tabindex=0><use href="images/actions/instance/delete-active.svg#symb"></svg>
|
<svg id="delete-btn" class="clickable" aria-label="delete instance" role="button" tabindex=0><use href="images/actions/instance/delete-active.svg#symb"></svg>
|
||||||
{{else if and (eq .NodeStatus "online") (eq .Status "loading")}}
|
{{else if and (eq .NodeStatus "online") (eq .Status "loading")}}
|
||||||
<svg id="power-btn" aria-disabled="true" role="none"><use href="images/actions/instance/loading.svg#symb"></svg>
|
<svg id="power-btn" aria-disabled="true" role="none"><use href="images/actions/instance/loading.svg#symb"></svg>
|
||||||
<svg id="configure-btn" aria-disabled="true" role="none"><use href="images/actions/instance/config-inactive.svg#symb"></svg>
|
<svg id="configure-btn" aria-disabled="true" role="none"><use href="images/actions/instance/config-inactive.svg#symb"></svg>
|
||||||
|
<a href="{{.BackupsPath}}">
|
||||||
|
<svg id="backup-btn" class="clickable" aria-label="manage backups"><use href="images/actions/instance/backup.svg#symb"></svg>
|
||||||
|
</a>
|
||||||
<svg id="console-btn" aria-disabled="true" role="none"><use href="images/actions/instance/console-inactive.svg#symb"></svg>
|
<svg id="console-btn" aria-disabled="true" role="none"><use href="images/actions/instance/console-inactive.svg#symb"></svg>
|
||||||
<svg id="delete-btn" aria-disabled="true" role="none"><use href="images/actions/instance/delete-inactive.svg#symb"></svg>
|
<svg id="delete-btn" aria-disabled="true" role="none"><use href="images/actions/instance/delete-inactive.svg#symb"></svg>
|
||||||
{{else}}
|
{{else}}
|
||||||
@@ -77,14 +96,12 @@
|
|||||||
</p>
|
</p>
|
||||||
<div id="body">
|
<div id="body">
|
||||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||||
<p>
|
|
||||||
{{if eq .Status "running"}}
|
{{if eq .Status "running"}}
|
||||||
Are you sure you want to <strong>stop</strong> {{.VMID}}?
|
<p>Are you sure you want to <strong>stop</strong> {{.VMID}}?</p>
|
||||||
{{else if eq .Status "stopped"}}
|
{{else if eq .Status "stopped"}}
|
||||||
Are you sure you want to <strong>start</strong> {{.VMID}}?
|
<p>Are you sure you want to <strong>start</strong> {{.VMID}}?</p>
|
||||||
{{else}}
|
{{else}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</p>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="controls" class="w3-center w3-container">
|
<div id="controls" class="w3-center w3-container">
|
||||||
@@ -103,9 +120,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<div id="body">
|
<div id="body">
|
||||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||||
<p>
|
<p>Are you sure you want to <strong>delete</strong> {{.VMID}}?</p>
|
||||||
Are you sure you want to <strong>delete</strong> {{.VMID}}
|
|
||||||
</p>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="controls" class="w3-center w3-container">
|
<div id="controls" class="w3-center w3-container">
|
||||||
|
Reference in New Issue
Block a user