feat: i18n add rest of routes #71

This commit is contained in:
seriousm4x
2023-10-01 13:04:27 +02:00
parent db6990fd5f
commit 7d2b3a7a4c
10 changed files with 899 additions and 142 deletions

View File

@@ -2,7 +2,7 @@ import en_US from '../en-US';
import type { Translation } from '../i18n-types';
const de_DE = {
...(en_US as Translation),
...(en_US as unknown as Translation),
home: {
order_groups: 'Gruppen',
order_name: 'Name',
@@ -24,15 +24,97 @@ const de_DE = {
change_password_body: 'Nachdem das Passwort geändert wurde, musst du dich neu einloggen.',
change_password_label: 'Altes Passwort',
change_password_new: 'Neues Password',
change_password_confirm: 'Password bestätigen',
toast_admin_saved: 'Admin gespeichert',
toast_user_saved: 'Benutzer gespeichert',
toast_password_changed: 'Passwort geändert. Bitte neu einloggen.',
toast_passwords_missmatch: 'Passwörter stimmen nicht überein'
change_password_confirm: 'Password bestätigen'
},
device: {
page_title: 'Neues Gerät',
tabs: ['Manuell', 'Netzwerkscan']
},
login: {
welcome: 'Willkommen',
email_label: 'Email oder Nutzername:',
password_label: 'Passwort:',
btn_more: 'Mehr',
menu_title_auth_providers: 'Andere Login Provider',
btn_login: 'Login'
},
settings: {
settings_title: 'Einstellungen',
ping_interval_title: 'Ping Intervall',
ping_interval_desc1:
'Legt den Intervall fest, in dem Geräte gepingt werden. Leer lassen um den Standardwert von <span class="badge">@every 3s</span> zu verwenden.',
ping_interval_desc2:
'Lerne mehr über die korrekte Cron Syntax auf <a class="link" href="https://de.wikipedia.org/wiki/Cron" target="_blank">Wikipedia</a> oder sieh dir die <a class="link" href="https://pkg.go.dev/github.com/robfig/cron/v3" target="_blank">Paketdokumentation</a> an.',
lazy_ping_title: 'Lazy Ping',
lazy_ping_desc:
'Wenn Lazy Ping aktiviert ist, pingt UpSnap die Geräte nur an, wenn ein aktiver Benutzer die Website besucht. Wenn es ausgeschaltet ist, pingt UpSnap die Geräte immer an.',
lazy_ping_enable: 'Aktivieren',
website_title_title: 'Seitentitel',
website_title_desc: 'Setzt den Seiten- und den Tabtitel.',
icon_title: 'Icon',
icon_desc: 'Eigenes Favicon nutzen. Unterstützte Dateitypen sind:',
upsnap_version: 'UpSnap Version'
},
users: {
page_title: 'Benutzer',
allow_create_devices:
'{username} erlauben neue Geräte zu erstellen und Gerätegruppen zu bearbeiten',
device_permissions: 'Geräteberechtigungen',
create_new_device: 'Neues Gerät erstellen',
read: 'Lesen',
update: 'Ändern',
delete: 'Löschen',
power: 'Power',
toggle: 'Toggle',
confirm_delete_title: 'Löschen bestätigen',
confirm_delete_desc: 'Soll {username} wirklich gelöscht werden?',
create_new_user: 'Neuen Benutzer erstellen',
username: 'Benutzername',
password: 'Passwort',
password_confirm: 'Passwort bestätigen',
required_field: 'Pflichtfeld'
},
buttons: {
save: 'Speichern',
change: 'Ändern'
delete: 'Löschen',
change: 'Ändern',
reset: 'Zurücksetzen',
cancle: 'Abbrechen',
add: 'Hinzufügen'
},
welcome: {
step1_page_title: 'Willkommen bei UpSnap 🥳',
step1_setup_desc:
'Bitte schließe die nachfolgenden Schritte ab, um die Einrichtung abzuschließen.',
step1_setup_btn_next: 'Weiter',
step2_page_title: 'Erstelle einen Admin Account',
step2_label_email: 'Email:',
step2_label_password: 'Passwort:',
step2_label_min_chars: 'min. 10 Zeichen',
step2_label_password_confirm: 'Passwort bestätigen:',
step2_btn_create: 'Erstellen',
step3_page_title: 'Du bist fertig! 🎉',
step3_page_desc: 'Füge Geräte zu deinem Dashboard hinzu.',
step3_btn_done: 'Lets go!',
not_expected_title: 'Ich hab dich hier nicht erwartet! 🧐',
not_expected_desc: 'Du hast die Einrichtung bereits abgeschlossen! Hier gibts nichts zu tun.',
not_expected_back: 'Bring mich zurück',
progress_step1: 'Willkommen',
progress_step2: 'Account erstellen',
progress_step3: 'Fertig'
},
toasts: {
admin_saved: 'Admin gespeichert',
user_saved: 'Benutzer gespeichert',
user_created: 'Benutzer {username} erstellt',
user_deleted: 'Benutzer {username} gelöscht',
settings_saved: 'Einstellungen gespeichert',
password_changed: 'Passwort geändert. Bitte neu einloggen.',
passwords_missmatch: 'Passwörter stimmen nicht überein',
permissions_created: 'Berechtigungen für {username} erstellt',
permissions_deleted: 'Berechtigungen für {username} gelöscht',
permissions_updated: 'Berechtigungen für {username} geändert',
no_permission: 'Du hast keine Berechtigung für {url}'
}
} satisfies Translation;

View File

@@ -22,15 +22,95 @@ const en_US = {
change_password_body: 'After the password has been changed, you will need to log in again.',
change_password_label: 'Old password',
change_password_new: 'New password',
change_password_confirm: 'Confirm password',
toast_admin_saved: 'Admin saved',
toast_user_saved: 'User saved',
toast_password_changed: 'Password changed. Please login again.',
toast_passwords_missmatch: "Passwords don't match"
change_password_confirm: 'Confirm password'
},
device: {
page_title: 'New device',
tabs: ['Manual', 'Network Scan']
},
login: {
welcome: 'Welcome',
email_label: 'Email or Username:',
password_label: 'Password:',
btn_more: 'More',
menu_title_auth_providers: 'Other Auth Providers',
btn_login: 'Login'
},
settings: {
settings_title: 'Settings',
ping_interval_title: 'Ping interval',
ping_interval_desc1:
'Sets the interval in which the devices are pinged. Leave blank to use default value of <span class="badge">@every 3s</span>.',
ping_interval_desc2:
'Learn more about the correct syntax for cron on <a class="link" href="https://en.wikipedia.org/wiki/Cron" target="_blank">Wikipedia</a> or refer to the <a class="link" href="https://pkg.go.dev/github.com/robfig/cron/v3" target="_blank">package documentation</a>.',
lazy_ping_title: 'Lazy ping',
lazy_ping_desc:
"When lazy ping is turned on, UpSnap will only ping devices if there is an active user visiting the website. If it's turned off, UpSnap will always ping devices.",
lazy_ping_enable: 'Enable',
website_title_title: 'Website title',
website_title_desc: 'Sets the title of the website and in the browser tab.',
icon_title: 'Icon',
icon_desc: 'Set a custom favicon. Supported file types are:',
upsnap_version: 'UpSnap version'
},
users: {
page_title: 'Users',
allow_create_devices: 'Allow {username} to create new devices and edit device groups',
device_permissions: 'Device permissions',
create_new_device: 'Create new device',
read: 'Read',
update: 'Update',
delete: 'Delete',
power: 'Power',
toggle: 'Toggle',
confirm_delete_title: 'Confirm delete',
confirm_delete_desc: 'Are you sure you want to delete {username}?',
create_new_user: 'Create new user',
username: 'Username',
password: 'Password',
password_confirm: 'Password confirm',
required_field: 'required field'
},
buttons: {
save: 'Save',
change: 'Change'
delete: 'Delete',
change: 'Change',
reset: 'Reset',
cancle: 'Cancle',
add: 'Add'
},
welcome: {
step1_page_title: 'Welcome to UpSnap 🥳',
step1_setup_desc: 'Please complete the following steps to finish the setup.',
step1_setup_btn_next: 'Next',
step2_page_title: 'Create an admin account',
step2_label_email: 'Email:',
step2_label_password: 'Password:',
step2_label_min_chars: 'min. 10 characters',
step2_label_password_confirm: 'Password confirm:',
step2_btn_create: 'Create',
step3_page_title: 'You are all set! 🎉',
step3_page_desc: 'Go ahead and add some devices to your dashboard.',
step3_btn_done: 'Lets go!',
not_expected_title: "I didn't expect you here! 🧐",
not_expected_desc: 'You are already done with the setup! Nothing to do.',
not_expected_back: 'Take me back',
progress_step1: 'Welcome',
progress_step2: 'Create account',
progress_step3: 'Done'
},
toasts: {
admin_saved: 'Admin saved',
user_saved: 'User saved',
user_created: 'User {username} created',
user_deleted: 'User {username} deleted',
settings_saved: 'Saved settings',
password_changed: 'Password changed. Please login again.',
passwords_missmatch: "Passwords don't match",
permissions_created: 'Permissions for {username} created',
permissions_deleted: 'Permissions for {username} deleted',
permissions_updated: 'Permissions for {username} updated',
no_permission: "You don't have permission to visit {url}"
}
} satisfies BaseTranslation;

View File

@@ -1,6 +1,6 @@
// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten.
/* eslint-disable */
import type { BaseTranslation as BaseTranslationType, LocalizedString } from 'typesafe-i18n'
import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n'
export type BaseTranslation = BaseTranslationType
export type BaseLocale = 'en-US'
@@ -89,32 +89,318 @@ type RootTranslation = {
* Confirm password
*/
change_password_confirm: string
}
device: {
/**
* Admin saved
* New device
*/
toast_admin_saved: string
page_title: string
tabs: {
/**
* Manual
*/
'0': string
/**
* Network Scan
*/
'1': string
}
}
login: {
/**
* User saved
* Welcome
*/
toast_user_saved: string
welcome: string
/**
* Password changed. Please login again.
* Email or Username:
*/
toast_password_changed: string
email_label: string
/**
* Passwords don't match
* Password:
*/
toast_passwords_missmatch: string
password_label: string
/**
* More
*/
btn_more: string
/**
* Other Auth Providers
*/
menu_title_auth_providers: string
/**
* Login
*/
btn_login: string
}
settings: {
/**
* Settings
*/
settings_title: string
/**
* Ping interval
*/
ping_interval_title: string
/**
* Sets the interval in which the devices are pinged. Leave blank to use default value of <span class="badge">@every 3s</span>.
*/
ping_interval_desc1: string
/**
* Learn more about the correct syntax for cron on <a class="link" href="https://en.wikipedia.org/wiki/Cron" target="_blank">Wikipedia</a> or refer to the <a class="link" href="https://pkg.go.dev/github.com/robfig/cron/v3" target="_blank">package documentation</a>.
*/
ping_interval_desc2: string
/**
* Lazy ping
*/
lazy_ping_title: string
/**
* When lazy ping is turned on, UpSnap will only ping devices if there is an active user visiting the website. If it's turned off, UpSnap will always ping devices.
*/
lazy_ping_desc: string
/**
* Enable
*/
lazy_ping_enable: string
/**
* Website title
*/
website_title_title: string
/**
* Sets the title of the website and in the browser tab.
*/
website_title_desc: string
/**
* Icon
*/
icon_title: string
/**
* Set a custom favicon. Supported file types are:
*/
icon_desc: string
/**
* UpSnap version
*/
upsnap_version: string
}
users: {
/**
* Users
*/
page_title: string
/**
* Allow {username} to create new devices and edit device groups
* @param {unknown} username
*/
allow_create_devices: RequiredParams<'username'>
/**
* Device permissions
*/
device_permissions: string
/**
* Create new device
*/
create_new_device: string
/**
* Read
*/
read: string
/**
* Update
*/
update: string
/**
* Delete
*/
'delete': string
/**
* Power
*/
power: string
/**
* Toggle
*/
toggle: string
/**
* Confirm delete
*/
confirm_delete_title: string
/**
* Are you sure you want to delete {username}?
* @param {unknown} username
*/
confirm_delete_desc: RequiredParams<'username'>
/**
* Create new user
*/
create_new_user: string
/**
* Username
*/
username: string
/**
* Password
*/
password: string
/**
* Password confirm
*/
password_confirm: string
/**
* required field
*/
required_field: string
}
buttons: {
/**
* Save
*/
save: string
/**
* Delete
*/
'delete': string
/**
* Change
*/
change: string
/**
* Reset
*/
reset: string
/**
* Cancle
*/
cancle: string
/**
* Add
*/
add: string
}
welcome: {
/**
* Welcome to UpSnap <><EFBFBD>
*/
step1_page_title: string
/**
* Please complete the following steps to finish the setup.
*/
step1_setup_desc: string
/**
* Next
*/
step1_setup_btn_next: string
/**
* Create an admin account
*/
step2_page_title: string
/**
* Email:
*/
step2_label_email: string
/**
* Password:
*/
step2_label_password: string
/**
* min. 10 characters
*/
step2_label_min_chars: string
/**
* Password confirm:
*/
step2_label_password_confirm: string
/**
* Create
*/
step2_btn_create: string
/**
* You are all set! <><EFBFBD>
*/
step3_page_title: string
/**
* Go ahead and add some devices to your dashboard.
*/
step3_page_desc: string
/**
* Lets go!
*/
step3_btn_done: string
/**
* I didn't expect you here! <><EFBFBD>
*/
not_expected_title: string
/**
* You are already done with the setup! Nothing to do.
*/
not_expected_desc: string
/**
* Take me back
*/
not_expected_back: string
/**
* Welcome
*/
progress_step1: string
/**
* Create account
*/
progress_step2: string
/**
* Done
*/
progress_step3: string
}
toasts: {
/**
* Admin saved
*/
admin_saved: string
/**
* User saved
*/
user_saved: string
/**
* User {username} created
* @param {unknown} username
*/
user_created: RequiredParams<'username'>
/**
* User {username} deleted
* @param {unknown} username
*/
user_deleted: RequiredParams<'username'>
/**
* Saved settings
*/
settings_saved: string
/**
* Password changed. Please login again.
*/
password_changed: string
/**
* Passwords don't match
*/
passwords_missmatch: string
/**
* Permissions for {username} created
* @param {unknown} username
*/
permissions_created: RequiredParams<'username'>
/**
* Permissions for {username} deleted
* @param {unknown} username
*/
permissions_deleted: RequiredParams<'username'>
/**
* Permissions for {username} updated
* @param {unknown} username
*/
permissions_updated: RequiredParams<'username'>
/**
* You don't have permission to visit {url}
* @param {unknown} url
*/
no_permission: RequiredParams<'url'>
}
}
@@ -194,32 +480,310 @@ export type TranslationFunctions = {
* Confirm password
*/
change_password_confirm: () => LocalizedString
}
device: {
/**
* Admin saved
* New device
*/
toast_admin_saved: () => LocalizedString
page_title: () => LocalizedString
tabs: {
/**
* Manual
*/
'0': () => LocalizedString
/**
* Network Scan
*/
'1': () => LocalizedString
}
}
login: {
/**
* User saved
* Welcome
*/
toast_user_saved: () => LocalizedString
welcome: () => LocalizedString
/**
* Password changed. Please login again.
* Email or Username:
*/
toast_password_changed: () => LocalizedString
email_label: () => LocalizedString
/**
* Passwords don't match
* Password:
*/
toast_passwords_missmatch: () => LocalizedString
password_label: () => LocalizedString
/**
* More
*/
btn_more: () => LocalizedString
/**
* Other Auth Providers
*/
menu_title_auth_providers: () => LocalizedString
/**
* Login
*/
btn_login: () => LocalizedString
}
settings: {
/**
* Settings
*/
settings_title: () => LocalizedString
/**
* Ping interval
*/
ping_interval_title: () => LocalizedString
/**
* Sets the interval in which the devices are pinged. Leave blank to use default value of <span class="badge">@every 3s</span>.
*/
ping_interval_desc1: () => LocalizedString
/**
* Learn more about the correct syntax for cron on <a class="link" href="https://en.wikipedia.org/wiki/Cron" target="_blank">Wikipedia</a> or refer to the <a class="link" href="https://pkg.go.dev/github.com/robfig/cron/v3" target="_blank">package documentation</a>.
*/
ping_interval_desc2: () => LocalizedString
/**
* Lazy ping
*/
lazy_ping_title: () => LocalizedString
/**
* When lazy ping is turned on, UpSnap will only ping devices if there is an active user visiting the website. If it's turned off, UpSnap will always ping devices.
*/
lazy_ping_desc: () => LocalizedString
/**
* Enable
*/
lazy_ping_enable: () => LocalizedString
/**
* Website title
*/
website_title_title: () => LocalizedString
/**
* Sets the title of the website and in the browser tab.
*/
website_title_desc: () => LocalizedString
/**
* Icon
*/
icon_title: () => LocalizedString
/**
* Set a custom favicon. Supported file types are:
*/
icon_desc: () => LocalizedString
/**
* UpSnap version
*/
upsnap_version: () => LocalizedString
}
users: {
/**
* Users
*/
page_title: () => LocalizedString
/**
* Allow {username} to create new devices and edit device groups
*/
allow_create_devices: (arg: { username: unknown }) => LocalizedString
/**
* Device permissions
*/
device_permissions: () => LocalizedString
/**
* Create new device
*/
create_new_device: () => LocalizedString
/**
* Read
*/
read: () => LocalizedString
/**
* Update
*/
update: () => LocalizedString
/**
* Delete
*/
'delete': () => LocalizedString
/**
* Power
*/
power: () => LocalizedString
/**
* Toggle
*/
toggle: () => LocalizedString
/**
* Confirm delete
*/
confirm_delete_title: () => LocalizedString
/**
* Are you sure you want to delete {username}?
*/
confirm_delete_desc: (arg: { username: unknown }) => LocalizedString
/**
* Create new user
*/
create_new_user: () => LocalizedString
/**
* Username
*/
username: () => LocalizedString
/**
* Password
*/
password: () => LocalizedString
/**
* Password confirm
*/
password_confirm: () => LocalizedString
/**
* required field
*/
required_field: () => LocalizedString
}
buttons: {
/**
* Save
*/
save: () => LocalizedString
/**
* Delete
*/
'delete': () => LocalizedString
/**
* Change
*/
change: () => LocalizedString
/**
* Reset
*/
reset: () => LocalizedString
/**
* Cancle
*/
cancle: () => LocalizedString
/**
* Add
*/
add: () => LocalizedString
}
welcome: {
/**
* Welcome to UpSnap 🥳
*/
step1_page_title: () => LocalizedString
/**
* Please complete the following steps to finish the setup.
*/
step1_setup_desc: () => LocalizedString
/**
* Next
*/
step1_setup_btn_next: () => LocalizedString
/**
* Create an admin account
*/
step2_page_title: () => LocalizedString
/**
* Email:
*/
step2_label_email: () => LocalizedString
/**
* Password:
*/
step2_label_password: () => LocalizedString
/**
* min. 10 characters
*/
step2_label_min_chars: () => LocalizedString
/**
* Password confirm:
*/
step2_label_password_confirm: () => LocalizedString
/**
* Create
*/
step2_btn_create: () => LocalizedString
/**
* You are all set! 🎉
*/
step3_page_title: () => LocalizedString
/**
* Go ahead and add some devices to your dashboard.
*/
step3_page_desc: () => LocalizedString
/**
* Lets go!
*/
step3_btn_done: () => LocalizedString
/**
* I didn't expect you here! 🧐
*/
not_expected_title: () => LocalizedString
/**
* You are already done with the setup! Nothing to do.
*/
not_expected_desc: () => LocalizedString
/**
* Take me back
*/
not_expected_back: () => LocalizedString
/**
* Welcome
*/
progress_step1: () => LocalizedString
/**
* Create account
*/
progress_step2: () => LocalizedString
/**
* Done
*/
progress_step3: () => LocalizedString
}
toasts: {
/**
* Admin saved
*/
admin_saved: () => LocalizedString
/**
* User saved
*/
user_saved: () => LocalizedString
/**
* User {username} created
*/
user_created: (arg: { username: unknown }) => LocalizedString
/**
* User {username} deleted
*/
user_deleted: (arg: { username: unknown }) => LocalizedString
/**
* Saved settings
*/
settings_saved: () => LocalizedString
/**
* Password changed. Please login again.
*/
password_changed: () => LocalizedString
/**
* Passwords don't match
*/
passwords_missmatch: () => LocalizedString
/**
* Permissions for {username} created
*/
permissions_created: (arg: { username: unknown }) => LocalizedString
/**
* Permissions for {username} deleted
*/
permissions_deleted: (arg: { username: unknown }) => LocalizedString
/**
* Permissions for {username} updated
*/
permissions_updated: (arg: { username: unknown }) => LocalizedString
/**
* You don't have permission to visit {url}
*/
no_permission: (arg: { url: unknown }) => LocalizedString
}
}

View File

@@ -58,7 +58,7 @@
$pocketbase.admins
.update($pocketbase.authStore.model.id, { avatar: newAvatar })
.then(() => {
toast.success($LL.account.toast_admin_saved());
toast.success($LL.toasts.admin_saved());
})
.catch((err) => {
toast.error(err.message);
@@ -69,7 +69,7 @@
.collection('users')
.update($pocketbase.authStore.model.id, { avatar: newAvatar })
.then(() => {
toast.success($LL.account.toast_user_saved());
toast.success($LL.toasts.user_saved());
})
.catch((err) => {
toast.error(err.message);
@@ -93,7 +93,7 @@
)
.then(async (data) => {
if (data.ok) {
toast.success($LL.account.toast_password_changed());
toast.success($LL.toasts.password_changed());
$pocketbase.authStore.clear();
goto('/login');
} else {
@@ -101,7 +101,7 @@
if (j?.data?.password?.message) {
toast.error(j?.data?.password?.message);
} else if (j?.data?.passwordConfirm?.message) {
toast.error($LL.account.toast_passwords_missmatch());
toast.error($LL.toasts.passwords_missmatch());
} else if (j.data?.oldPassword?.message) {
toast.error(j.data.oldPassword.message);
} else {

View File

@@ -3,13 +3,14 @@
import { page } from '$app/stores';
import DeviceForm from '$lib/components/DeviceForm.svelte';
import PageLoading from '$lib/components/PageLoading.svelte';
import LL from '$lib/i18n/i18n-svelte';
import { permission, pocketbase } from '$lib/stores/pocketbase';
import type { Device, Port } from '$lib/types/device';
import toast from 'svelte-french-toast';
$: if (Object.hasOwn($permission, 'update')) {
if (!$pocketbase.authStore.isAdmin && !$permission.update.includes($page.params.id)) {
toast(`You don't have permission to visit ${$page.url.pathname}`, {
toast($LL.toasts.no_permission({ url: $page.url.pathname }), {
icon: '⛔'
});
goto('/');

View File

@@ -3,6 +3,7 @@
import { page } from '$app/stores';
import DeviceForm from '$lib/components/DeviceForm.svelte';
import NetworkScan from '$lib/components/NetworkScan.svelte';
import LL from '$lib/i18n/i18n-svelte';
import { permission, pocketbase } from '$lib/stores/pocketbase';
import type { Device, Port } from '$lib/types/device';
import { faBinoculars, faWrench } from '@fortawesome/free-solid-svg-icons';
@@ -18,7 +19,7 @@
$: if (Object.hasOwn($permission, 'create')) {
if (!$pocketbase.authStore.isAdmin && !$permission.create) {
toast(`You don't have permission to visit ${$page.url.pathname}`, {
toast($LL.toasts.no_permission({ url: $page.url.pathname }), {
icon: '⛔'
});
goto('/');
@@ -28,11 +29,13 @@
let tabs = [
{
name: 'manual',
ll_name: $LL.device.tabs[0](),
icon: faWrench,
show: true
},
{
name: 'network scan',
name: 'scan',
ll_name: $LL.device.tabs[1](),
icon: faBinoculars,
show: $pocketbase.authStore.isAdmin
}
@@ -40,17 +43,14 @@
let activeTab = 'manual';
</script>
<h1 class="text-3xl font-bold mb-8">New device</h1>
<h1 class="text-3xl font-bold mb-8">{$LL.device.page_title()}</h1>
<div class="flex justify-center mb-6">
<ul class="menu menu-horizontal bg-base-300 rounded-box gap-1">
{#each tabs as tab}
{#if tab.show}
<li>
<button
class="capitalize"
class:active={activeTab === tab.name}
on:click={() => (activeTab = tab.name)}
>{tab.name} <Fa icon={tab.icon} class="ms-2" /></button
<button class:active={activeTab === tab.name} on:click={() => (activeTab = tab.name)}
>{tab.ll_name} <Fa icon={tab.icon} class="ms-2" /></button
>
</li>
{/if}

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { toggleVisibility } from '$lib/helpers/forms';
import LL from '$lib/i18n/i18n-svelte';
import { pocketbase } from '$lib/stores/pocketbase';
import { settingsPub } from '$lib/stores/settings';
import { faEye, faLockOpen, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
@@ -62,11 +63,11 @@
{/if}
<div class="flex flex-row gap-4">
<figure class="w-16"><img src="/gopher.svg" alt="Gopher" /></figure>
<h2 class="card-title">Welcome</h2>
<h2 class="card-title">{$LL.login.welcome()}</h2>
</div>
<form class="form-control w-full" on:submit|preventDefault={tryAdminThenUser}>
<label class="label" for="email">
<span class="label-text">Email or Username:</span>
<span class="label-text">{$LL.login.email_label()}</span>
</label>
<input
id="email"
@@ -76,7 +77,7 @@
bind:value={form.email}
/>
<label class="label" for="password">
<span class="label-text">Password:</span>
<span class="label-text">{$LL.login.password_label()}</span>
</label>
<label class="relative block">
<div
@@ -99,16 +100,18 @@
</label>
<div class="card-actions mt-4">
<div class="dropdown">
<label tabindex="-1" class="btn btn-neutral" for="other-providers">Other</label>
<label tabindex="-1" class="btn btn-neutral" for="other-providers"
>{$LL.login.btn_more()}</label
>
<ul
id="other-providers"
tabindex="-1"
class="dropdown-content z-[1] menu p-2 shadow bg-base-200 rounded-box w-56"
>
<li class="menu-title flex flex-row gap-2 items-center">
Other Auth Providers <a
href="https://github.com/seriousm4x/UpSnap/wiki/Auth-providers"
target="_blank"><Fa icon={faQuestionCircle} /></a
{$LL.login.menu_title_auth_providers()}
<a href="https://github.com/seriousm4x/UpSnap/wiki/Auth-providers" target="_blank"
><Fa icon={faQuestionCircle} /></a
>
</li>
<li>
@@ -117,7 +120,7 @@
</ul>
</div>
<button class="btn btn-primary ms-auto" type="submit"
>Login <Fa icon={faLockOpen} /></button
>{$LL.login.btn_login()} <Fa icon={faLockOpen} /></button
>
</div>
</form>

View File

@@ -3,6 +3,7 @@
import { page } from '$app/stores';
import { PUBLIC_VERSION } from '$env/static/public';
import PageLoading from '$lib/components/PageLoading.svelte';
import LL from '$lib/i18n/i18n-svelte';
import { backendUrl, pocketbase } from '$lib/stores/pocketbase';
import { settingsPriv, settingsPub } from '$lib/stores/settings';
import type { SettingsPrivate, SettingsPublic } from '$lib/types/settings';
@@ -18,7 +19,7 @@
onMount(() => {
if (!$pocketbase.authStore.isAdmin) {
toast(`You don't have permission to visit ${$page.url.pathname}`, {
toast($LL.toasts.no_permission({ url: $page.url.pathname }), {
icon: '⛔'
});
goto('/');
@@ -76,7 +77,7 @@
.collection('settings_private')
.update(settingsPrivClone.id, settingsPrivClone)
.then((res) => {
toast.success('Saved settings');
toast.success($LL.toasts.settings_saved());
settingsPriv.set(res as SettingsPrivate);
goto('/');
})
@@ -89,23 +90,21 @@
{#if settingsPubClone === undefined || settingsPrivClone === undefined}
<PageLoading />
{:else}
<h1 class="text-3xl font-bold mb-8">Settings</h1>
<h1 class="text-3xl font-bold mb-8">{$LL.settings.settings_title()}</h1>
<form on:submit|preventDefault={save}>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Ping interval</h2>
<h2 class="card-title">{$LL.settings.ping_interval_title()}</h2>
<p class="mt-2">
Sets the interval in which the devices are pinged. Leave blank to use default value of <span
class="badge">@every 3s</span
>.
<!-- eslint-disable svelte/no-at-html-tags -->
{@html $LL.settings.ping_interval_desc1()}
</p>
<p>
Learn more about the correct syntax for cron on
<a target="_blank" class="link" href="https://en.wikipedia.org/wiki/Cron">Wikipedia</a>
or refer to the
<a target="_blank" class="link" href="https://pkg.go.dev/github.com/robfig/cron/v3"
>package documentation</a
>.
<!-- Usage of @html is not a nice way to do this, but it's safe in this case. If someones finds
a good solution to avoid @html but still be able to use multiple links in a single sentence,
please create a pr/issue. -->
<!-- eslint-disable svelte/no-at-html-tags -->
{@html $LL.settings.ping_interval_desc2()}
</p>
<div class="form-control w-full mt-2">
<input
@@ -115,10 +114,9 @@
bind:value={settingsPrivClone.interval}
/>
</div>
<h2 class="card-title mt-2">Lazy ping</h2>
<h2 class="card-title mt-2">{$LL.settings.lazy_ping_title()}</h2>
<p class="mt-2">
When lazy ping is turned on, UpSnap will only ping devices if there is an active user
visiting the website. If it's turned off, UpSnap will always ping devices.
{$LL.settings.lazy_ping_desc()}
</p>
<div class="form-control w-fit">
<label class="label cursor-pointer gap-2">
@@ -127,15 +125,15 @@
class="checkbox checkbox-primary"
bind:checked={settingsPrivClone.lazy_ping}
/>
<span class="label-text">Enable</span>
<span class="label-text">{$LL.settings.lazy_ping_enable()}</span>
</label>
</div>
</div>
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Website title</h2>
<p class="my-2">Sets the title of the website and in the browser tab.</p>
<h2 class="card-title">{$LL.settings.website_title_title()}</h2>
<p class="my-2">{$LL.settings.website_title_desc()}</p>
<div class="form-control w-full">
<input
type="text"
@@ -148,12 +146,12 @@
</div>
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Icon</h2>
<h2 class="card-title">{$LL.settings.icon_title()}</h2>
<p class="my-2">
Set a custom favicon. Supported file types are
<span class="badge">.ico</span>, <span class="badge">.png</span>,
<span class="badge">.svg</span>, <span class="badge">.gif</span> and
<span class="badge">.jpg/.jpeg</span>.
{$LL.settings.icon_desc()}
<span class="badge">.ico</span> <span class="badge">.png</span>
<span class="badge">.svg</span> <span class="badge">.gif</span>
<span class="badge">.jpg/.jpeg</span>
</p>
<div>
<img
@@ -175,22 +173,23 @@
<button
class="btn btn-outline btn-error"
on:click={() => resetFavicon()}
on:keydown={() => resetFavicon()}>Reset</button
on:keydown={() => resetFavicon()}>{$LL.buttons.reset()}</button
>
</div>
</div>
</div>
<div class="card-actions justify-end mt-6">
<button class="btn btn-success" type="submit"><Fa icon={faSave} />Save</button>
<button class="btn btn-success" type="submit"><Fa icon={faSave} />{$LL.buttons.save()}</button
>
</div>
</form>
<div class="container mx-auto text-center mt-6">
{#if PUBLIC_VERSION === ''}
UpSnap version: (untracked)
{$LL.settings.upsnap_version()}: (untracked)
{:else}
UpSnap version: <a
href="https://github.com/seriousm4x/UpSnap/releases/tag/{PUBLIC_VERSION}"
class="link">{PUBLIC_VERSION}</a
{$LL.settings.upsnap_version()}:
<a href="https://github.com/seriousm4x/UpSnap/releases/tag/{PUBLIC_VERSION}" class="link"
>{PUBLIC_VERSION}</a
>
{/if}
</div>

View File

@@ -2,11 +2,12 @@
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import PageLoading from '$lib/components/PageLoading.svelte';
import LL from '$lib/i18n/i18n-svelte';
import { backendUrl, pocketbase } from '$lib/stores/pocketbase';
import type { Device } from '$lib/types/device';
import type { Permission } from '$lib/types/permission';
import type { User } from '$lib/types/user';
import { faPlus, faSave, faTrash } from '@fortawesome/free-solid-svg-icons';
import { faPlus, faRetweet, faSave, faTrash } from '@fortawesome/free-solid-svg-icons';
import { onMount } from 'svelte';
import Fa from 'svelte-fa';
import toast from 'svelte-french-toast';
@@ -33,7 +34,7 @@
onMount(() => {
if (!$pocketbase.authStore.isAdmin) {
toast(`You don't have permission to visit ${$page.url.pathname}`, {
toast($LL.toasts.no_permission({ url: $page.url.pathname }), {
icon: '⛔'
});
goto('/');
@@ -58,7 +59,7 @@
.collection('users')
.create(newUser)
.then((data) => {
toast.success(`User ${data.username} created`);
toast.success($LL.toasts.user_created({ username: data.username }));
newUser.username = '';
newUser.password = '';
newUser.passwordConfirm = '';
@@ -74,14 +75,14 @@
.collection('users')
.delete(user.id)
.then(async () => {
toast.success(`User ${user.username} deleted`);
toast.success($LL.toasts.user_deleted({ username: user.username }));
let permission = permissions.find((perm) => perm.user === user.id);
if (permission?.id !== undefined) {
await $pocketbase
.collection('permissions')
.delete(permission.id)
.then(() => {
toast.success(`Permissions for ${user.username} deleted`);
toast.success($LL.toasts.permissions_deleted({ username: user.username }));
})
.catch((err) => {
toast.error(err.message);
@@ -130,7 +131,7 @@
// if (i > -1) {
// permissions[i] = data as Permission;
// }
toast.success(`Permissions for ${user.username} created`);
toast.success($LL.toasts.permissions_created({ username: user.username }));
reload();
})
.catch((err) => {
@@ -142,7 +143,7 @@
.collection('permissions')
.update(permission.id, permission)
.then(() => {
toast.success(`Permissions for ${user.username} updated`);
toast.success($LL.toasts.permissions_updated({ username: user.username }));
reload();
})
.catch((err) => {
@@ -179,7 +180,7 @@
{#await Promise.all([getUsersPromise, getPermissionsPromise, getDevicesPromise])}
<PageLoading />
{:then}
<h1 class="text-3xl font-bold mb-8">Users</h1>
<h1 class="text-3xl font-bold mb-8">{$LL.users.page_title()}</h1>
{#each users as user, index}
<form on:submit|preventDefault={() => save(user)}>
<div class="card w-full bg-base-300 shadow-xl mt-6">
@@ -203,37 +204,37 @@
bind:checked={permission.create}
class="checkbox checked:checkbox-primary"
/>
<span class="label-text font-bold"
>Allow {user.username} to create new devices and edit device groups?</span
>
{$LL.users.allow_create_devices({ username: user.username })}
</label>
</div>
{/each}
<div class="collapse collapse-arrow bg-base-200">
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">Device permissions</div>
<div class="collapse-title text-xl font-medium">{$LL.users.device_permissions()}</div>
<div class="collapse-content">
{#if devices.length === 0}
<p>No devices here. <a href="/device/new" class="link">Create new device</a></p>
<p>
{$LL.home.message_no_devices()}
<a href="/device/new" class="link">{$LL.users.create_new_device()}</a>
</p>
{:else}
<div class="grid grid-cols-5 gap-4 justify-items-center">
<div class="font-bold" />
<div class="font-bold">Read</div>
<div class="font-bold">Update</div>
<div class="font-bold">Delete</div>
<div class="font-bold">Power</div>
<div class="grid grid-cols-4 md:grid-cols-5 gap-4 justify-items-center">
<div class="font-bold md:col-start-2">{$LL.users.read()}</div>
<div class="font-bold">{$LL.users.update()}</div>
<div class="font-bold">{$LL.users.delete()}</div>
<div class="font-bold">{$LL.users.power()}</div>
{#each devices as device}
<hr class="col-span-full w-full border-b-1 opacity-30 border-neutral" />
{#each permissions.filter((perm) => perm.user === user.id) as permission}
<div
class="flex flex-row flex-wrap gap-2 place-self-start break-all col-span-full sm:col-span-1"
class="flex flex-row flex-wrap gap-2 place-self-start break-all col-span-full md:col-span-1"
>
<span class="font-bold">{device.name}</span>
<span class="badge hidden sm:block">{device.ip}</span>
<span class="badge hidden md:block">{device.ip}</span>
</div>
<input
type="checkbox"
class="checkbox checked:checkbox-primary col-start-2"
class="checkbox checked:checkbox-primary md:col-start-2"
bind:group={permission.read}
value={device.id}
/>
@@ -258,32 +259,48 @@
{/each}
{/each}
<button
class="btn btn-sm btn-neutral col-start-2"
class="btn btn-sm btn-neutral md:col-start-2"
type="button"
on:click={() => {
toggleAllPermissions(user, 'read');
}}>Toggle</button
}}
><span class="hidden md:block">{$LL.users.toggle()}</span><Fa
icon={faRetweet}
size="1.5x"
/></button
>
<button
class=" btn btn-sm btn-neutral"
type="button"
on:click={() => {
toggleAllPermissions(user, 'update');
}}>Toggle</button
}}
><span class="hidden md:block">{$LL.users.toggle()}</span><Fa
icon={faRetweet}
size="1.5x"
/></button
>
<button
class=" btn btn-sm btn-neutral"
type="button"
on:click={() => {
toggleAllPermissions(user, 'delete');
}}>Toggle</button
}}
><span class="hidden md:block">{$LL.users.toggle()}</span><Fa
icon={faRetweet}
size="1.5x"
/></button
>
<button
class=" btn btn-sm btn-neutral"
type="button"
on:click={() => {
toggleAllPermissions(user, 'power');
}}>Toggle</button
}}
><span class="hidden md:block">{$LL.users.toggle()}</span><Fa
icon={faRetweet}
size="1.5x"
/></button
>
</div>
<div class="mt-4 flex flex-row flex-wrap gap-4 justify-end"></div>
@@ -294,9 +311,11 @@
<button
class="join-item btn btn-error"
type="button"
on:click={() => deleteModal.showModal()}><Fa icon={faTrash} />Delete</button
on:click={() => deleteModal.showModal()}
><Fa icon={faTrash} />{$LL.buttons.delete()}</button
>
<button class="join-item btn btn-success" type="submit"><Fa icon={faSave} />Save</button
<button class="join-item btn btn-success" type="submit"
><Fa icon={faSave} />{$LL.buttons.save()}</button
>
</div>
</div>
@@ -304,31 +323,33 @@
</form>
<dialog class="modal" bind:this={deleteModal}>
<form method="dialog" class="modal-box">
<h3 class="font-bold text-lg">Confirm delete</h3>
<p class="py-4">Are you sure you want to delete <strong>{user.username}</strong>?</p>
<h3 class="font-bold text-lg">{$LL.users.confirm_delete_title()}</h3>
<p class="py-4">{$LL.users.confirm_delete_desc({ username: user.username })}</p>
<div class="modal-action">
<button class="btn">Cancle</button>
<button class="btn btn-error" on:click={() => deleteUser(user)}>Delete</button>
<button class="btn">{$LL.buttons.cancle()}</button>
<button class="btn btn-error" on:click={() => deleteUser(user)}
>{$LL.buttons.delete()}</button
>
</div>
</form>
</dialog>
{/each}
<div class="card w-full bg-base-300 shadow-xl mt-6">
<div class="card-body">
<h2 class="card-title">Create new user</h2>
<h2 class="card-title">{$LL.users.create_new_user()}</h2>
<form on:submit|preventDefault={createUser}>
<div class="flex flex-row flex-wrap gap-4">
<div class="form-control w-full max-w-xs">
<label class="label" for="username">
<div class="label-text">
<span>Username</span>
<span>{$LL.users.username()}</span>
<span class="text-error">*</span>
</div>
</label>
<input
id="username"
type="text"
placeholder="Username"
placeholder={$LL.users.username()}
class="input input-bordered w-full max-w-xs"
required
bind:value={newUser.username}
@@ -337,14 +358,14 @@
<div class="form-control w-full max-w-xs">
<label class="label" for="password">
<div class="label-text">
<span>Password</span>
<span>{$LL.users.password()}</span>
<span class="text-error">*</span>
</div>
</label>
<input
id="password"
type="password"
placeholder="Password"
placeholder={$LL.users.password()}
class="input input-bordered w-full max-w-xs"
minlength="5"
maxlength="72"
@@ -355,14 +376,14 @@
<div class="form-control w-full max-w-xs">
<label class="label" for="passwordConfirm">
<div class="label-text">
<span>Password confirm</span>
<span>{$LL.users.password_confirm()}</span>
<span class="text-error">*</span>
</div>
</label>
<input
id="passwordConfirm"
type="password"
placeholder="Password"
placeholder={$LL.users.password_confirm()}
class="input input-bordered w-full max-w-xs"
minlength="5"
maxlength="72"
@@ -371,9 +392,11 @@
/>
</div>
</div>
<span class="badge text-error mt-4 self-center">* required field</span>
<span class="badge text-error mt-4 self-center">* {$LL.users.required_field()}</span>
<div class="card-actions justify-end">
<button type="submit" class="btn btn-success mt-2"><Fa icon={faPlus} />Add</button>
<button type="submit" class="btn btn-success mt-2"
><Fa icon={faPlus} />{$LL.buttons.add()}</button
>
</div>
</form>
</div>

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { toggleVisibility } from '$lib/helpers/forms';
import LL from '$lib/i18n/i18n-svelte';
import { pocketbase } from '$lib/stores/pocketbase';
import { settingsPub } from '$lib/stores/settings';
import { faArrowRight, faEye } from '@fortawesome/free-solid-svg-icons';
@@ -45,7 +46,7 @@
})
.catch((err) => {
if (err.data?.data?.passwordConfirm?.message) {
toast.error("Passwords don't match");
toast.error($LL.toasts.passwords_missmatch());
} else if (err.data?.data?.email?.message) {
toast.error(err.data.data.email.message);
} else {
@@ -61,20 +62,22 @@
{#if $settingsPub?.setup_completed}
<figure class="w-72 mx-auto pt-6"><img src="/gopher.svg" alt="Gopher" /></figure>
<div class="card-body">
<h2 class="card-title">I didn't expect you here! 🧐</h2>
<p>You are already done with the setup! Nothing to do.</p>
<h2 class="card-title">{$LL.welcome.not_expected_title()}</h2>
<p>{$LL.welcome.not_expected_desc()}</p>
<div class="card-actions justify-end">
<button class="btn btn-primary" on:click={() => goto('/')}>Take me back</button>
<button class="btn btn-primary" on:click={() => goto('/')}
>{$LL.welcome.not_expected_back()}</button
>
</div>
</div>
{:else if stepsCompleted === 0}
<figure class="w-44 mx-auto pt-6"><img src="/gopher.svg" alt="Gopher" /></figure>
<div class="card-body">
<h2 class="card-title">Welcome to UpSnap 🥳</h2>
<p>Please complete the following steps to finish the setup.</p>
<h2 class="card-title">{$LL.welcome.step1_page_title()}</h2>
<p>{$LL.welcome.step1_setup_desc()}</p>
<div class="card-actions justify-end">
<button class="btn btn-primary" on:click={() => (stepsCompleted = 1)}
>Next <Fa icon={faArrowRight} /></button
>{$LL.welcome.step1_setup_btn_next()} <Fa icon={faArrowRight} /></button
>
</div>
</div>
@@ -82,11 +85,11 @@
<div class="card-body">
<div class="flex flex-row gap-4">
<figure class="w-16"><img src="/gopher.svg" alt="Gopher" /></figure>
<h2 class="card-title">Create an admin account</h2>
<h2 class="card-title">{$LL.welcome.step2_page_title()}</h2>
</div>
<form class="form-control w-full" on:submit|preventDefault={register}>
<label class="label" for="email">
<span class="label-text">Email:</span>
<span class="label-text">{$LL.welcome.step2_label_email()}</span>
</label>
<input
id="email"
@@ -96,8 +99,8 @@
bind:value={form.email}
/>
<label class="label" for="password">
<span class="label-text">Password:</span>
<span class="label-text-alt">min. 10 characters</span>
<span class="label-text">{$LL.welcome.step2_label_password()}</span>
<span class="label-text-alt">{$LL.welcome.step2_label_min_chars()}</span>
</label>
<label class="relative block">
<div
@@ -120,7 +123,7 @@
/>
</label>
<label class="label" for="passwordConfirm">
<span class="label-text">Password confirm:</span>
<span class="label-text">{$LL.welcome.step2_label_password_confirm()}</span>
</label>
<label class="relative block">
<div
@@ -144,7 +147,7 @@
</label>
<div class="card-actions justify-end mt-4">
<button class="btn btn-primary" type="submit"
>Create <Fa icon={faArrowRight} /></button
>{$LL.welcome.step2_btn_create()} <Fa icon={faArrowRight} /></button
>
</div>
</form>
@@ -152,19 +155,21 @@
{:else if stepsCompleted === 2}
<figure class="w-72 mx-auto pt-6"><img src="/gopher.svg" alt="Gopher" /></figure>
<div class="card-body">
<h2 class="card-title">You are all set! 🎉</h2>
<p>Go ahead and add some devices to your dashboard.</p>
<h2 class="card-title">{$LL.welcome.step3_page_title()}</h2>
<p>{$LL.welcome.step3_page_desc()}</p>
<div class="card-actions justify-end">
<button class="btn btn-success" on:click={() => goto('/')}>Lets go!</button>
<button class="btn btn-success" on:click={() => goto('/')}
>{$LL.welcome.step3_btn_done()}</button
>
</div>
</div>
{/if}
</div>
{#if !$settingsPub?.setup_completed}
<ul class="steps steps-horizontal">
<li class="step step-primary">Welcome</li>
<li class="step" class:step-primary={stepsCompleted > 0}>Create account</li>
<li class="step" class:step-primary={stepsCompleted > 1}>Done</li>
<li class="step step-primary">{$LL.welcome.progress_step1()}</li>
<li class="step" class:step-primary={stepsCompleted > 0}>{$LL.welcome.progress_step2()}</li>
<li class="step" class:step-primary={stepsCompleted > 1}>{$LL.welcome.progress_step3()}</li>
</ul>
{/if}
</div>