feat: add optional confirmation dialog for wake/shutdown #260

This commit is contained in:
seriousm4x
2023-11-21 10:43:59 +01:00
parent 2fce7f2b1d
commit 2d7d1119d4
15 changed files with 2056 additions and 675 deletions

View File

@@ -0,0 +1,66 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models/schema"
)
func init() {
m.Register(func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("z5lghx2r3tm45n1")
if err != nil {
return err
}
// add
new_wake_confirm := &schema.SchemaField{}
json.Unmarshal([]byte(`{
"system": false,
"id": "chtoblvl",
"name": "wake_confirm",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
}`), new_wake_confirm)
collection.Schema.AddField(new_wake_confirm)
// add
new_shutdown_confirm := &schema.SchemaField{}
json.Unmarshal([]byte(`{
"system": false,
"id": "muqy3axq",
"name": "shutdown_confirm",
"type": "bool",
"required": false,
"presentable": false,
"unique": false,
"options": {}
}`), new_shutdown_confirm)
collection.Schema.AddField(new_shutdown_confirm)
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("z5lghx2r3tm45n1")
if err != nil {
return err
}
// remove
collection.Schema.RemoveField("chtoblvl")
// remove
collection.Schema.RemoveField("muqy3axq")
return dao.SaveCollection(collection)
})
}

2465
frontend/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,8 @@
let disabled = false;
let timeout = 120;
var interval: number;
let modalWake: HTMLDialogElement;
let modalShutdown: HTMLDialogElement;
$: if (device.status === 'pending' && !interval) {
countdown(Date.parse(device.updated));
@@ -86,9 +88,17 @@
function handleClick() {
if (device.status === 'offline') {
wake();
device.wake_confirm ? askConfirmation('wake') : wake();
} else if (device.status === 'online') {
shutdown();
device.shutdown_confirm ? askConfirmation('shutdown') : shutdown();
}
}
function askConfirmation(action: string) {
if (action === 'wake') {
modalWake.showModal();
} else {
modalShutdown.showModal();
}
}
</script>
@@ -141,6 +151,36 @@
</div>
</li>
<dialog class="modal" bind:this={modalWake}>
<div class="modal-box">
<h3 class="font-bold text-lg">
{$LL.device.modal_confirm_wake_title({ device: device.name })}
</h3>
<p class="py-4">{$LL.device.modal_confirm_wake_desc({ device: device.name })}</p>
<div class="modal-action">
<form method="dialog" class="flex flex-row flex-wrap gap-2">
<button class="btn btn-neutral">{$LL.buttons.cancel()}</button>
<button class="btn btn-success" on:click={wake}>{$LL.buttons.confirm()}</button>
</form>
</div>
</div>
</dialog>
<dialog class="modal" bind:this={modalShutdown}>
<div class="modal-box">
<h3 class="font-bold text-lg">
{$LL.device.modal_confirm_shutdown_title({ device: device.name })}
</h3>
<p class="py-4">{$LL.device.modal_confirm_shutdown_desc({ device: device.name })}</p>
<div class="modal-action">
<form method="dialog" class="flex flex-row flex-wrap gap-2">
<button class="btn btn-neutral">{$LL.buttons.cancel()}</button>
<button class="btn btn-success" on:click={shutdown}>{$LL.buttons.confirm()}</button>
</form>
</div>
</div>
</dialog>
<style>
:global(.menu li.disabled) {
color: inherit;

View File

@@ -284,6 +284,19 @@
<!-- eslint-disable svelte/no-at-html-tags -->
{@html $LL.settings.ping_interval_desc2()}
</p>
<div class="form-control flex flex-row flex-wrap gap-4">
<div class="flex flex-row gap-2 items-center">
<input
id="wake-confirm"
type="checkbox"
class="toggle toggle-success"
bind:checked={device.wake_confirm}
/>
<label class="label cursor-pointer" for="wake-confirm">
<span class="label-text">{$LL.device.require_confirmation()}</span>
</label>
</div>
</div>
<div class="form-control flex flex-row flex-wrap gap-4">
<div class="w-full max-w-xs">
<label class="label" for="wake-cron">
@@ -460,6 +473,19 @@
bind:value={device.shutdown_cmd}
/>
</div>
<div class="form-control flex flex-row flex-wrap gap-4">
<div class="flex flex-row gap-2 items-center">
<input
id="shutdown-confirm"
type="checkbox"
class="toggle toggle-success"
bind:checked={device.shutdown_confirm}
/>
<label class="label cursor-pointer" for="shutdown-confirm">
<span class="label-text">{$LL.device.require_confirmation()}</span>
</label>
</div>
</div>
<p class="my-2">
{$LL.device.shutdown_cron_desc()}
</p>

View File

@@ -3,7 +3,7 @@
import { pocketbase } from '$lib/stores/pocketbase';
import type { Device, Port } from '$lib/types/device';
import { faTrash } from '@fortawesome/free-solid-svg-icons';
import type { Record } from 'pocketbase';
import type { RecordModel } from 'pocketbase';
import Fa from 'svelte-fa';
import toast from 'svelte-french-toast';
import { scale } from 'svelte/transition';
@@ -11,7 +11,7 @@
export let device: Device;
export let index: number;
function deletePort(port: Port | Record) {
function deletePort(port: Port | RecordModel) {
if (port.id !== undefined) {
$pocketbase
.collection('ports')

View File

@@ -45,6 +45,10 @@ const de = {
card_nic_tooltip_shutdown_no_permission: 'Keine Berechtigung zum ausschalten',
card_nic_tooltip_power: 'Einschalten',
card_nic_tooltip_power_no_permission: 'Keine Berechtigung zum einschalten',
modal_confirm_wake_title: '{device} einschalten?',
modal_confirm_wake_desc: 'Bitte bestätigen, um {device} einzuschalten.',
modal_confirm_shutdown_title: '{device} herunterfahren?',
modal_confirm_shutdown_desc: 'Bitte bestätigen, um {device} herunterzufahren.',
general: 'Allgemein',
general_name: 'Name',
general_name_placeholder: 'Gerätename',
@@ -109,7 +113,8 @@ const de = {
network_scan_add_all: 'Alle Geräte hinzufügen',
network_scan_replace_netmask: 'Netmask für alle Geräte ändern?',
network_scan_new_netmask: 'Neue Netmask',
network_scan_include_unknown: 'Schließe Geräte ein, bei denen der Name "Unknown" ist.'
network_scan_include_unknown: 'Schließe Geräte ein, bei denen der Name "Unknown" ist.',
require_confirmation: 'Bestätigung erforderlich'
},
login: {
welcome: 'Willkommen',
@@ -161,7 +166,8 @@ const de = {
change: 'Ändern',
reset: 'Zurücksetzen',
cancel: 'Abbrechen',
add: 'Hinzufügen'
add: 'Hinzufügen',
confirm: 'Bestätigen'
},
welcome: {
step1_page_title: 'Willkommen bei UpSnap 🥳',

View File

@@ -43,6 +43,10 @@ const en = {
card_nic_tooltip_shutdown_no_permission: 'No permission to shut down this device',
card_nic_tooltip_power: 'Power on',
card_nic_tooltip_power_no_permission: 'No permission to power on this device',
modal_confirm_wake_title: 'Wake {device}?',
modal_confirm_wake_desc: 'Please confirm to wake {device}.',
modal_confirm_shutdown_title: 'Shutdown {device}?',
modal_confirm_shutdown_desc: 'Please confirm to shutdown {device}.',
general: 'General',
general_name: 'Name',
general_name_placeholder: 'Device name',
@@ -106,7 +110,8 @@ const en = {
network_scan_add_all: 'Add all devices',
network_scan_replace_netmask: 'Replace netmask for all devices?',
network_scan_new_netmask: 'New netmask',
network_scan_include_unknown: 'Include devices where name is "Unknown"'
network_scan_include_unknown: 'Include devices where name is "Unknown"',
require_confirmation: 'Require Confirmation'
},
login: {
welcome: 'Welcome',
@@ -157,7 +162,8 @@ const en = {
change: 'Change',
reset: 'Reset',
cancel: 'Cancel',
add: 'Add'
add: 'Add',
confirm: 'Confirm'
},
welcome: {
step1_page_title: 'Welcome to UpSnap 🥳',

View File

@@ -46,6 +46,10 @@ const fr = {
"Vous n'avez pas la permission pour éteindre cet appareil",
card_nic_tooltip_power: 'Allumer',
card_nic_tooltip_power_no_permission: "Vous n'avez pas la permission pour allumer cet appareil",
modal_confirm_wake_title: 'Réveiller {device} ?',
modal_confirm_wake_desc: 'Veuillez confirmer le réveil de {device}.',
modal_confirm_shutdown_title: 'Arrêter {device} ?',
modal_confirm_shutdown_desc: "Veuillez confirmer l'arrêt du {device}.",
general: 'Général',
general_name: 'Nom',
general_name_placeholder: "Nom de l'appareil",
@@ -110,7 +114,8 @@ const fr = {
network_scan_add_all: 'Ajouter tous les appareils',
network_scan_replace_netmask: 'Remplacer le masque réseau pour tous les appareils ?',
network_scan_new_netmask: 'Nouveau masque réseau',
network_scan_include_unknown: 'Inclure les appareils dont nom est « Inconnu »'
network_scan_include_unknown: 'Inclure les appareils dont nom est « Inconnu »',
require_confirmation: 'Demande de confirmation'
},
login: {
welcome: 'Bienvenue',
@@ -163,7 +168,8 @@ const fr = {
change: 'Modifier',
reset: 'Réinitialiser',
cancel: 'Annuler',
add: 'Ajouter'
add: 'Ajouter',
confirm: 'Confirmer'
},
welcome: {
step1_page_title: 'Bienvenue sur UpSnap 🥳',

View File

@@ -181,6 +181,26 @@ type RootTranslation = {
* No permission to power on this device
*/
card_nic_tooltip_power_no_permission: string;
/**
* Wake {device}?
* @param {unknown} device
*/
modal_confirm_wake_title: RequiredParams<'device'>;
/**
* Please confirm to wake {device}.
* @param {unknown} device
*/
modal_confirm_wake_desc: RequiredParams<'device'>;
/**
* Shutdown {device}?
* @param {unknown} device
*/
modal_confirm_shutdown_title: RequiredParams<'device'>;
/**
* Please confirm to shutdown {device}.
* @param {unknown} device
*/
modal_confirm_shutdown_desc: RequiredParams<'device'>;
/**
* General
*/
@@ -405,6 +425,10 @@ type RootTranslation = {
* Include devices where name is "Unknown"
*/
network_scan_include_unknown: string;
/**
* Require Confirmation
*/
require_confirmation: string;
};
login: {
/**
@@ -575,6 +599,10 @@ type RootTranslation = {
* Add
*/
add: string;
/**
* Confirm
*/
confirm: string;
};
welcome: {
/**
@@ -912,6 +940,22 @@ export type TranslationFunctions = {
* No permission to power on this device
*/
card_nic_tooltip_power_no_permission: () => LocalizedString;
/**
* Wake {device}?
*/
modal_confirm_wake_title: (arg: { device: unknown }) => LocalizedString;
/**
* Please confirm to wake {device}.
*/
modal_confirm_wake_desc: (arg: { device: unknown }) => LocalizedString;
/**
* Shutdown {device}?
*/
modal_confirm_shutdown_title: (arg: { device: unknown }) => LocalizedString;
/**
* Please confirm to shutdown {device}.
*/
modal_confirm_shutdown_desc: (arg: { device: unknown }) => LocalizedString;
/**
* General
*/
@@ -1136,6 +1180,10 @@ export type TranslationFunctions = {
* Include devices where name is "Unknown"
*/
network_scan_include_unknown: () => LocalizedString;
/**
* Require Confirmation
*/
require_confirmation: () => LocalizedString;
};
login: {
/**
@@ -1304,6 +1352,10 @@ export type TranslationFunctions = {
* Add
*/
add: () => LocalizedString;
/**
* Confirm
*/
confirm: () => LocalizedString;
};
welcome: {
/**

View File

@@ -45,6 +45,10 @@ const pt = {
card_nic_tooltip_shutdown_no_permission: 'Não tem permissão para desligar este dispositivo',
card_nic_tooltip_power: 'Ligar',
card_nic_tooltip_power_no_permission: 'Não tem permissão para ligar este dispositivo',
modal_confirm_wake_title: 'Despertar {device}?',
modal_confirm_wake_desc: 'Confirme para ativar {device}.',
modal_confirm_shutdown_title: 'Desligar {device}?',
modal_confirm_shutdown_desc: 'Confirme o encerramento do {device}.',
general: 'Geral',
general_name: 'Nome',
general_name_placeholder: 'Nome do dispositivo',
@@ -108,7 +112,8 @@ const pt = {
network_scan_add_all: 'Adicionar todos os dispositivos',
network_scan_replace_netmask: 'Substituir máscara de rede para todos os dispositivos?',
network_scan_new_netmask: 'Nova máscara de rede',
network_scan_include_unknown: 'Incluir dispositivos em que o nome é "Unknown"'
network_scan_include_unknown: 'Incluir dispositivos em que o nome é "Unknown"',
require_confirmation: 'Exigir confirmação'
},
login: {
welcome: 'Bem-vindo',
@@ -160,7 +165,8 @@ const pt = {
change: 'Alterar',
reset: 'Repor',
cancel: 'Cancelar',
add: 'Adicionar'
add: 'Adicionar',
confirm: 'Confirmar'
},
welcome: {
step1_page_title: 'Bem-vindo ao UpSnap 🥳',

View File

@@ -42,6 +42,10 @@ const zh = {
card_nic_tooltip_shutdown_no_permission: '没有关闭此设备的权限',
card_nic_tooltip_power: '启动',
card_nic_tooltip_power_no_permission: '没有启动此设备的权限',
modal_confirm_wake_title: '唤醒 {device}?',
modal_confirm_wake_desc: '请确认唤醒 {device}.',
modal_confirm_shutdown_title: '关闭 {device}?',
modal_confirm_shutdown_desc: '请确认关闭 {device}.',
general: '通用',
general_name: '名称',
general_name_placeholder: '设备名',
@@ -102,7 +106,8 @@ const zh = {
network_scan_add_all: '添加全部设备',
network_scan_replace_netmask: '更换所有设备的子网掩码?',
network_scan_new_netmask: '新子网掩码',
network_scan_include_unknown: '包括名称为 “Unknown” 的设备'
network_scan_include_unknown: '包括名称为 “Unknown” 的设备',
require_confirmation: '要求确认'
},
login: {
welcome: '欢迎使用',
@@ -151,7 +156,8 @@ const zh = {
change: '更改',
reset: '重置',
cancel: '取消',
add: '添加'
add: '添加',
confirm: '确认'
},
welcome: {
step1_page_title: '欢迎使用 UpSnap 🥳',

View File

@@ -1,6 +1,6 @@
import type { Record } from 'pocketbase';
import type { RecordModel } from 'pocketbase';
export type Device = Record & {
export type Device = RecordModel & {
name: string;
ip: string;
mac: string;
@@ -10,9 +10,11 @@ export type Device = Record & {
link: URL;
wake_cron: string;
wake_cron_enabled: boolean;
wake_confirm: boolean;
shutdown_cron: string;
shutdown_cron_enabled: boolean;
shutdown_cmd: string;
shutdown_confirm: boolean;
password: string;
groups: string[];
expand: {
@@ -27,11 +29,11 @@ export type Device = Record & {
sol_port: number;
};
export type Port = Record & {
export type Port = RecordModel & {
name: string;
number: number;
};
export type Group = Record & {
export type Group = RecordModel & {
name: string;
};

View File

@@ -1,6 +1,6 @@
import type { Record } from 'pocketbase';
import type { RecordModel } from 'pocketbase';
export type Permission = Record & {
export type Permission = RecordModel & {
user: string;
create: boolean;
read: string[];

View File

@@ -1,13 +1,13 @@
import type { Record } from 'pocketbase';
import type { RecordModel } from 'pocketbase';
export type SettingsPublic = Record & {
export type SettingsPublic = RecordModel & {
collectionId: string;
favicon: string;
setup_completed: boolean;
website_title: string;
};
export type SettingsPrivate = Record & {
export type SettingsPrivate = RecordModel & {
interval: number;
lazy_ping: boolean;
scan_range: string;

View File

@@ -1,6 +1,6 @@
import type { Record } from 'pocketbase';
import type { RecordModel } from 'pocketbase';
export type User = Record & {
export type User = RecordModel & {
username: string;
email: string;
avatar: number;