🔀 Rebased from master

This commit is contained in:
Alicia Sykes
2022-03-29 00:58:44 +01:00
50 changed files with 865 additions and 249 deletions

View File

@@ -1,10 +1,10 @@
<template>
<div id="dashy">
<div id="dashy" :style="topLevelStyleModifications">
<EditModeTopBanner v-if="isEditMode" />
<LoadingScreen :isLoading="isLoading" v-if="shouldShowSplash" />
<Header :pageInfo="pageInfo" />
<router-view />
<Footer :text="footerText" v-if="visibleComponents.footer" />
<router-view v-if="!isFetching" />
<Footer :text="footerText" v-if="visibleComponents.footer && !isFetching" />
</div>
</template>
<script>
@@ -33,6 +33,7 @@ export default {
data() {
return {
isLoading: true, // Set to false after mount complete
isFetching: true, // Set to false after the conf has been fetched
};
},
watch: {
@@ -40,6 +41,9 @@ export default {
// When in edit mode, show confirmation dialog on page exit
window.onbeforeunload = isEditMode ? this.confirmExit : null;
},
config() {
this.isFetching = false;
},
},
computed: {
/* If the user has specified custom text for footer - get it */
@@ -68,9 +72,17 @@ export default {
isEditMode() {
return this.$store.state.editMode;
},
},
created() {
this.$store.dispatch(Keys.INITIALIZE_CONFIG);
topLevelStyleModifications() {
const vc = this.visibleComponents;
if (!vc.footer && !vc.pageTitle) {
return '--footer-height: 1rem;';
} else if (!vc.footer) {
return '--footer-height: 5rem;';
} else if (!vc.pageTitle) {
return '--footer-height: 4rem;';
}
return '';
},
},
methods: {
/* Injects the users custom CSS as a style tag */
@@ -135,7 +147,8 @@ export default {
},
},
/* Basic initialization tasks on app load */
mounted() {
async mounted() {
await this.$store.dispatch(Keys.INITIALIZE_CONFIG); // Initialize config before moving on
this.applyLanguage(); // Apply users local language
this.hideSplash(); // Hide the splash screen, if visible
if (this.appConfig.customCss) { // Inject users custom CSS, if present

View File

@@ -48,7 +48,7 @@
"change-language-button": "Change App Language",
"reset-settings-button": "Reset Local Settings",
"app-info-button": "App Info",
"backup-note": "It is recommend to make a backup of your configuration before making changes.",
"backup-note": "It is recommended to make a backup of your configuration before making changes.",
"reset-config-msg-l1": "This will remove all user settings from local storage, but won't effect your 'conf.yml' file.",
"reset-config-msg-l2": "You should first backup any changes you've made locally, if you want to use them in the future.",
"reset-config-msg-l3": "Are you sure you want to proceed?",
@@ -204,7 +204,7 @@
"start-editing-tooltip": "Enter the Interactive Editor",
"edit-site-data-subheading": "Edit Site Data",
"edit-page-info-btn": "Edit Page Info",
"edit-page-info-tooltip": "App title, description, nav links, footer text, etc",
"edit-page-info-tooltip": "App title, description, nav links, footer text, etc.",
"edit-app-config-btn": "Edit App Config",
"edit-app-config-tooltip": "All other app configuration options",
"config-save-methods-subheading": "Config Saving Options",

View File

@@ -1,7 +1,8 @@
{
"home": {
"no-results": "nessun risultato trovato",
"no-data": "Nessun dato configurato"
"no-results": "Nessun risultato trovato",
"no-data": "Nessun dato configurato",
"no-items-section": "Nessun elemento"
},
"search": {
"search-label": "Ricerca",
@@ -12,24 +13,25 @@
"login": {
"title": "Dashy",
"username-label": "Nome utente",
"password-label": "Parola d'ordine",
"password-label": "Password",
"login-button": "Login",
"remember-me-label": "Ricordami per",
"remember-me-never": "Mai",
"remember-me-hour": "4 ore",
"remember-me-day": "1 giorno",
"remember-me-week": "1 settimana",
"remember-me-long-time": "Molto tempo",
"error-missing-username": "Nome utente mancante",
"error-missing-password": "Password mancante",
"error-incorrect-username": "Utente non trovato",
"error-incorrect-password": "Password errata",
"success-message": "Entrando...",
"success-message": "Login in corso...",
"logout-message": "Disconnesso",
"already-logged-in-title": "Ha già effettuato il login",
"already-logged-in-text": "Hai effettuato l'accesso come",
"proceed-to-dashboard": "Procedi alla dashboard",
"log-out-button": "Disconnettersi",
"proceed-guest-button": "Procedi come ospite"
"already-logged-in-text": "Hai effettuato il login come",
"proceed-to-dashboard": "Vai alla dashboard",
"log-out-button": "Disconnessione",
"proceed-guest-button": "Procedi come Ospite"
},
"config": {
"main-tab": "Menu principale",
@@ -42,12 +44,12 @@
"edit-css-button": "Modifica CSS personalizzato",
"cloud-sync-button": "Abilita sincronizzazione cloud",
"edit-cloud-sync-button": "Modifica sincronizzazione cloud",
"rebuild-app-button": "Ricostruisci applicazione",
"rebuild-app-button": "Ricompila l'applicazione",
"change-language-button": "Cambia la lingua dell'app",
"reset-settings-button": "Ripristina impostazioni locali",
"app-info-button": "Informazioni sull'app",
"backup-note": "Si consiglia di eseguire un backup della configurazione prima di apportare modifiche.",
"reset-config-msg-l1": "Questo rimuoverà tutte le impostazioni utente dalla memoria locale, ma non influenzerà il tuo file 'conf.yml'.",
"reset-config-msg-l1": "Questo rimuoverà tutte le impostazioni utente dalla memoria locale, ma non modificherà il tuo file 'conf.yml'.",
"reset-config-msg-l2": "Dovresti prima eseguire il backup di tutte le modifiche apportate localmente, se desideri utilizzarle in futuro.",
"reset-config-msg-l3": "Sei sicuro di voler procedere?",
"data-cleared-msg": "Dati cancellati con successo",
@@ -58,14 +60,14 @@
"css-save-btn": "Salvare le modifiche",
"css-note-label": "Nota",
"css-note-l1": "Sarà necessario aggiornare la pagina affinché le modifiche abbiano effetto.",
"css-note-l2": "Le sostituzioni di stili sono memorizzate solo localmente, quindi si consiglia di fare una copia del proprio CSS.",
"css-note-l2": "Le personalizzazioni degli stili sono memorizzate solo localmente, quindi si consiglia di fare una copia del proprio CSS.",
"css-note-l3": "Per rimuovere tutti gli stili personalizzati, elimina i contenuti e premi Salva modifiche"
},
"alternate-views": {
"alternate-view-heading": "Cambia vista",
"default": "Predefinito",
"default": "Predefinita",
"workspace": "Area di lavoro",
"minimal": "Minimo"
"minimal": "Minima"
},
"settings": {
"theme-label": "Tema",
@@ -73,22 +75,22 @@
"layout-auto": "Auto",
"layout-horizontal": "Orizzontale",
"layout-vertical": "Verticale",
"item-size-label": "Dimensione articolo",
"item-size-label": "Dimensione Elemento",
"item-size-small": "Piccolo",
"item-size-medium": "medio",
"item-size-medium": "Medio",
"item-size-large": "Grande",
"config-launcher-label": "config",
"config-launcher-tooltip": "Aggiorna configurazione",
"config-launcher-label": "Configurazione",
"config-launcher-tooltip": "Aggiorna Configurazione",
"sign-out-tooltip": "Disconnessione",
"sign-in-tooltip": "Accesso",
"sign-in-welcome": "Ciao {nome utente}!"
"sign-in-welcome": "Ciao {username}!"
},
"updates": {
"app-version-note": "Versione Dash",
"up-to-date": "Aggiornato",
"app-version-note": "Versione Dashy",
"up-to-date": "Aggiornata",
"out-of-date": "Aggiornamento disponibile",
"unsupported-version-l1": "Stai utilizzando una versione non supportata di Dashy",
"unsupported-version-l2": "Per la migliore esperienza e le patch di sicurezza recenti, aggiorna a"
"unsupported-version-l2": "Per un miglior utilizzo e una maggior sicurezza, si consiglia l'aggiornamento alla versione"
},
"language-switcher": {
"title": "Cambia lingua applicazione",
@@ -97,52 +99,54 @@
"success-msg": "Lingua aggiornata a"
},
"theme-maker": {
"title": "Configuratore di temi",
"title": "Configurazione dei Temi",
"export-button": "Esporta variabili personalizzate",
"reset-button": "Ripristina stili per",
"reset-button": "Reset degli Stili per",
"show-all-button": "Mostra tutte le variabili",
"change-fonts-button": "Change Fonts",
"save-button": "Salva",
"cancel-button": "Annulla",
"saved-toast": "{theme} aggiornato con successo",
"copied-toast": "Dati del tema per {theme} copiati negli appunti",
"reset-toast": "Colori personalizzati per {theme} rimossi"
"copied-toast": "Dati del tema {theme} copiati negli appunti",
"reset-toast": "Rimossi Colori personalizzati per {theme}"
},
"config-editor": {
"save-location-label": "Salva l'indirizzo",
"location-local-label": "Applicare localmente",
"location-disk-label": "Scrivi modifiche al file di configurazione",
"save-location-label": "Posizione del salvataggio",
"location-local-label": "Salvataggio Locale",
"location-disk-label": "Salva le modifiche nel file di configurazione",
"save-button": "Salvare le modifiche",
"preview-button": "Preview Changes",
"valid-label": "La configurazione è valida",
"status-success-msg": "Compito completato",
"status-success-msg": "Attività completata",
"status-fail-msg": "Attività fallita",
"success-msg-disk": "File di configurazione scritto su disco con successo",
"success-msg-local": "Modifiche locali salvate con successo",
"success-note-l1": "L'app dovrebbe ricostruirsi automaticamente.",
"success-msg-disk": "File di configurazione scritto correttamente su disco",
"success-msg-local": "Modifiche locali salvate correttamente",
"success-note-l1": "L'app dovrebbe ricompilarsi automaticamente.",
"success-note-l2": "Questa operazione potrebbe richiedere fino a un minuto.",
"success-note-l3": "Sarà necessario aggiornare la pagina affinché le modifiche abbiano effetto.",
"error-msg-save-mode": "Seleziona una modalità di salvataggio: Locale o File",
"error-msg-save-mode": "Seleziona una modalità di salvataggio: Localmente o in un File",
"error-msg-cannot-save": "Si è verificato un errore durante il salvataggio della configurazione",
"error-msg-bad-json": "Errore in JSON, probabilmente non valido",
"error-msg-bad-json": "Errore nella struttura JSON, probabilmente non formattata correttamente",
"warning-msg-validation": "Avviso di convalida",
"not-admin-note": "Non puoi scrivere le modifiche su disco, perché non sei loggato come amministratore"
"not-admin-note": "Non puoi scrivere le modifiche su disco, perché non sei autenticato come amministratore"
},
"app-rebuild": {
"title": "Ricostruisci applicazione",
"rebuild-note-l1": "È necessaria una ricostruzione affinché le modifiche scritte nel file conf.yml abbiano effetto.",
"rebuild-note-l2": "Questo dovrebbe accadere automaticamente, ma in caso contrario, puoi attivarlo manualmente qui.",
"title": "Ricompila l'applicazione",
"rebuild-note-l1": "È necessaria una ricompilazione affinché le modifiche scritte nel file conf.yml abbiano effetto.",
"rebuild-note-l2": "La ricompilazione dovrebbe avvenire automaticamente, in caso contrario, può essere avviata manualmente.",
"rebuild-note-l3": "Questo non è richiesto per le modifiche memorizzate localmente.",
"rebuild-button": "Inizia a costruire",
"rebuilding-status-1": "Costruzione...",
"rebuild-button": "Inizia la ricompilazione",
"rebuilding-status-1": "Ricompilazione...",
"rebuilding-status-2": "Questo potrebbe richiedere alcuni minuti",
"error-permission": "Non hai l'autorizzazione per attivare questa azione",
"error-permission": "Non hai l'autorizzazione per avviare questa azione",
"success-msg": "Build completata con successo",
"fail-msg": "Operazione di compilazione non riuscita",
"reload-note": ora necessario ricaricare la pagina affinché le modifiche abbiano effetto",
"fail-msg": "Operazione di ricompilazione non riuscita",
"reload-note": "È necessario ricaricare la pagina affinché le modifiche abbiano effetto",
"reload-button": "Ricarica la pagina"
},
"cloud-sync": {
"title": "Backup e ripristino su cloud",
"intro-l1": "Il backup e il ripristino su cloud sono una funzionalità opzionale che ti consente di caricare la tua configurazione su Internet e quindi ripristinarla su qualsiasi altro dispositivo o istanza di Dashy.",
"title": "Backup e Restore su Cloud",
"intro-l1": "Il backup e il restore su cloud consentono di salvare la tua configurazione su un server internet e quindi ripristinarla su qualsiasi altro dispositivo o istanza Dashy.",
"intro-l2": "Tutti i dati sono completamente crittografati end-to-end con AES, utilizzando la tua password come chiave.",
"intro-l3": "Per maggiori informazioni, vedere il",
"backup-title-setup": "Fai un backup",
@@ -152,11 +156,11 @@
"backup-button-setup": "Backup",
"backup-button-update": "Aggiorna backup",
"backup-id-label": "Il tuo ID di backup",
"backup-id-note": "Questo viene utilizzato per ripristinare dai backup in un secondo momento. Quindi tienilo, insieme alla tua password, in un posto sicuro.",
"backup-id-note": "ID utilizzato per il restore dal cloud. Conservarlo, insieme alla password, in un posto sicuro.",
"restore-title": "Ripristina un backup",
"restore-id-label": "Ripristina ID",
"restore-password-label": "Parola d'ordine",
"restore-button": "Ristabilire",
"restore-password-label": "Password",
"restore-button": "Restore",
"backup-missing-password": "Password mancante",
"backup-error-unknown": "Impossibile elaborare la richiesta",
"backup-error-password": "Password errata. Inserisci la tua password attuale.",
@@ -164,9 +168,128 @@
"restore-success-msg": "Configurazione ripristinata con successo"
},
"menu": {
"sametab": "Apri nella scheda corrente",
"newtab": "Apri in una nuova scheda",
"modal": "Apri in modalità pop-up",
"workspace": "Apri nella vista dell'area di lavoro"
"open-section-title": "Open In",
"sametab": "Tab Attuale",
"newtab": "Nuovo Tab",
"modal": "Pop-Up",
"workspace": "Workspace",
"options-section-title": "Opzioni",
"edit-item": "Edita",
"move-item": "Copia/Sposta",
"remove-item": "Rimuovi"
},
"context-menus": {
"item": {
"open-section-title": "Apri In",
"sametab": "Tab Attuale",
"newtab": "Nuovo Tab",
"modal": "Pop-Up",
"workspace": "Workspace",
"clipboard": "Copia negli Appunti",
"options-section-title": "Opzioni",
"edit-item": "Edita",
"move-item": "Copia/Sposta",
"remove-item": "Rimuovi",
"copied-toast": "URL copiata negli appunti"
},
"section": {
"open-section": "Apri Sezione",
"edit-section": "Edita",
"move-section": "Sposta in",
"remove-section": "Rimuovi"
}
},
"interactive-editor": {
"menu": {
"start-editing-tooltip": "Apri l'Editor Interattivo",
"edit-site-data-subheading": "Modifica i dati del sito",
"edit-page-info-btn": "Modifica le informazioni della pagina",
"edit-page-info-tooltip": "Titolo App, descrizione, link navigazione, piè di pagina, etc.",
"edit-app-config-btn": "Modifica Configurazione App",
"edit-app-config-tooltip": "Altre opzioni di configurazione",
"config-save-methods-subheading": "Opzioni di salvataggio della configurazione",
"save-locally-btn": "Salva Localmente",
"save-locally-tooltip": "Save localmente, nell'area dati del browser. Il file di configurazione non verrà modificato, le modifiche sono salvate solo su questo dispositivo",
"save-disk-btn": "Salva su Disco",
"save-disk-tooltip": "Salva la configurazione su disco, nel file conf.yml. Verrà effettuato un backup, la configurazione attuale verrà sovrascritta",
"export-config-btn": "Esporta la Configurazione",
"export-config-tooltip": "Visualizza ed esporta la configurazione, in un file o negli appunti",
"cloud-backup-btn": "Backup nel Cloud",
"cloud-backup-tooltip": "Salva il backup criptato della configurazione nel cloud",
"edit-raw-config-btn": "Modifica la configurazione nativa",
"edit-raw-config-tooltip": "Visualizza e modifica la configurazione nativa con l'editor JSON",
"cancel-changes-btn": "Annulla le Modifiche",
"cancel-changes-tooltip": "Annulla le modifiche ed esci dalla modalità modifica. La configurazione attuale non verrà modificata",
"edit-mode-name": "Modalità Modifica",
"edit-mode-subtitle": "Sei in modalità modifica",
"edit-mode-description": "Puoi modificare la configurazione e verificare il risultato, ma prima del salvataggio, nessun cambiamento verrà preservato",
"save-stage-btn": "Salva",
"cancel-stage-btn": "Annulla"
},
"edit-item": {
"missing-title-err": "Il titolo è obbligatorio"
},
"edit-section": {
"edit-section-title": "Modifica Sezione",
"add-section-title": "Aggiungi Nuova Sezione",
"edit-tooltip": "Seleziona per modificare, tasto destro per altre opzioni",
"remove-confirm": "Sei sicuro di voler rimuovere questa sezione? Questa scelta può essere rivista successivamente."
},
"edit-app-config": {
"warning-msg-title": "Procedi con cautela",
"warning-msg-l1": "Queste opzioni sono necessarie per la configurazione avanzata dell'app",
"warning-msg-l2": "Se non sei sicuro di qualche opzione, controlla la",
"warning-msg-docs": "documentazione",
"warning-msg-l3": "per evitare spiacevoli conseguenze."
},
"export": {
"export-title": "Esporta Configurazione",
"copy-clipboard-btn": "Copia negli Appunti",
"copy-clipboard-tooltip": "Copia la configurazione negli appunti, in formato YAML",
"download-file-btn": "Salva file",
"download-file-tooltip": "Salva la configurazione dell'app in un file YAML",
"view-title": "View Config"
}
},
"widgets": {
"general": {
"loading": "Caricamento...",
"show-more": "Dettagli",
"show-less": "Minori dettagli",
"open-link": "Continua Lettura"
},
"pi-hole": {
"status-heading": "Stato"
},
"stat-ping": {
"up": "Online",
"down": "Offline"
},
"net-data": {
"cpu-chart-title": "Utilizzo CPU",
"mem-chart-title": "Utilizzo Memoria",
"mem-breakdown-title": "Dettaglio Utilizzo Memoria",
"load-chart-title": "Carico del Sistema"
},
"glances": {
"disk-space-free": "Utilizzabile",
"disk-space-used": "Utilizzato",
"disk-mount-point": "Mount Point",
"disk-file-system": "File System",
"disk-io-read": "Lettura",
"disk-io-write": "Scrittura",
"system-load-desc": "Numero di processi in attesa nella coda di esecuzione, calcolati sulla media di tutti i core"
},
"system-info": {
"uptime": "Uptime"
},
"flight-data": {
"arrivals": "Arrivi",
"departures": "Partenze"
},
"tfl-status": {
"good-service-all": "Buon servizio su tutte le Linee",
"good-service-rest": "Buon servizio su tutte le altre Linee"
}
}
}
}

View File

@@ -231,8 +231,8 @@ export default {
const newItem = item;
newItem.id = this.itemId;
if (newItem.hotkey) newItem.hotkey = parseInt(newItem.hotkey, 10);
const strToTags = (str) => {
const tagArr = str.split(',');
const strToTags = (tags) => {
const tagArr = (typeof tags === 'string') ? tags.split(',') : tags;
return tagArr.map((tag) => tag.trim().toLowerCase().replace(/[^a-z0-9]+/, ''));
};
const strToBool = (str) => {

View File

@@ -17,6 +17,8 @@
<h3>{{ title }}</h3>
<EditModeIcon v-if="isEditMode" @click="openEditModal"
v-tooltip="editTooltip()" class="edit-mode-item" />
<OpenIcon @click.prevent.stop="openContextMenu" @contextmenu.prevent
class="edit-mode-item" />
</label>
<div class="collapsible-content">
<div class="content-inner">
@@ -31,6 +33,7 @@
import { localStorageKeys } from '@/utils/defaults';
import Icon from '@/components/LinkItems/ItemIcon.vue';
import EditModeIcon from '@/assets/interface-icons/interactive-editor-edit-mode.svg';
import OpenIcon from '@/assets/interface-icons/config-open-settings.svg';
export default {
name: 'CollapsableContainer',
@@ -48,6 +51,7 @@ export default {
components: {
Icon,
EditModeIcon,
OpenIcon,
},
computed: {
isEditMode() {
@@ -244,6 +248,8 @@ export default {
float: right;
right: 0.5rem;
top: 0.5rem;
margin-left: 0.2rem;
margin-right: 0.2rem;
}
/* Makes sections fill available space */

View File

@@ -3,7 +3,7 @@
classes="dashy-modal">
<div slot="top-right" @click="hide()">Close</div>
<a @click="hide()" class="close-button" title="Close">x</a>
<iframe v-if="url" :src="url" @keydown.esc="close" class="frame"/>
<iframe v-if="url" :src="url" @keydown.esc="close" class="frame" allow="fullscreen" />
<div v-else class="no-url">No URL Specified</div>
</modal>
</template>

View File

@@ -1,9 +1,9 @@
<template ref="container">
<div :class="`item-wrapper wrap-size-${itemSize}`">
<a @click="beforeLaunchItem"
<a @click="itemClicked"
@mouseup.right="openContextMenu"
@contextmenu.prevent
:href="hyperLinkHref"
:href="url"
:target="anchorTarget"
:class="`item ${makeClassList}`"
v-tooltip="getTooltipOptions()"
@@ -91,6 +91,7 @@ export default {
statusCheckInterval: Number, // Num seconds beteween repeating checks
statusCheckAllowInsecure: Boolean, // Status check ignore SSL certs
statusCheckAcceptCodes: String, // Allow status checks to pass with a code other than 200
statusCheckMaxRedirects: Number, // Specify max number of redirects
parentSectionTitle: String, // Title of parent section (for add new)
isAddNew: Boolean, // Only set if 'fake' item used as Add New button
},
@@ -136,30 +137,6 @@ export default {
};
},
methods: {
/* Called when an item is clicked, manages the opening of modal & resets the search field */
beforeLaunchItem(e) {
if (this.isEditMode) { // If in edit mode, open settings, don't launch app
this.openItemSettings();
return;
}
if (e.altKey) {
e.preventDefault();
this.launchItem('modal');
} else if (this.accumulatedTarget === 'modal') {
this.launchItem('modal');
} else if (this.accumulatedTarget === 'workspace') {
this.launchItem('workspace');
} else if (this.accumulatedTarget === 'clipboard') {
this.launchItem('clipboard');
}
// Clear search bar
this.$emit('itemClicked');
// Update the most/ last used ledger, for smart-sorting
if (!this.appConfig.disableSmartSort) {
this.incrementMostUsedCount(this.id);
this.incrementLastUsedCount(this.id);
}
},
/* Returns configuration object for the tooltip */
getTooltipOptions() {
if (!this.description && !this.provider) return {}; // If no description, then skip
@@ -179,7 +156,6 @@ export default {
classes: `item-description-tooltip tooltip-is-${this.itemSize}`,
};
},
/* Open the Edit Item modal form */
openItemSettings() {
this.editMenuOpen = true;
this.contextMenuOpen = false;

View File

@@ -45,11 +45,11 @@ export default {
return this.$store.getters.appConfig;
},
/* Determines the type of icon */
iconType: function iconType() {
iconType() {
return this.determineImageType(this.icon);
},
/* Gets the icon path, dependent on icon type */
iconPath: function iconPath() {
iconPath() {
if (this.broken) return this.getFallbackIcon();
return this.getIconPath(this.icon, this.url);
},
@@ -176,7 +176,7 @@ export default {
},
/* Fetches the path of local images, from Docker container */
getLocalImagePath(img) {
return `${iconCdns.localPath}/${img}`;
return `/${iconCdns.localPath}/${img}`;
},
/* Formats the URL for fetching the generative icons */
getGenerativeIcon(url, cdn) {

View File

@@ -54,6 +54,7 @@
:statusCheckInterval="statusCheckInterval"
:statusCheckAllowInsecure="item.statusCheckAllowInsecure"
:statusCheckAcceptCodes="item.statusCheckAcceptCodes"
:statusCheckMaxRedirects="item.statusCheckMaxRedirects"
@itemClicked="$emit('itemClicked')"
@triggerModal="triggerModal"
:isAddNew="false"
@@ -166,7 +167,7 @@ export default {
return this.$store.state.editMode;
},
itemSize() {
return this.$store.getters.iconSize;
return this.displayData.itemSize || this.$store.getters.iconSize;
},
sortOrder() {
return this.displayData.sortBy || defaultSortOrder;

View File

@@ -1,7 +1,7 @@
<template>
<transition name="slide">
<div class="context-menu" v-if="show && !isMenuDisabled"
:style="posX && posY ? `top:${posY}px;left:${posX}px;` : ''">
:style="posX && posY ? calcPosition() : ''">
<!-- Open Options -->
<ul class="menu-section">
<li @click="openSection()">
@@ -59,6 +59,13 @@ export default {
removeSection() {
this.$emit('removeSection');
},
calcPosition() {
const bounds = this.$parent.$el.getBoundingClientRect();
const left = this.posX < (bounds.right + bounds.left) / 2;
const position = `top:${this.posY}px;${left ? 'left' : 'right'}:\
${left ? this.posX : document.documentElement.clientWidth - this.posX}px;`;
return position;
},
},
};
</script>

View File

@@ -19,6 +19,7 @@
:enableStatusCheck="shouldEnableStatusCheck(item.statusCheck)"
:statusCheckAllowInsecure="item.statusCheckAllowInsecure"
:statusCheckAcceptCodes="item.statusCheckAcceptCodes"
:statusCheckMaxRedirects="item.statusCheckMaxRedirects"
:statusCheckInterval="getStatusCheckInterval()"
@itemClicked="$emit('itemClicked')"
@triggerModal="triggerModal"

View File

@@ -5,15 +5,23 @@
@click="navVisible = !navVisible"
/>
<nav id="nav" v-if="navVisible">
<router-link
v-for="(link, index) in links"
:key="index"
:to="link.path"
:href="link.path"
:target="isUrl(link.path) ? '_blank' : ''"
rel="noopener noreferrer"
class="nav-item"
>{{link.title}}</router-link>
<!-- Render either router-link or anchor, depending if internal / external link -->
<template v-for="(link, index) in links">
<router-link v-if="!isUrl(link.path)"
:key="index"
:to="link.path"
class="nav-item"
>{{link.title}}
</router-link>
<a v-else
:key="index"
:href="link.path"
:target="determineTarget(link)"
class="nav-item"
rel="noopener noreferrer"
>{{link.title}}
</a>
</template>
</nav>
</div>
</template>
@@ -43,6 +51,16 @@ export default {
return screenWidth && screenWidth < 600;
},
isUrl: (str) => new RegExp(/(http|https):\/\/(\S+)(:[0-9]+)?/).test(str),
determineTarget(link) {
if (!link.target) return '_blank';
switch (link.target) {
case 'sametab': return '_self';
case 'newtab': return '_blank';
case 'parent': return '_parent';
case 'top': return '_top';
default: return undefined;
}
},
},
};
</script>

View File

@@ -476,10 +476,13 @@ export default {
/* Returns users specified widget options, or empty object */
widgetOptions() {
const options = this.widget.options || {};
const timeout = this.widget.timeout || 500;
const useProxy = this.appConfig.widgetsAlwaysUseProxy || !!this.widget.useProxy;
const updateInterval = this.widget.updateInterval !== undefined
? this.widget.updateInterval : null;
return { useProxy, updateInterval, ...options };
return {
timeout, useProxy, updateInterval, ...options,
};
},
/* A unique string to reference the widget by */
widgetRef() {

View File

@@ -1,6 +1,6 @@
<template>
<div class="web-content" :id="id">
<iframe :src="url" />
<iframe :src="url" allow="fullscreen" />
</div>
</template>

View File

@@ -14,8 +14,7 @@ export default {
credentials() {
if (this.options.username && this.options.password) {
const stringifiedUser = `${this.options.username}:${this.options.password}`;
const headers = { Authorization: `Basic ${window.btoa(stringifiedUser)}` };
return { headers };
return { Authorization: `Basic ${window.btoa(stringifiedUser)}` };
}
return null;
},

View File

@@ -106,8 +106,9 @@ const WidgetMixin = {
const CustomHeaders = options || null;
const headers = this.useProxy
? { 'Target-URL': endpoint, CustomHeaders: JSON.stringify(CustomHeaders) } : CustomHeaders;
const timeout = this.options.timeout || 500;
const requestConfig = {
method, url, headers, data,
method, url, headers, data, timeout,
};
// Make request
return new Promise((resolve, reject) => {

View File

@@ -1,6 +1,8 @@
/* eslint-disable no-param-reassign, prefer-destructuring */
import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';
import yaml from 'js-yaml';
import Keys from '@/utils/StoreMutations';
import ConfigAccumulator from '@/utils/ConfigAccumalator';
import { componentVisibility } from '@/utils/ConfigHelpers';
@@ -14,6 +16,7 @@ Vue.use(Vuex);
const {
INITIALIZE_CONFIG,
SET_CONFIG,
SET_REMOTE_CONFIG,
SET_MODAL_OPEN,
SET_LANGUAGE,
SET_ITEM_LAYOUT,
@@ -38,6 +41,7 @@ const {
const store = new Vuex.Store({
state: {
config: {},
remoteConfig: {}, // The configuration stored on the server
editMode: false, // While true, the user can drag and edit items + sections
modalOpen: false, // KB shortcut functionality will be disabled when modal is open
navigateConfToTab: undefined, // Used to switch active tab in config modal
@@ -126,6 +130,9 @@ const store = new Vuex.Store({
[SET_CONFIG](state, config) {
state.config = config;
},
[SET_REMOTE_CONFIG](state, config) {
state.remoteConfig = config;
},
[SET_LANGUAGE](state, lang) {
const newConfig = state.config;
newConfig.appConfig.language = lang;
@@ -271,7 +278,9 @@ const store = new Vuex.Store({
},
actions: {
/* Called when app first loaded. Reads config and sets state */
[INITIALIZE_CONFIG]({ commit }) {
async [INITIALIZE_CONFIG]({ commit }) {
// Get the config file from the server and store it for use by the accumulator
commit(SET_REMOTE_CONFIG, yaml.load((await axios.get('conf.yml')).data));
const deepCopy = (json) => JSON.parse(JSON.stringify(json));
const config = deepCopy(new ConfigAccumulator().config());
commit(SET_CONFIG, config);

View File

@@ -14,30 +14,34 @@ import {
} from '@/utils/defaults';
import ErrorHandler from '@/utils/ErrorHandler';
import { applyItemId } from '@/utils/SectionHelpers';
import conf from '../../public/conf.yml';
import $store from '@/store';
import buildConf from '../../public/conf.yml';
export default class ConfigAccumulator {
constructor() {
this.conf = conf;
this.conf = $store.state.remoteConfig;
}
/* App Config */
appConfig() {
let appConfigFile = {};
// Set app config from file
if (this.conf) appConfigFile = this.conf.appConfig || {};
if (this.conf) appConfigFile = this.conf.appConfig || buildConf.appConfig || {};
// Fill in defaults if anything missing
let usersAppConfig = defaultAppConfig;
if (localStorage[localStorageKeys.APP_CONFIG]) {
usersAppConfig = JSON.parse(localStorage[localStorageKeys.APP_CONFIG]);
} else if (appConfigFile !== {}) {
} else if (Object.keys(appConfigFile).length > 0) {
usersAppConfig = appConfigFile;
}
// Some settings have their own local storage keys, apply them here
usersAppConfig.layout = localStorage[localStorageKeys.LAYOUT_ORIENTATION]
|| appConfigFile.layout || defaultLayout;
usersAppConfig.iconSize = localStorage[localStorageKeys.ICON_SIZE]
|| appConfigFile.iconSize || defaultIconSize;
usersAppConfig.layout = appConfigFile.layout
|| localStorage[localStorageKeys.LAYOUT_ORIENTATION]
|| defaultLayout;
usersAppConfig.iconSize = appConfigFile.iconSize
|| localStorage[localStorageKeys.ICON_SIZE]
|| defaultIconSize;
// Don't let users modify users locally
if (appConfigFile.auth) usersAppConfig.auth = appConfigFile.auth;
// All done, return final appConfig object

View File

@@ -36,6 +36,18 @@
},
"path": {
"type": "string"
},
"target": {
"title": "Opening Method",
"type": "string",
"enum": [
"newtab",
"sametab",
"parent",
"top"
],
"default": "newtab",
"description": "Where / how the item is opened when it's clicked"
}
}
}
@@ -789,11 +801,16 @@
"description": "Allows for running status checks on insecure content/ non-HTTPS apps. Prevents checks failing for non-SSL sites"
},
"statusCheckAcceptCodes": {
"title": "Accepted HTTP Status Codes",
"title": "Status Check - Accepted HTTP Codes",
"type": "string",
"default": "",
"description": "If your service's response code is anything other than 2xx, then you can opt to specify an alternative success code"
},
"statusCheckMaxRedirects": {
"title": "Status Check - Max Redirects",
"type": "number",
"default": "0",
"description": "If your service redirects to another page, and you would like status checks to follow redirects, then specify the maximum number of redirects here"
},
"color": {
"title": "Custom Color",
"type": "string",
@@ -820,6 +837,21 @@
"type": "string",
"description": "The type of widget to use, see docs for supported options"
},
"updateInterval": {
"title": "Update Interval",
"type": "number",
"description": "Specified in seconds. If set, widget data will be re-retched at this interval to display up-to-date data"
},
"timeout": {
"title": "Timeout",
"type": "number",
"description": "Specified in milliseconds. If set, request will timeout after the specified interval. Defaults to 500/ half a sec"
},
"useProxy": {
"title": "Use Proxy?",
"type": "boolean",
"description": "If set to true, request will be proxied through the backend. Requires the Node server to be running"
},
"options": {
"title": "Widget Options",
"type": "object",

View File

@@ -1,7 +1,8 @@
import axios from 'axios';
import yaml from 'js-yaml';
import { register } from 'register-service-worker';
import { sessionStorageKeys } from '@/utils/defaults';
import { statusMsg, statusErrorMsg } from '@/utils/CoolConsole';
import conf from '../../public/conf.yml';
/* Sets a local storage item with the state from the SW lifecycle */
const setSwStatus = (swStateToSet) => {
@@ -31,7 +32,8 @@ const setSwStatus = (swStateToSet) => {
* Disable if not running in production
* Or disable if user specified to disable
*/
const shouldEnableServiceWorker = () => {
const shouldEnableServiceWorker = async () => {
const conf = yaml.load((await axios.get('conf.yml')).data);
if (conf && conf.appConfig && conf.appConfig.enableServiceWorker) {
setSwStatus({ disabledByUser: false });
return true;
@@ -51,8 +53,8 @@ const printSwStatus = (msg) => {
const swUrl = `${process.env.BASE_URL || '/'}service-worker.js`;
/* If service worker enabled, then register it, and print message when status changes */
const registerServiceWorker = () => {
if (shouldEnableServiceWorker()) {
const registerServiceWorker = async () => {
if (await shouldEnableServiceWorker()) {
register(swUrl, {
ready() {
setSwStatus({ ready: true });

View File

@@ -14,7 +14,7 @@ class KeycloakAuth {
const { auth } = getAppConfig();
const { serverUrl, realm, clientId } = auth.keycloak;
const initOptions = {
url: `${serverUrl}/auth`, realm, clientId, onLoad: 'login-required',
url: `${serverUrl}`, realm, clientId, onLoad: 'login-required',
};
this.keycloakClient = Keycloak(initOptions);

View File

@@ -2,6 +2,7 @@
const KEY_NAMES = [
'INITIALIZE_CONFIG',
'SET_CONFIG',
'SET_REMOTE_CONFIG',
'SET_MODAL_OPEN',
'SET_LANGUAGE',
'SET_EDIT_MODE',

View File

@@ -32,6 +32,7 @@ export const LoadExternalTheme = function th() {
const preloadTheme = (href) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = href;
document.head.appendChild(link);
return new Promise((resolve, reject) => {

View File

@@ -201,7 +201,7 @@ module.exports = {
generativeFallback: 'https://evatar.io/{icon}',
localPath: './item-icons',
faviconName: 'favicon.ico',
homeLabIcons: 'https://raw.githubusercontent.com/WalkxCode/dashboard-icons/master/png/{icon}.png',
homeLabIcons: 'https://raw.githubusercontent.com/walkxhub/dashboard-icons/master/png/{icon}.png',
homeLabIconsFallback: 'https://raw.githubusercontent.com/NX211/homer-icons/master/png/{icon}.png',
},
/* API endpoints for widgets that need to fetch external data */

View File

@@ -19,7 +19,7 @@
</router-link>
</div>
<!-- Main content, section for each group of items -->
<div v-if="checkTheresData(sections)"
<div v-if="checkTheresData(sections) || isEditMode"
:class="`item-group-container `
+ `orientation-${layout} `
+ `item-size-${itemSizeBound} `
@@ -50,7 +50,7 @@
<AddNewSection v-if="isEditMode" />
</div>
<!-- Show message when there's no data to show -->
<div v-if="checkIfResults()" class="no-data">
<div v-if="checkIfResults() && !isEditMode" class="no-data">
{{searchValue ? $t('home.no-results') : $t('home.no-data')}}
</div>
<!-- Show banner at bottom of screen, for Saving config changes -->
@@ -213,7 +213,7 @@ export default {
overflow: auto;
@extend .scroll-bar;
@include monitor-up {
max-width: 1400px;
max-width: 85%;
}
/* Options for alternate layouts, triggered by buttons */