Merge branch 'master' into update-simple-icons
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
<LoadingScreen :isLoading="isLoading" v-if="shouldShowSplash" />
|
||||
<Header :pageInfo="pageInfo" />
|
||||
<router-view v-if="!isFetching" />
|
||||
<CriticalError v-if="hasCriticalError" />
|
||||
<Footer :text="footerText" v-if="visibleComponents.footer && !isFetching" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -12,6 +13,7 @@
|
||||
import Header from '@/components/PageStrcture/Header.vue';
|
||||
import Footer from '@/components/PageStrcture/Footer.vue';
|
||||
import EditModeTopBanner from '@/components/InteractiveEditor/EditModeTopBanner.vue';
|
||||
import CriticalError from '@/components/PageStrcture/CriticalError.vue';
|
||||
import LoadingScreen from '@/components/PageStrcture/LoadingScreen.vue';
|
||||
import { welcomeMsg } from '@/utils/CoolConsole';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
@@ -29,6 +31,7 @@ export default {
|
||||
Footer,
|
||||
LoadingScreen,
|
||||
EditModeTopBanner,
|
||||
CriticalError,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -64,7 +67,7 @@ export default {
|
||||
return this.$store.getters.pageInfo;
|
||||
},
|
||||
sections() {
|
||||
return this.$store.getters.pageInfo;
|
||||
return this.$store.getters.sections;
|
||||
},
|
||||
visibleComponents() {
|
||||
return this.$store.getters.visibleComponents;
|
||||
@@ -72,6 +75,9 @@ export default {
|
||||
isEditMode() {
|
||||
return this.$store.state.editMode;
|
||||
},
|
||||
hasCriticalError() {
|
||||
return this.$store.state.criticalError;
|
||||
},
|
||||
subPageClassName() {
|
||||
const currentSubPage = this.$store.state.currentConfigInfo;
|
||||
return (currentSubPage && currentSubPage.pageId) ? currentSubPage.pageId : '';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"home": {
|
||||
"no-results": "لا نتائج للبحث",
|
||||
"no-data": "لم يتم تكوين بيانات"
|
||||
"no-data": "لا يوجد بيانات"
|
||||
},
|
||||
"search": {
|
||||
"search-label": "بحث",
|
||||
@@ -33,11 +33,11 @@
|
||||
},
|
||||
"config": {
|
||||
"main-tab": "القائمة الرئيسية",
|
||||
"view-config-tab": "مشاهدة ملف Config",
|
||||
"edit-config-tab": "تحرير التكوين",
|
||||
"view-config-tab": "مشاهده ملف الإعدادات",
|
||||
"edit-config-tab": "تغير ملف الاع",
|
||||
"custom-css-tab": "الأنماط المخصصة",
|
||||
"heading": "خيارات الإعداد",
|
||||
"download-config-button": "تنزيل Config",
|
||||
"download-config-button": "تنزيل ملف الإعدادات",
|
||||
"edit-config-button": "تحرير التكوين",
|
||||
"edit-css-button": "تحرير CSS مخصص",
|
||||
"cloud-sync-button": "قم بتمكين Cloud Sync",
|
||||
@@ -84,7 +84,7 @@
|
||||
"sign-in-welcome": "مرحبًا {username}!"
|
||||
},
|
||||
"updates": {
|
||||
"app-version-note": "نسخة متهورة",
|
||||
"app-version-note": "ملاحظة نسخة التطبيق",
|
||||
"up-to-date": "حتى الآن",
|
||||
"out-of-date": "التحديث متاح",
|
||||
"unsupported-version-l1": "أنت تستخدم إصدارًا غير مدعوم من Dashy",
|
||||
|
||||
@@ -1,421 +1,448 @@
|
||||
{
|
||||
"home":
|
||||
{
|
||||
"no-results": "keine Suchergebnisse",
|
||||
"no-data": "keine Daten konfiguriert",
|
||||
"no-items-section": "Noch keine Elemente zum Anzeigen"
|
||||
},
|
||||
"search":
|
||||
{
|
||||
"search-label": "Suche",
|
||||
"search-placeholder": "Tippen um zu filtern",
|
||||
"clear-search-tooltip": "Suchfeld leeren",
|
||||
"enter-to-search-web": "Drücke Enter um das Internet zu durchsuchen"
|
||||
},
|
||||
"login":
|
||||
{
|
||||
"title": "Dashy",
|
||||
"username-label": "Benutzername",
|
||||
"password-label": "Passwort",
|
||||
"login-button": "Anmelden",
|
||||
"remember-me-label": "Angemeldet bleiben für",
|
||||
"remember-me-never": "Niemals",
|
||||
"remember-me-hour": "4 Stunden",
|
||||
"remember-me-day": "1 Tag",
|
||||
"remember-me-week": "1 Woche",
|
||||
"remember-me-long-time": "Eine lange Zeit",
|
||||
"error-missing-username": "Benutzername fehlt",
|
||||
"error-missing-password": "Passwort fehlt",
|
||||
"error-incorrect-username": "Benutzer nicht gefunden",
|
||||
"error-incorrect-password": "Falsches Passwort",
|
||||
"success-message": "Anmeldung läuft...",
|
||||
"logout-message": "Abgemeldet",
|
||||
"already-logged-in-title": "Bereits angemeldet",
|
||||
"already-logged-in-text": "Angemeldet als",
|
||||
"proceed-to-dashboard": "Zum Dashboard fortfahren",
|
||||
"log-out-button": "Abmelden",
|
||||
"proceed-guest-button": "Als Gast fortfahren"
|
||||
},
|
||||
"config":
|
||||
{
|
||||
"main-tab": "Hauptmenü",
|
||||
"view-config-tab": "Konfiguration",
|
||||
"edit-config-tab": "Konfiguration bearbeiten",
|
||||
"custom-css-tab": "eigene CSS",
|
||||
"heading": "Konfigurationseinstellungen",
|
||||
"download-config-button": "Konfigurationsdownload",
|
||||
"edit-config-button": "Konfiguration bearbeiten",
|
||||
"edit-css-button": "CSS bearbeiten",
|
||||
"cloud-sync-button": "Cloud-Synchronisation aktivieren",
|
||||
"edit-cloud-sync-button": "Cloud-Synchronisation bearbeiten",
|
||||
"rebuild-app-button": "Anwendung neu kompilieren",
|
||||
"change-language-button": "App-Sprache ändern",
|
||||
"reset-settings-button": "lokale Einstellungen zurücksetzen",
|
||||
"app-info-button": "App Informationen",
|
||||
"backup-note": "Es wird empfohlen ein Backup der Konfiguration zu erstellen bevor Änderungen durchgeführt werden.",
|
||||
"reset-config-msg-l1": "Dadurch werden alle Benutzereinstellungen aus dem lokalen Speicher entfernt, dies hat jedoch keine Auswirkungen auf Ihre Datei 'conf.yml'.",
|
||||
"reset-config-msg-l2": "Sie sollten zuerst alle Änderungen, die Sie lokal vorgenommen haben, sichern, wenn Sie sie in Zukunft wiederverwenden möchten.",
|
||||
"reset-config-msg-l3": "Sind Sie sicher, dass Sie fortfahren möchten?",
|
||||
"data-cleared-msg": "Daten erfolgreich gelöscht",
|
||||
"actions-label": "Aktionen",
|
||||
"copy-config-label": "Konfiguration kopieren",
|
||||
"data-copied-msg": "Konfiguration wurde in die Zwischenablage kopiert",
|
||||
"reset-config-label": "Konfiguration zurücksetzen",
|
||||
"css-save-btn": "Änderungen speichern",
|
||||
"css-note-label": "Bemerkung",
|
||||
"css-note-l1": "Sie müssen die Seite aktualisieren, damit Ihre Änderungen wirksam werden.",
|
||||
"css-note-l2": "Stilüberschreibungen werden nur lokal gespeichert, daher wird empfohlen vorher eine Kopie Ihres CSS zu erstellen.",
|
||||
"css-note-l3": "Um alle benutzerdefinierten Stile zu entfernen löschen Sie den Inhalt und klicken Sie auf Änderungen speichern."
|
||||
},
|
||||
"alternate-views": {
|
||||
"alternate-view-heading": "Ansicht wechseln",
|
||||
"default": "Standard",
|
||||
"workspace": "Arbeitsplatz",
|
||||
"minimal": "Minimal"
|
||||
},
|
||||
"settings":
|
||||
{
|
||||
"theme-label": "Design",
|
||||
"layout-label": "Layout",
|
||||
"layout-auto": "Auto",
|
||||
"layout-horizontal": "Horizontal",
|
||||
"layout-vertical": "Vertikal",
|
||||
"item-size-label": "Itemgröße",
|
||||
"item-size-small": "klein",
|
||||
"item-size-medium": "mittel",
|
||||
"item-size-large": "groß",
|
||||
"config-launcher-label": "Konfiguration",
|
||||
"config-launcher-tooltip": "Konfiguration aktualisieren",
|
||||
"sign-out-tooltip": "Abmelden",
|
||||
"sign-in-tooltip": "Anmelden",
|
||||
"sign-in-welcome": "Hallo {username}!"
|
||||
},
|
||||
"updates":
|
||||
{
|
||||
"app-version-note": "Dashy Version",
|
||||
"up-to-date": "Aktuell",
|
||||
"out-of-date": "Update verfügbar",
|
||||
"unsupported-version-l1": "Sie verwenden eine nicht unterstützte Version von Dashy",
|
||||
"unsupported-version-l2": "Für die beste Erfahrung und aktuelle Sicherheitspatches aktualisieren Sie bitte auf"
|
||||
},
|
||||
"language-switcher":
|
||||
{
|
||||
"title": "Applikationssprache ändern",
|
||||
"dropdown-label": "Sprache auswählen",
|
||||
"save-button": "Speichern",
|
||||
"success-msg": "Sprache geändert auf"
|
||||
},
|
||||
"theme-maker":
|
||||
{
|
||||
"title": "Design Konfigurator",
|
||||
"export-button": "Benutzerdefinierte Variablen exportieren",
|
||||
"reset-button": "CSS zurücksetzen für",
|
||||
"show-all-button": "Alle Variablen anzeigen",
|
||||
"change-fonts-button": "Schriftart ändern",
|
||||
"save-button": "Speichern",
|
||||
"cancel-button": "Abbrechen",
|
||||
"saved-toast": "{theme} wurde erfolgreich aktualisiert",
|
||||
"copied-toast": "Designdaten für {theme} wurden in die Zwischenablage kopiert.",
|
||||
"reset-toast": "Benutzerdefinierte Farben für {theme} wurden entfernt"
|
||||
},
|
||||
"config-editor":
|
||||
{
|
||||
"save-location-label": "Speicherort",
|
||||
"location-local-label": "Lokal anwenden",
|
||||
"location-disk-label": "Änderungen in die Konfigurationsdatei schreiben",
|
||||
"save-button": "Änderungen speichern",
|
||||
"preview-button": "Vorschau der Änderungen",
|
||||
"valid-label": "Syntax ist gültig",
|
||||
"status-success-msg": "Aufgabe abgeschlossen",
|
||||
"status-fail-msg": "Aufgabe fehlgeschlagen",
|
||||
"success-msg-disk": "Konfigurationsdatei wurde erfolgreich auf die Festplatte geschrieben",
|
||||
"success-msg-local": "Lokale Änderungen wurden erfolgreich gespeichert",
|
||||
"success-note-l1": "Die Applikation sollte automatisch re-kompiliert werden.",
|
||||
"success-note-l2": "Dies kann bis zu einer Minute dauern.",
|
||||
"success-note-l3": "Sie müssen die Seite aktualisieren damit die Änderungen wirksam werden.",
|
||||
"error-msg-save-mode": "Bitte wählen Sie einen Speichermodus: Lokal oder Datei",
|
||||
"error-msg-cannot-save": "Beim Speichern der Konfiguration ist ein Fehler aufgetreten",
|
||||
"error-msg-bad-json": "Fehler in JSON-Daten, möglicherweise fehlerhafter Syntax",
|
||||
"warning-msg-validation": "Validierungswarnung",
|
||||
"not-admin-note": "Änderungen können nicht auf die Festplatte gespeichert werden, da Sie nicht als Administrator angemeldet sind"
|
||||
},
|
||||
"app-rebuild":
|
||||
{
|
||||
"title": "Applikation re-kompilieren",
|
||||
"rebuild-note-l1": "Damit die in die Datei conf.yml geschriebenen Änderungen wirksam werden ist ein re-kompilieren erforderlich.",
|
||||
"rebuild-note-l2": "Dies sollte automatisch passieren, aber falls nicht können Sie es hier manuell starten.",
|
||||
"rebuild-note-l3": "Dies ist bei lokal gespeicherten Änderungen nicht erforderlich.",
|
||||
"rebuild-button": "Start Kompilierung",
|
||||
"rebuilding-status-1": "Baue...",
|
||||
"rebuilding-status-2": "Das kann ein paar Minuten dauern",
|
||||
"error-permission": "Sie sind nicht berechtigt diese Aktion auszulösen",
|
||||
"success-msg": "Kompilierung erfolgreich abgeschlossen",
|
||||
"fail-msg": "Kompilierung fehlgeschlagen",
|
||||
"reload-note": "Ein neu Laden der Seite ist erforderlich, damit die Änderungen wirksam werden.",
|
||||
"reload-button": "Seite neu laden"
|
||||
},
|
||||
"cloud-sync":
|
||||
{
|
||||
"title": "Cloud Backup & Wiederherstellung",
|
||||
"intro-l1": "Cloud-Backup und Wiederherstellung ist eine optionale Funktion mit der Sie Ihre Konfiguration in das Internet hochladen und dann auf einem anderen Gerät oder einer anderen Dashy-Instanz wiederherstellen können.",
|
||||
"intro-l2": "Alle Daten sind vollständig Ende-zu-Ende mit AES verschlüsselt. Ihr Passwort wird als Schlüssel verwendet.",
|
||||
"intro-l3": "Weitere Informationen finden Sie im",
|
||||
"backup-title-setup": "Backup erstellen",
|
||||
"backup-title-update": "Backup aktualisieren",
|
||||
"password-label-setup": "Passwort auswählen",
|
||||
"password-label-update": "Passwort eingeben",
|
||||
"backup-button-setup": "Backup",
|
||||
"backup-button-update": "Backup aktualisieren",
|
||||
"backup-id-label": "Ihre Backup ID",
|
||||
"backup-id-note": "Diese wird zusammen mit dem Passwort benötigt um Ihr Backup wiederherzustellen. Bewahren Sie sie zusammen mit Ihrem Passwort an einem sicheren Ort auf.",
|
||||
"restore-title": "Backup wiederherstellen",
|
||||
"restore-id-label": "ID wiederherstellen",
|
||||
"restore-password-label": "Passwort",
|
||||
"restore-button": "Wiederherstellen",
|
||||
"backup-error-unknown": "Anfrage kann nicht verarbeitet werden",
|
||||
"backup-error-password": "Falsches Passwort. Bitte geben Sie Ihr aktuelles Passwort ein.",
|
||||
"backup-success-msg": "Erfolgreich abgeschlossen",
|
||||
"restore-success-msg": "Konfiguration erfolgreich wiederhergestellt"
|
||||
},
|
||||
"menu":
|
||||
{
|
||||
"open-section-title": "Öffnen in",
|
||||
"sametab": "Aktueller Tab",
|
||||
"newtab": "Neuer Tab",
|
||||
"modal": "Popup Modal",
|
||||
"workspace": "Arbeitsflächenansicht",
|
||||
"options-section-title": "Optionen",
|
||||
"edit-item": "Bearbeiten",
|
||||
"move-item": "Kopieren oder Verschieben",
|
||||
"remove-item": "Entfernen"
|
||||
},
|
||||
"context-menus":
|
||||
{
|
||||
"item":
|
||||
{
|
||||
"open-section-title": "Öffnen in",
|
||||
"sametab": "Aktueller Tab",
|
||||
"newtab": "Neuer Tab",
|
||||
"modal": "Popup Modal",
|
||||
"workspace": "Arbeitsflächenansicht",
|
||||
"clipboard": "In Zwischenablage kopieren",
|
||||
"options-section-title": "Optionen",
|
||||
"edit-item": "Bearbeiten",
|
||||
"move-item": "Kopieren oder Verschieben",
|
||||
"remove-item": "Entfernen",
|
||||
"copied-toast": "URL wurde in die Zwischenablage kopiert"
|
||||
},
|
||||
"section":
|
||||
{
|
||||
"open-section": "Sektion öffnen",
|
||||
"edit-section": "Bearbeiten",
|
||||
"expand-collapse": "Aus- / Einklappen",
|
||||
"move-section": "Verschieben nach",
|
||||
"remove-section": "Entfernen"
|
||||
}
|
||||
},
|
||||
"interactive-editor":
|
||||
{
|
||||
"menu":
|
||||
{
|
||||
"start-editing-tooltip": "Interaktiven Editor starten",
|
||||
"edit-site-data-subheading": "Seiteninformationen bearbeiten",
|
||||
"edit-page-info-btn": "Seiteninformationen bearbeiten",
|
||||
"edit-page-info-tooltip": "Applikationstitel, Beschreibung, Nav. links, Fußzeile, etc.",
|
||||
"edit-app-config-btn": "Applikationskonfiguration bearbeiten",
|
||||
"edit-app-config-tooltip": "Alle anderen Konfigurationsoptionen",
|
||||
"edit-pages-btn": "Seiten bearbeiten",
|
||||
"edit-pages-tooltip": "Hinzufügen oder entfernen von zusätzlichen Ansichten",
|
||||
"config-save-methods-subheading": "Speicheroptionen der Konfiguration",
|
||||
"save-locally-btn": "Lokal speichern",
|
||||
"save-locally-tooltip": "Konfiguration lokal im Browser speichern. Dies hat keinen Einfluss auf die Konfigurationsdatei, aber Änderungen werden nur in diesem Browser gespeichert",
|
||||
"save-disk-btn": "Auf Festplatte speichern",
|
||||
"save-disk-tooltip": "Konfigurationsdatei conf.yml speichern. Dies erzeugt ein Backup und überschreibt dann die existierende Konfigurationsdatei",
|
||||
"export-config-btn": "Konfiguration exportieren",
|
||||
"export-config-tooltip": "Konfiguration anzeigen und exportieren, entweder in eine Datei oder in die Zwischenablage",
|
||||
"cloud-backup-btn": "Cloud-Backup starten",
|
||||
"cloud-backup-tooltip": "Speichert ein verrschlüsseltes Backup in die Cloud",
|
||||
"edit-raw-config-btn": "Konfiguration als Rohdaten bearbeiten",
|
||||
"edit-raw-config-tooltip": "Anzeigen und bearbeiten der Konfiguration als Rohdaten im JSON-Editor",
|
||||
"cancel-changes-btn": "Verwerfen",
|
||||
"cancel-changes-tooltip": "Modifikationen zurücksetzen und Bearbeitungsmodus schließen. Dies hat keinen Einfluss auf die Konfigurationsdatei",
|
||||
"edit-mode-name": "Bearbeitung",
|
||||
"edit-mode-subtitle": "Sie sind im Bearbeitungsmodus",
|
||||
"edit-mode-description": "Das bedeutet, dass Änderungen an der Konfigurationsdatei vorgenommen werden können. Änderungen können vor dem Speichern betrachtet werden.",
|
||||
"save-stage-btn": "Speichern",
|
||||
"cancel-stage-btn": "Abbrechen",
|
||||
"save-locally-warning": "Wenn Sie fortfahren werden die Änderungen nur in Ihrem Browser gespeichert. Um die Konfiguration auf anderen Geräten zu nutzen sollten Sie sie exportieren. Möchten Sie fortfahren?"
|
||||
},
|
||||
"edit-item":
|
||||
{
|
||||
"missing-title-err": "Ein Titel is zwingend notwendig"
|
||||
},
|
||||
"edit-section":
|
||||
{
|
||||
"edit-section-title": "Sektion bearbeiten",
|
||||
"add-section-title": "Neue Sektion hinzufügen",
|
||||
"edit-tooltip": "Klicken zum Bearbeiten oder Rechtsklick für weitere Optionen",
|
||||
"remove-confirm": "Sind Sie sicher, dass sie diese Sektion entfernen möchten? Diese Aktion kann nicht rückgänging gemacht werden."
|
||||
},
|
||||
"edit-app-config":
|
||||
{
|
||||
"warning-msg-title": "Ab hier ist Vorsicht geboten",
|
||||
"warning-msg-l1": "Die folgenden Optionen sind für fortgeschrittene Konfigurationen.",
|
||||
"warning-msg-l2": "Sollten Felder unklar sein, konsultieren Sie die",
|
||||
"warning-msg-docs": "Dokumentation",
|
||||
"warning-msg-l3": "um unbeabsichtigte Folgen zu vermeiden."
|
||||
},
|
||||
"export":
|
||||
{
|
||||
"export-title": "Konfiguration exportieren",
|
||||
"copy-clipboard-btn": "In Zwischenablage kopieren",
|
||||
"copy-clipboard-tooltip": "Applikationskonfiguration als YAML in Zwischenablage kopieren",
|
||||
"download-file-btn": "Datei herunterladen",
|
||||
"download-file-tooltip": "Applikationskonfiguration auf Ihr Gerät herunterladen",
|
||||
"view-title": "Konfiguration anzeigen"
|
||||
}
|
||||
},
|
||||
"widgets":
|
||||
{
|
||||
"general":
|
||||
{
|
||||
"loading": "Lade...",
|
||||
"show-more": "Details",
|
||||
"show-less": "Weniger anzeigen",
|
||||
"open-link": "Weiterlesen"
|
||||
},
|
||||
"pi-hole":
|
||||
{
|
||||
"status-heading": "Status"
|
||||
},
|
||||
"stat-ping":
|
||||
{
|
||||
"up": "Online",
|
||||
"down": "Offline"
|
||||
},
|
||||
"net-data":
|
||||
{
|
||||
"cpu-chart-title": "CPU Historie",
|
||||
"mem-chart-title": "Speichernutzung",
|
||||
"mem-breakdown-title": "Speichernutzung",
|
||||
"load-chart-title": "Systemlast"
|
||||
},
|
||||
"glances":
|
||||
{
|
||||
"disk-space-free": "Frei",
|
||||
"disk-space-used": "Genutzt",
|
||||
"disk-mount-point": "Mount Punkt",
|
||||
"disk-file-system": "Dateisystem,",
|
||||
"disk-io-read": "Lesen",
|
||||
"disk-io-write": "Schreiben",
|
||||
"system-load-desc": "Prozesse in Warteschlange. (Durchschnitt aller Kerne)"
|
||||
},
|
||||
"system-info":
|
||||
{
|
||||
"uptime": "Uptime"
|
||||
},
|
||||
"flight-data":
|
||||
{
|
||||
"arrivals": "Ankünfte",
|
||||
"departures": "Abflüge"
|
||||
},
|
||||
"tfl-status":
|
||||
{
|
||||
"good-service-all": "Guter Service auf allen Leitungen",
|
||||
"good-service-rest": "Guter Service auf allen anderen Leitungen"
|
||||
},
|
||||
"synology-download":
|
||||
{
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"downloaded": "Heruntergeladen",
|
||||
"uploaded": "Hochgeladen",
|
||||
"remaining": "Verbleibend",
|
||||
"up": "Hoch",
|
||||
"down": "Runter"
|
||||
},
|
||||
"gluetun-status":
|
||||
{
|
||||
"vpn-ip": "VPN IP",
|
||||
"country": "Land",
|
||||
"region": "Bundesland",
|
||||
"city": "Stadt",
|
||||
"post-code": "Postleitzahl",
|
||||
"location": "Standort",
|
||||
"timezone": "Zeitzone",
|
||||
"organization": "Organisation"
|
||||
},
|
||||
"nextcloud":
|
||||
{
|
||||
"active": "Aktiv",
|
||||
"and": "und",
|
||||
"applications": "Anwendungen",
|
||||
"available": "Verfügbar",
|
||||
"away": "Abwesend",
|
||||
"cache-full": "CACHE VOLL",
|
||||
"chat-room": "Chatraum",
|
||||
"delete-all": "Alle löschen",
|
||||
"delete-notification": "Benachrichtigung löschen",
|
||||
"disabled": "Deaktivert",
|
||||
"disk-quota": "Disk Quota",
|
||||
"disk-space": "Disk Speicherplatz",
|
||||
"dnd": "Nicht stören",
|
||||
"email": "EMail",
|
||||
"enabled": "Aktiviert",
|
||||
"federated-shares-ucfirst": "Verbundsfreigaben",
|
||||
"federated-shares": "Verbundsfreigaben",
|
||||
"files": "Dateien{plural}",
|
||||
"free": "Frei",
|
||||
"groups": "Gruppen",
|
||||
"hit-rate": "Trefferrate",
|
||||
"hits": "Treffer",
|
||||
"home": "Zuhause",
|
||||
"in": "in",
|
||||
"keys": "Schlüssel",
|
||||
"last-24-hours": "Letzte 24 Stunden",
|
||||
"last-5-minutes": "in den letzten 5 Minuten",
|
||||
"last-hour": "in der letzten Stunde",
|
||||
"last-login": "Letzte Anmeldung",
|
||||
"last-restart": "Letzter Neustart",
|
||||
"load-averages": "Systemlast aller CPU-Kerne",
|
||||
"local-shares": "Lokale Freigaben",
|
||||
"local": "Lokal",
|
||||
"max-keys": "Maximale Schlüssel",
|
||||
"memory-used": "Speuchernutzung",
|
||||
"memory-utilisation": "Speuchernutzung",
|
||||
"memory": "Speicher",
|
||||
"misses": "Fehlschläge",
|
||||
"no-notifications": "Keine Benachrichtigungen",
|
||||
"no-pending-updates": "Keine ausstehenden Aktualisierungen",
|
||||
"nothing-to-show": "Momentan gibt es hier nichts zu zeigen",
|
||||
"of-which": "welche",
|
||||
"of": "von",
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
"other": "Andere",
|
||||
"overall": "Insgesamt",
|
||||
"private-link": "privater Link",
|
||||
"public-link": "öffentlicher Link",
|
||||
"quota-enabled": "Disk Quota ist {nicht}aktiviert für diesen Benutzer",
|
||||
"received": "Empfangen",
|
||||
"scripts": "Skripte",
|
||||
"sent": "Gesendet",
|
||||
"started": "Gestartet",
|
||||
"storages-by-type": "Speicher nach Typ",
|
||||
"storages": "Speicher{plural}",
|
||||
"strings-use": "Strings benutzen",
|
||||
"tasks": "Aufgaben",
|
||||
"total-files": "Dateien gesamt",
|
||||
"total-users": "Benutzer gesamt",
|
||||
"total": "Insgesamt",
|
||||
"until": "Bis",
|
||||
"updates-available-for": "Aktualisierungen sind verfügbar für",
|
||||
"updates-available": "Aktualisierungen{plural} verfügbar",
|
||||
"used": "benutzt",
|
||||
"user": "Benutzer",
|
||||
"using": "nutzt",
|
||||
"version": "Version",
|
||||
"wasted": "verschwendet"
|
||||
}
|
||||
"home": {
|
||||
"no-results": "keine Suchergebnisse",
|
||||
"no-data": "keine Daten konfiguriert",
|
||||
"no-items-section": "Noch keine Elemente zum Anzeigen"
|
||||
},
|
||||
"search": {
|
||||
"search-label": "Suche",
|
||||
"search-placeholder": "Tippe um zu filtern",
|
||||
"clear-search-tooltip": "Suchfeld leeren",
|
||||
"enter-to-search-web": "Drücke Enter um das Internet zu durchsuchen"
|
||||
},
|
||||
"splash-screen": {
|
||||
"loading": "Lädt"
|
||||
},
|
||||
"login": {
|
||||
"title": "Dashy",
|
||||
"guest-label": "Gastzugriff",
|
||||
"username-label": "Benutzername",
|
||||
"password-label": "Passwort",
|
||||
"login-button": "Anmelden",
|
||||
"remember-me-label": "Angemeldet bleiben für",
|
||||
"remember-me-never": "Niemals",
|
||||
"remember-me-hour": "4 Stunden",
|
||||
"remember-me-day": "1 Tag",
|
||||
"remember-me-week": "1 Woche",
|
||||
"remember-me-long-time": "Eine lange Zeit",
|
||||
"error-missing-username": "Benutzername fehlt",
|
||||
"error-missing-password": "Passwort fehlt",
|
||||
"error-incorrect-username": "Benutzer nicht gefunden",
|
||||
"error-incorrect-password": "Falsches Passwort",
|
||||
"success-message": "Anmeldung läuft...",
|
||||
"logout-message": "Abgemeldet",
|
||||
"already-logged-in-title": "Bereits angemeldet",
|
||||
"already-logged-in-text": "Angemeldet als",
|
||||
"proceed-to-dashboard": "Zum Dashboard fortfahren",
|
||||
"log-out-button": "Abmelden",
|
||||
"proceed-guest-button": "Als Gast fortfahren",
|
||||
"guest-intro-1": "Diese Instanz hat Gastzugriffe aktiviert",
|
||||
"guest-intro-2": "Gäste haben lesenden Zugriff auf Dashboards, können also keine Veränderungen auf die Festplatte schreiben.",
|
||||
"error": "Error",
|
||||
"error-no-user-configured": "Authentifizierung ist nicht aktiviert, oder es sind keine Benutzer konfiguriert",
|
||||
"error-go-home-button": "Geh nach Hause",
|
||||
"logged-in-guest": "Als Gast eingeloggt, Umleitung...",
|
||||
"error-guest-access": "Gastzugriff nicht erlaubt"
|
||||
},
|
||||
"app-info": {
|
||||
"title": "App Info",
|
||||
"error-log": "Fehlerprotokoll",
|
||||
"no-errors": "Keine kürzlichen Fehler erkannt",
|
||||
"help-support": "Hilfe & Support",
|
||||
"help-support-description": "Um beim Betrieb oder der Konfiguration von Dashy Hilfe zu bekommen, siehe die",
|
||||
"help-support-discussions": "Diskussionen",
|
||||
"support-dashy": "Dashy unterstützen",
|
||||
"support-dashy-description": "Für Wege, sich zu beteiligen, besuchen Sie die",
|
||||
"support-dashy-link": "Contributions Seite",
|
||||
"report-bug": "Melde einen Fehler",
|
||||
"report-bug-description": "Wenn Sie glauben, einen Fehler gefunden zu haben, dann bitte",
|
||||
"report-bug-link": "öffne ein Issue",
|
||||
"more-info": "Mehr Informationen",
|
||||
"source": "Quellcode",
|
||||
"documentation": "Dokumentation",
|
||||
"privacy-and-security": "Datenschutz & Sicherheit",
|
||||
"privacy-and-security-l1": "Für eine Übersicht, wie Dashy mit Ihren Daten umgeht, siehe die",
|
||||
"privacy-and-security-privacy-policy": "Datenschutzerklärung",
|
||||
"privacy-and-security-advice": "Für Anleitung, um Ihr Dashboard zu sichern, konsultieren Sie die",
|
||||
"privacy-and-security-advice-link": "Management Dokumentation",
|
||||
"privacy-and-security-security-issue": "Wenn Sie glauben, eine mögliche Sicherheitslücke gefunden zu haben, melde sie wie beschrieben in unserer",
|
||||
"privacy-and-security-security-policy": "Security Policy",
|
||||
"license": "Lizenz",
|
||||
"license-under": "Lizensiert unter",
|
||||
"licence-third-party": "Für Lizenzen von Drittanbietermodulen, siehe",
|
||||
"licence-third-party-link": "Legal",
|
||||
"list-contributors": "Für eine vollstandige Liste aller Beteiligten und Dank, siehe",
|
||||
"list-contributors-link": "Credits",
|
||||
"version": "Version"
|
||||
},
|
||||
"config": {
|
||||
"main-tab": "Hauptmenü",
|
||||
"view-config-tab": "Konfiguration",
|
||||
"edit-config-tab": "Konfiguration bearbeiten",
|
||||
"custom-css-tab": "eigene CSS",
|
||||
"heading": "Konfigurationseinstellungen",
|
||||
"download-config-button": "Konfigurationsdownload",
|
||||
"edit-config-button": "Konfiguration bearbeiten",
|
||||
"edit-css-button": "CSS bearbeiten",
|
||||
"cloud-sync-button": "Cloud-Synchronisation aktivieren",
|
||||
"edit-cloud-sync-button": "Cloud-Synchronisation bearbeiten",
|
||||
"rebuild-app-button": "Anwendung neu kompilieren",
|
||||
"change-language-button": "App-Sprache ändern",
|
||||
"reset-settings-button": "lokale Einstellungen zurücksetzen",
|
||||
"disabled-note": "Einige Konfigurationsoptionen wurden vom Administrator deaktivert",
|
||||
"small-screen-note": "Sie benutzen einen sehr kleinen Bildschirm. Einige Seiten in diesem Menü sind dafür möglicherweise nicht ideal.",
|
||||
"app-info-button": "App Informationen",
|
||||
"backup-note": "Es wird empfohlen ein Backup der Konfiguration zu erstellen bevor Änderungen durchgeführt werden.",
|
||||
"reset-config-msg-l1": "Dadurch werden alle Benutzereinstellungen aus dem lokalen Speicher entfernt, dies hat jedoch keine Auswirkungen auf Ihre Datei 'conf.yml'.",
|
||||
"reset-config-msg-l2": "Sie sollten zuerst alle Änderungen, die Sie lokal vorgenommen haben, sichern, wenn Sie sie in Zukunft wiederverwenden möchten.",
|
||||
"reset-config-msg-l3": "Sind Sie sicher, dass Sie fortfahren möchten?",
|
||||
"data-cleared-msg": "Daten erfolgreich gelöscht",
|
||||
"actions-label": "Aktionen",
|
||||
"copy-config-label": "Konfiguration kopieren",
|
||||
"data-copied-msg": "Konfiguration wurde in die Zwischenablage kopiert",
|
||||
"reset-config-label": "Konfiguration zurücksetzen",
|
||||
"css-save-btn": "Änderungen speichern",
|
||||
"css-note-label": "Bemerkung",
|
||||
"css-note-l1": "Sie müssen die Seite aktualisieren, damit Ihre Änderungen wirksam werden.",
|
||||
"css-note-l2": "Stilüberschreibungen werden nur lokal gespeichert, daher wird empfohlen vorher eine Kopie Ihres CSS zu erstellen.",
|
||||
"css-note-l3": "Um alle benutzerdefinierten Stile zu entfernen löschen Sie den Inhalt und klicken Sie auf Änderungen speichern.",
|
||||
"custom-css": {
|
||||
"title": "Eigenes CSS",
|
||||
"base-theme": "Basisdesign"
|
||||
}
|
||||
},
|
||||
"alternate-views": {
|
||||
"alternate-view-heading": "Ansicht wechseln",
|
||||
"default": "Standard",
|
||||
"workspace": "Arbeitsplatz",
|
||||
"minimal": "Minimal"
|
||||
},
|
||||
"settings": {
|
||||
"theme-label": "Design",
|
||||
"layout-label": "Layout",
|
||||
"layout-auto": "Auto",
|
||||
"layout-horizontal": "Horizontal",
|
||||
"layout-vertical": "Vertikal",
|
||||
"item-size-label": "Itemgröße",
|
||||
"item-size-small": "klein",
|
||||
"item-size-medium": "mittel",
|
||||
"item-size-large": "groß",
|
||||
"config-launcher-label": "Konfiguration",
|
||||
"config-launcher-tooltip": "Konfiguration aktualisieren",
|
||||
"sign-out-tooltip": "Abmelden",
|
||||
"sign-in-tooltip": "Anmelden",
|
||||
"sign-in-welcome": "Hallo {username}!",
|
||||
"hide": "Verstecke",
|
||||
"open": "Öffne"
|
||||
},
|
||||
"updates": {
|
||||
"app-version-note": "Dashy Version",
|
||||
"up-to-date": "Aktuell",
|
||||
"out-of-date": "Update verfügbar",
|
||||
"unsupported-version-l1": "Sie verwenden eine nicht unterstützte Version von Dashy",
|
||||
"unsupported-version-l2": "Für die beste Erfahrung und aktuelle Sicherheitspatches aktualisieren Sie bitte auf"
|
||||
},
|
||||
"language-switcher": {
|
||||
"title": "Applikationssprache ändern",
|
||||
"dropdown-label": "Sprache auswählen",
|
||||
"save-button": "Speichern",
|
||||
"success-msg": "Sprache geändert auf"
|
||||
},
|
||||
"theme-maker": {
|
||||
"title": "Design Konfigurator",
|
||||
"export-button": "Benutzerdefinierte Variablen exportieren",
|
||||
"reset-button": "CSS zurücksetzen für",
|
||||
"show-all-button": "Alle Variablen anzeigen",
|
||||
"change-fonts-button": "Schriftart ändern",
|
||||
"save-button": "Speichern",
|
||||
"cancel-button": "Abbrechen",
|
||||
"saved-toast": "{theme} wurde erfolgreich aktualisiert",
|
||||
"copied-toast": "Designdaten für {theme} wurden in die Zwischenablage kopiert.",
|
||||
"reset-toast": "Benutzerdefinierte Farben für {theme} wurden entfernt"
|
||||
},
|
||||
"config-editor": {
|
||||
"save-location-label": "Speicherort",
|
||||
"location-local-label": "Lokal anwenden",
|
||||
"location-disk-label": "Änderungen in die Konfigurationsdatei schreiben",
|
||||
"save-button": "Änderungen speichern",
|
||||
"preview-button": "Vorschau der Änderungen",
|
||||
"valid-label": "Syntax ist gültig",
|
||||
"status-success-msg": "Aufgabe abgeschlossen",
|
||||
"status-fail-msg": "Aufgabe fehlgeschlagen",
|
||||
"success-msg-disk": "Konfigurationsdatei wurde erfolgreich auf die Festplatte geschrieben",
|
||||
"success-msg-local": "Lokale Änderungen wurden erfolgreich gespeichert",
|
||||
"success-note-l1": "Die Applikation sollte automatisch re-kompiliert werden.",
|
||||
"success-note-l2": "Dies kann bis zu einer Minute dauern.",
|
||||
"success-note-l3": "Sie müssen die Seite aktualisieren damit die Änderungen wirksam werden.",
|
||||
"error-msg-save-mode": "Bitte wählen Sie einen Speichermodus: Lokal oder Datei",
|
||||
"error-msg-cannot-save": "Beim Speichern der Konfiguration ist ein Fehler aufgetreten",
|
||||
"error-msg-bad-json": "Fehler in JSON-Daten, möglicherweise fehlerhafter Syntax",
|
||||
"warning-msg-validation": "Validierungswarnung",
|
||||
"not-admin-note": "Änderungen können nicht auf die Festplatte gespeichert werden, da Sie nicht als Administrator angemeldet sind"
|
||||
},
|
||||
"app-rebuild": {
|
||||
"title": "Applikation neu kompilieren",
|
||||
"rebuild-note-l1": "Damit die in die Datei conf.yml geschriebenen Änderungen wirksam werden ist ein Neukompilieren erforderlich.",
|
||||
"rebuild-note-l2": "Dies sollte automatisch passieren, aber falls nicht können Sie es hier manuell starten.",
|
||||
"rebuild-note-l3": "Dies ist bei lokal gespeicherten Änderungen nicht erforderlich.",
|
||||
"rebuild-button": "Starte Kompilierung",
|
||||
"rebuilding-status-1": "Baue...",
|
||||
"rebuilding-status-2": "Das kann ein paar Minuten dauern",
|
||||
"error-permission": "Sie sind nicht berechtigt diese Aktion auszulösen",
|
||||
"success-msg": "Kompilierung erfolgreich abgeschlossen",
|
||||
"fail-msg": "Kompilierung fehlgeschlagen",
|
||||
"reload-note": "Ein neu Laden der Seite ist erforderlich, damit die Änderungen wirksam werden.",
|
||||
"reload-button": "Seite neu laden"
|
||||
},
|
||||
"cloud-sync": {
|
||||
"title": "Cloud Backup & Wiederherstellung",
|
||||
"intro-l1": "Cloud-Backup und Wiederherstellung ist eine optionale Funktion mit der Sie Ihre Konfiguration in das Internet hochladen und dann auf einem anderen Gerät oder einer anderen Dashy-Instanz wiederherstellen können.",
|
||||
"intro-l2": "Alle Daten sind vollständig Ende-zu-Ende mit AES verschlüsselt. Ihr Passwort wird als Schlüssel verwendet.",
|
||||
"intro-l3": "Weitere Informationen finden Sie in der",
|
||||
"intro-docs": "Dokumentation",
|
||||
"backup-title-setup": "Backup erstellen",
|
||||
"backup-title-update": "Backup aktualisieren",
|
||||
"password-label-setup": "Passwort auswählen",
|
||||
"password-label-update": "Passwort eingeben",
|
||||
"backup-button-setup": "Backup",
|
||||
"backup-button-update": "Backup aktualisieren",
|
||||
"backup-id-label": "Ihre Backup ID",
|
||||
"backup-id-note": "Diese wird zusammen mit dem Passwort benötigt um Ihr Backup wiederherzustellen. Bewahren Sie sie zusammen mit Ihrem Passwort an einem sicheren Ort auf.",
|
||||
"restore-title": "Backup wiederherstellen",
|
||||
"restore-id-label": "ID wiederherstellen",
|
||||
"restore-password-label": "Passwort",
|
||||
"restore-button": "Wiederherstellen",
|
||||
"backup-missing-password": "Passwort fehlt",
|
||||
"backup-error-unknown": "Anfrage kann nicht verarbeitet werden",
|
||||
"backup-error-password": "Falsches Passwort. Bitte geben Sie Ihr aktuelles Passwort ein.",
|
||||
"backup-success-msg": "Erfolgreich abgeschlossen",
|
||||
"restore-success-msg": "Konfiguration erfolgreich wiederhergestellt"
|
||||
},
|
||||
"menu": {
|
||||
"open-section-title": "Öffne in",
|
||||
"sametab": "Aktueller Tab",
|
||||
"newtab": "Neuer Tab",
|
||||
"modal": "Popup Modal",
|
||||
"workspace": "Arbeitsflächenansicht",
|
||||
"options-section-title": "Optionen",
|
||||
"edit-item": "Bearbeiten",
|
||||
"move-item": "Kopieren oder Verschieben",
|
||||
"remove-item": "Entfernen"
|
||||
},
|
||||
"context-menus": {
|
||||
"item": {
|
||||
"open-section-title": "Öffnen in",
|
||||
"sametab": "Aktueller Tab",
|
||||
"newtab": "Neuer Tab",
|
||||
"modal": "Popup Modal",
|
||||
"workspace": "Arbeitsflächenansicht",
|
||||
"clipboard": "In Zwischenablage kopieren",
|
||||
"options-section-title": "Optionen",
|
||||
"edit-item": "Bearbeiten",
|
||||
"move-item": "Kopieren oder Verschieben",
|
||||
"remove-item": "Entfernen",
|
||||
"copied-toast": "URL wurde in die Zwischenablage kopiert"
|
||||
},
|
||||
"section": {
|
||||
"open-section": "Sektion öffnen",
|
||||
"edit-section": "Bearbeiten",
|
||||
"expand-collapse": "Aus- / Einklappen",
|
||||
"move-section": "Verschieben nach",
|
||||
"remove-section": "Entfernen"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"dev-by": "Entwickelt von",
|
||||
"licensed-under": "Lizensiert unter",
|
||||
"get-the": "Hole dir den",
|
||||
"source-code": "Quellcode"
|
||||
},
|
||||
"interactive-editor": {
|
||||
"menu": {
|
||||
"start-editing-tooltip": "Interaktiven Editor starten",
|
||||
"edit-site-data-subheading": "Seiteninformationen bearbeiten",
|
||||
"edit-page-info-btn": "Seiteninformationen bearbeiten",
|
||||
"edit-page-info-tooltip": "Applikationstitel, Beschreibung, Nav. links, Fußzeile, etc.",
|
||||
"edit-app-config-btn": "Applikationskonfiguration bearbeiten",
|
||||
"edit-app-config-tooltip": "Alle anderen Konfigurationsoptionen",
|
||||
"edit-pages-btn": "Seiten bearbeiten",
|
||||
"edit-pages-tooltip": "Hinzufügen oder entfernen von zusätzlichen Ansichten",
|
||||
"config-save-methods-subheading": "Speicheroptionen der Konfiguration",
|
||||
"save-locally-btn": "Lokal speichern",
|
||||
"save-locally-tooltip": "Konfiguration lokal im Browser speichern. Dies hat keinen Einfluss auf die Konfigurationsdatei, aber Änderungen werden nur in diesem Browser gespeichert",
|
||||
"save-disk-btn": "Auf Festplatte speichern",
|
||||
"save-disk-tooltip": "Konfigurationsdatei conf.yml speichern. Dies erzeugt ein Backup und überschreibt dann die existierende Konfigurationsdatei",
|
||||
"export-config-btn": "Konfiguration exportieren",
|
||||
"export-config-tooltip": "Konfiguration anzeigen und exportieren, entweder in eine Datei oder in die Zwischenablage",
|
||||
"cloud-backup-btn": "Cloud-Backup starten",
|
||||
"cloud-backup-tooltip": "Speichert ein verrschlüsseltes Backup in die Cloud",
|
||||
"edit-raw-config-btn": "Konfiguration als Rohdaten bearbeiten",
|
||||
"edit-raw-config-tooltip": "Anzeigen und bearbeiten der Konfiguration als Rohdaten im JSON-Editor",
|
||||
"cancel-changes-btn": "Verwerfen",
|
||||
"cancel-changes-tooltip": "Modifikationen zurücksetzen und Bearbeitungsmodus schließen. Dies hat keinen Einfluss auf die Konfigurationsdatei",
|
||||
"edit-mode-name": "Bearbeitung",
|
||||
"edit-mode-subtitle": "Sie sind im Bearbeitungsmodus",
|
||||
"edit-mode-description": "Das bedeutet, dass Änderungen an der Konfigurationsdatei vorgenommen werden können. Änderungen können vor dem Speichern betrachtet werden.",
|
||||
"save-stage-btn": "Speichern",
|
||||
"cancel-stage-btn": "Abbrechen",
|
||||
"save-locally-warning": "Wenn Sie fortfahren werden die Änderungen nur in Ihrem Browser gespeichert. Um die Konfiguration auf anderen Geräten zu nutzen sollten Sie sie exportieren. Möchten Sie fortfahren?"
|
||||
},
|
||||
"edit-item": {
|
||||
"missing-title-err": "Ein Titel is zwingend notwendig"
|
||||
},
|
||||
"edit-section": {
|
||||
"edit-section-title": "Sektion bearbeiten",
|
||||
"add-section-title": "Neue Sektion hinzufügen",
|
||||
"edit-tooltip": "Klicken zum Bearbeiten oder Rechtsklick für weitere Optionen",
|
||||
"remove-confirm": "Sind Sie sicher, dass sie diese Sektion entfernen möchten? Diese Aktion kann nicht rückgänging gemacht werden."
|
||||
},
|
||||
"edit-app-config": {
|
||||
"warning-msg-title": "Ab hier ist Vorsicht geboten",
|
||||
"warning-msg-l1": "Die folgenden Optionen sind für fortgeschrittene Konfigurationen.",
|
||||
"warning-msg-l2": "Sollten Felder unklar sein, konsultieren Sie die",
|
||||
"warning-msg-docs": "Dokumentation",
|
||||
"warning-msg-l3": "um unbeabsichtigte Folgen zu vermeiden."
|
||||
},
|
||||
"export": {
|
||||
"export-title": "Konfiguration exportieren",
|
||||
"copy-clipboard-btn": "In Zwischenablage kopieren",
|
||||
"copy-clipboard-tooltip": "Applikationskonfiguration als YAML in Zwischenablage kopieren",
|
||||
"download-file-btn": "Datei herunterladen",
|
||||
"download-file-tooltip": "Applikationskonfiguration auf Ihr Gerät herunterladen",
|
||||
"view-title": "Konfiguration anzeigen"
|
||||
}
|
||||
},
|
||||
"widgets": {
|
||||
"general": {
|
||||
"loading": "Lade...",
|
||||
"show-more": "Details",
|
||||
"cpu-details": "Details CPU",
|
||||
"mem-details": "Details Arbeitsspeicher",
|
||||
"show-less": "Weniger anzeigen",
|
||||
"open-link": "Weiterlesen"
|
||||
},
|
||||
"pi-hole": {
|
||||
"status-heading": "Status"
|
||||
},
|
||||
"stat-ping": {
|
||||
"up": "Online",
|
||||
"down": "Offline"
|
||||
},
|
||||
"net-data": {
|
||||
"cpu-chart-title": "CPU Historie",
|
||||
"mem-chart-title": "Speichernutzung",
|
||||
"mem-breakdown-title": "Speichernutzung",
|
||||
"load-chart-title": "Systemlast"
|
||||
},
|
||||
"glances": {
|
||||
"disk-space-free": "Frei",
|
||||
"disk-space-used": "Genutzt",
|
||||
"disk-mount-point": "Mountpunkt",
|
||||
"disk-file-system": "Dateisystem,",
|
||||
"disk-io-read": "Lesen",
|
||||
"disk-io-write": "Schreiben",
|
||||
"system-load-desc": "Prozesse in der Warteschlange, Durchschnitt aller Kerne"
|
||||
},
|
||||
"system-info": {
|
||||
"uptime": "Uptime"
|
||||
},
|
||||
"flight-data": {
|
||||
"arrivals": "Ankünfte",
|
||||
"departures": "Abflüge"
|
||||
},
|
||||
"tfl-status": {
|
||||
"good-service-all": "Guter Service auf allen Leitungen",
|
||||
"good-service-rest": "Guter Service auf allen anderen Leitungen"
|
||||
},
|
||||
"synology-download": {
|
||||
"download": "Download",
|
||||
"upload": "Upload",
|
||||
"downloaded": "Heruntergeladen",
|
||||
"uploaded": "Hochgeladen",
|
||||
"remaining": "Verbleibend",
|
||||
"up": "Hoch",
|
||||
"down": "Runter"
|
||||
},
|
||||
"gluetun-status": {
|
||||
"vpn-ip": "VPN IP",
|
||||
"country": "Land",
|
||||
"region": "Bundesland",
|
||||
"city": "Stadt",
|
||||
"post-code": "Postleitzahl",
|
||||
"location": "Standort",
|
||||
"timezone": "Zeitzone",
|
||||
"organization": "Organisation"
|
||||
},
|
||||
"nextcloud": {
|
||||
"active": "Aktiv",
|
||||
"and": "und",
|
||||
"applications": "Anwendungen",
|
||||
"available": "Verfügbar",
|
||||
"away": "Abwesend",
|
||||
"cache-full": "CACHE VOLL",
|
||||
"chat-room": "Chatraum",
|
||||
"delete-all": "Alle löschen",
|
||||
"delete-notification": "Benachrichtigung löschen",
|
||||
"disabled": "deaktivert",
|
||||
"disk-quota": "Disk Quota",
|
||||
"disk-space": "Disk Speicherplatz",
|
||||
"dnd": "Nicht stören",
|
||||
"email": "E-Mail",
|
||||
"enabled": "aktiviert",
|
||||
"federated-shares-ucfirst": "Föderierte Freigaben",
|
||||
"federated-shares": "föderierte Freigaben",
|
||||
"files": "Dateien",
|
||||
"free": "frei",
|
||||
"groups": "Gruppen",
|
||||
"hit-rate": "Trefferrate",
|
||||
"hits": "Treffer",
|
||||
"home": "Zuhause",
|
||||
"in": "in",
|
||||
"keys": "Schlüssel",
|
||||
"last-24-hours": "letzte 24 Stunden",
|
||||
"last-5-minutes": "in den letzten 5 Minuten",
|
||||
"last-hour": "in der letzten Stunde",
|
||||
"last-login": "Letzte Anmeldung",
|
||||
"last-restart": "Letzter Neustart",
|
||||
"load-averages": "Systemlast aller CPU-Kerne",
|
||||
"local-shares": "Lokale Freigaben",
|
||||
"local": "lokal",
|
||||
"max-keys": "Maximale Schlüssel",
|
||||
"memory-used": "Speichernutzung",
|
||||
"memory-utilisation": "Speichernutzung",
|
||||
"memory": "Speicher",
|
||||
"misses": "Fehlschläge",
|
||||
"no-notifications": "Keine Benachrichtigungen",
|
||||
"no-pending-updates": "keine ausstehenden Aktualisierungen",
|
||||
"nothing-to-show": "Momentan gibt es hier nichts zu zeigen",
|
||||
"of-which": "welche",
|
||||
"of": "von",
|
||||
"offline": "Offline",
|
||||
"online": "Online",
|
||||
"other": "andere",
|
||||
"overall": "Insgesamt",
|
||||
"private-link": "privater Link",
|
||||
"public-link": "öffentlicher Link",
|
||||
"quota-enabled": "Disk Quota ist {nicht}aktiviert für diesen Benutzer",
|
||||
"received": "empfangen",
|
||||
"scripts": "Skripte",
|
||||
"sent": "gesendet",
|
||||
"started": "gestartet",
|
||||
"storages-by-type": "Speicher nach Typ",
|
||||
"storages": "Speicher",
|
||||
"strings-use": "Strings benutzen",
|
||||
"tasks": "Aufgaben",
|
||||
"total-files": "Dateien gesamt",
|
||||
"total-users": "Benutzer gesamt",
|
||||
"total": "insgesamt",
|
||||
"until": "Bis",
|
||||
"updates-available-for": "Aktualisierungen sind verfügbar für",
|
||||
"updates-available": "Aktualisierungen verfügbar",
|
||||
"used": "benutzt",
|
||||
"user": "Benutzer",
|
||||
"using": "nutzt",
|
||||
"version": "Version",
|
||||
"wasted": "verschwendet"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
"privacy-and-security": "Privacy & Security",
|
||||
"privacy-and-security-l1": "For a break-down of how your data is managed by Dashy, see the",
|
||||
"privacy-and-security-privacy-policy": "Privacy Policy",
|
||||
"app-info.privacy-and-security-advice": "For advise in securing your dashboard, you can reference the",
|
||||
"app-info.privacy-and-security-advice-link": "Management Docs",
|
||||
"privacy-and-security-advice": "For advise in securing your dashboard, you can reference the",
|
||||
"privacy-and-security-advice-link": "Management Docs",
|
||||
"privacy-and-security-security-issue": "If you've found a potential security issue, report it following our",
|
||||
"privacy-and-security-security-policy": "Security Policy",
|
||||
"license": "License",
|
||||
@@ -171,9 +171,9 @@
|
||||
"status-fail-msg": "Task Failed",
|
||||
"success-msg-disk": "Config file written to disk successfully",
|
||||
"success-msg-local": "Local changes saved successfully",
|
||||
"success-note-l1": "The app should rebuild automatically.",
|
||||
"success-note-l2": "This may take up to a minute.",
|
||||
"success-note-l3": "You will need to refresh the page for changes to take effect.",
|
||||
"success-note-l1": "You will need to refresh the page for changes to take effect.",
|
||||
"success-note-l2": "",
|
||||
"success-note-l3": "",
|
||||
"error-msg-save-mode": "Please select a Save Mode: Local or File",
|
||||
"error-msg-cannot-save": "An error occurred saving config",
|
||||
"error-msg-bad-json": "Error in JSON, possibly malformed",
|
||||
@@ -182,9 +182,9 @@
|
||||
},
|
||||
"app-rebuild": {
|
||||
"title": "Rebuild Application",
|
||||
"rebuild-note-l1": "A rebuild is required for changes written to the conf.yml file to take effect.",
|
||||
"rebuild-note-l2": "This should happen automatically, but if it hasn't, you can manually trigger it here.",
|
||||
"rebuild-note-l3": "This is not required for modifications stored locally.",
|
||||
"rebuild-note-l1": "A rebuild is no longer required for changes to take effect.",
|
||||
"rebuild-note-l2": "Some changes (entry-point, and auth settings) are read at build-time. So to apply these, you should trigger a rebuild here.",
|
||||
"rebuild-note-l3": "Note that this is only available on Node and Docker installations, not via statically deployed instances.",
|
||||
"rebuild-button": "Start Build",
|
||||
"rebuilding-status-1": "Building...",
|
||||
"rebuilding-status-2": "This may take a few minutes",
|
||||
@@ -312,10 +312,20 @@
|
||||
"view-title": "View Config"
|
||||
}
|
||||
},
|
||||
"critical-error": {
|
||||
"title": "Configuration Load Error",
|
||||
"subtitle": "Dashy has failed to load correctly due to a configuration error.",
|
||||
"sub-ensure-that": "Ensure that",
|
||||
"sub-error-details": "Error Details",
|
||||
"sub-next-steps": "Next Steps",
|
||||
"ignore-button": "Ignore Critical Errors"
|
||||
},
|
||||
"widgets": {
|
||||
"general": {
|
||||
"loading": "Loading...",
|
||||
"show-more": "Expand Details",
|
||||
"cpu-details": "CPU Details",
|
||||
"mem-details": "Memory Details",
|
||||
"show-less": "Show Less",
|
||||
"open-link": "Continue Reading"
|
||||
},
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
"privacy-and-security": "Privacidade e seguridade",
|
||||
"privacy-and-security-l1": "Para obter detalles sobre como os teus datos son xestionados por Dashy, consulta a",
|
||||
"privacy-and-security-privacy-policy": "Política de privacidade",
|
||||
"app-info.privacy-and-security-advice": "Para consellos sobre como asegurar o teu panel de control, consulta os",
|
||||
"app-info.privacy-and-security-advice-link": "Documentos de xestión",
|
||||
"privacy-and-security-advice": "Para consellos sobre como asegurar o teu panel de control, consulta os",
|
||||
"privacy-and-security-advice-link": "Documentos de xestión",
|
||||
"privacy-and-security-security-issue": "Se atopaches un problema de seguridade potencial, informa seguindo a nosa",
|
||||
"privacy-and-security-security-policy": "Política de seguridade",
|
||||
"license": "Licenza",
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
"privacy-and-security": "プライバシーとセキュリティー",
|
||||
"privacy-and-security-l1": "Daisyがどのようにあなたのデータを管理するかについての概要は、こちらをご覧ください: ",
|
||||
"privacy-and-security-privacy-policy": "プライバシーポリシー",
|
||||
"app-info.privacy-and-security-advice": "ダッシュボードを安全に保つためのアドバイスは、こちらを参照してください: ",
|
||||
"app-info.privacy-and-security-advice-link": "管理ドキュメント",
|
||||
"privacy-and-security-advice": "ダッシュボードを安全に保つためのアドバイスは、こちらを参照してください: ",
|
||||
"privacy-and-security-advice-link": "管理ドキュメント",
|
||||
"privacy-and-security-security-issue": "セキュリティー問題の可能性のある事象を見つけた場合、セキュリティーポリシーに従ってください: ",
|
||||
"privacy-and-security-security-policy": "セキュリティーポリシー",
|
||||
"license": "ライセンス",
|
||||
|
||||
@@ -10,8 +10,12 @@
|
||||
"clear-search-tooltip": "Limpar busca",
|
||||
"enter-to-search-web": "Tecle enter para buscar na rede"
|
||||
},
|
||||
"splash-screen": {
|
||||
"loading": "Carregando"
|
||||
},
|
||||
"login": {
|
||||
"title": "Dashy",
|
||||
"guest-label": "Entrar como convidado",
|
||||
"username-label": "Nome do usuário",
|
||||
"password-label": "Senha",
|
||||
"login-button": "Conectar",
|
||||
@@ -31,8 +35,20 @@
|
||||
"already-logged-in-text": "Você está logado como",
|
||||
"proceed-to-dashboard": "Ir para o Painel",
|
||||
"log-out-button": "Sair",
|
||||
"proceed-guest-button": "Seguir como Convidado"
|
||||
"proceed-guest-button": "Seguir como Convidado",
|
||||
"guest-intro-1": "Essa instância possui o acesso como convidado ativado",
|
||||
"guest-intro-2": "Convidados podem apenas visualizar os painéis, logo não podem escrever quaisquer mudanças no disco.",
|
||||
"error": "Erro",
|
||||
"error-no-user-configured": "Autenticação não está habilitada, ou nenhum usuário foi configurado.",
|
||||
"error-go-home-button": "Ir para a página inicial",
|
||||
"logged-in-guest": "Logado como convidado. Redirecionando...",
|
||||
"error-guest-access": "O acesso como convidado não foi permitido"
|
||||
},
|
||||
|
||||
"app-info": {
|
||||
"title": "Informação do App"
|
||||
},
|
||||
|
||||
"config": {
|
||||
"main-tab": "Menu Principal",
|
||||
"view-config-tab": "Ver configuração",
|
||||
@@ -299,6 +315,16 @@
|
||||
"remaining": "Restante",
|
||||
"up": "Up",
|
||||
"down": "Down"
|
||||
},
|
||||
"gluetun-status": {
|
||||
"vpn-ip": "IP da VPN",
|
||||
"country": "País",
|
||||
"region": "Região",
|
||||
"city": "Cidade",
|
||||
"post-code": "Código postal",
|
||||
"location": "Localização",
|
||||
"timezone": "Fuso horário",
|
||||
"organization": "Organização"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
448
src/assets/locales/ro.json
Normal file
448
src/assets/locales/ro.json
Normal file
@@ -0,0 +1,448 @@
|
||||
{
|
||||
"home": {
|
||||
"no-results": "Niciun rezultat găsit",
|
||||
"no-data": "Nicio dată configurată",
|
||||
"no-items-section": "Niciun element de afișat încă"
|
||||
},
|
||||
"search": {
|
||||
"search-label": "Caută",
|
||||
"search-placeholder": "Începeți să tastați pentru a filtra",
|
||||
"clear-search-tooltip": "Șterge căutarea",
|
||||
"enter-to-search-web": "Apasă enter pentru a căuta pe web"
|
||||
},
|
||||
"splash-screen": {
|
||||
"loading": "Încărcare"
|
||||
},
|
||||
"login": {
|
||||
"title": "Dashy",
|
||||
"guest-label": "Acces Vizitator",
|
||||
"username-label": "Nume utilizator",
|
||||
"password-label": "Parolă",
|
||||
"login-button": "Autentificare",
|
||||
"remember-me-label": "Ține-mă minte pentru",
|
||||
"remember-me-never": "Niciodată",
|
||||
"remember-me-hour": "4 Ore",
|
||||
"remember-me-day": "1 Zi",
|
||||
"remember-me-week": "1 Săptămână",
|
||||
"remember-me-long-time": "O perioadă lungă",
|
||||
"error-missing-username": "Lipsește numele de utilizator",
|
||||
"error-missing-password": "Lipsește parola",
|
||||
"error-incorrect-username": "Utilizator negăsit",
|
||||
"error-incorrect-password": "Parolă incorectă",
|
||||
"success-message": "Autentificare...",
|
||||
"logout-message": "Deconectat",
|
||||
"already-logged-in-title": "Deja autentificat",
|
||||
"already-logged-in-text": "Ești autentificat ca",
|
||||
"proceed-to-dashboard": "Continuă către Tabloul de bord",
|
||||
"log-out-button": "Deconectare",
|
||||
"proceed-guest-button": "Continuă ca Vizitator",
|
||||
"guest-intro-1": "Această instanță are acces pentru vizitatori activat.",
|
||||
"guest-intro-2": "Vizitatorii au acces doar pentru vizualizare la tablourile de bord, deci nu pot scrie modificări pe disc.",
|
||||
"error": "Eroare",
|
||||
"error-no-user-configured": "Autentificarea nu este activată, sau nu au fost configurați utilizatori",
|
||||
"error-go-home-button": "Mergi la Pagina Principală",
|
||||
"logged-in-guest": "Autentificat ca Vizitator, Redirecționare...",
|
||||
"error-guest-access": "Acces Vizitator Interzis"
|
||||
},
|
||||
"app-info": {
|
||||
"title": "Informații Aplicație",
|
||||
"error-log": "Jurnal Erori",
|
||||
"no-errors": "Nicio eroare recentă detectată",
|
||||
"help-support": "Ajutor & Suport",
|
||||
"help-support-description" : "Pentru suport în utilizarea sau configurarea Dashy, consultați",
|
||||
"help-support-discussions": "Discuțiile",
|
||||
"support-dashy": "Suport pentru Dashy",
|
||||
"support-dashy-description": "Pentru moduri în care poți contribui, verificați",
|
||||
"support-dashy-link": "Pagina de Contribuții",
|
||||
"report-bug": "Raportează o Eroare",
|
||||
"report-bug-description": "Dacă crezi că ai găsit o eroare, atunci te rog",
|
||||
"report-bug-link": "deschide o Problemă",
|
||||
"more-info": "Mai Multe Informații",
|
||||
"source": "Sursă",
|
||||
"documentation": "Documentație",
|
||||
"privacy-and-security": "Confidențialitate & Securitate",
|
||||
"privacy-and-security-l1": "Pentru o descompunere a modului în care datele tale sunt gestionate de Dashy, consultați",
|
||||
"privacy-and-security-privacy-policy": "Politica de Confidențialitate",
|
||||
"privacy-and-security-advice": "Pentru sfaturi în securizarea tabloului tău de bord, poți consulta",
|
||||
"privacy-and-security-advice-link": "Documentele de Management",
|
||||
"privacy-and-security-security-issue": "Dacă ați descoperit o potențială problemă de securitate, raportați-o urmând",
|
||||
"privacy-and-security-security-policy": "Politica de Securitate",
|
||||
"license": "Licență",
|
||||
"license-under": "Licențiat sub",
|
||||
"licence-third-party": "Pentru licențele modulelor terțe părți, vă rugăm să consultați",
|
||||
"licence-third-party-link": "Legal",
|
||||
"list-contributors": "Pentru lista completă a contribuitorilor și mulțumiri, vedeți",
|
||||
"list-contributors-link": "Credite",
|
||||
"version": "Versiune"
|
||||
},
|
||||
"config": {
|
||||
"main-tab": "Meniu Principal",
|
||||
"view-config-tab": "Vizualizare Configurație",
|
||||
"edit-config-tab": "Editare Configurație",
|
||||
"custom-css-tab": "Stiluri Personalizate",
|
||||
"heading": "Opțiuni de Configurare",
|
||||
"download-config-button": "Vizualizați / Exportați Configurația",
|
||||
"edit-config-button": "Editare Configurație",
|
||||
"edit-css-button": "Editare CSS Personalizat",
|
||||
"cloud-sync-button": "Activează Sincronizarea în Cloud",
|
||||
"edit-cloud-sync-button": "Editare Sincronizare în Cloud",
|
||||
"rebuild-app-button": "Reconstruire Aplicație",
|
||||
"change-language-button": "Schimbă Limba Aplicației",
|
||||
"reset-settings-button": "Resetează Setările Locale",
|
||||
"disabled-note": "Unele caracteristici de configurare au fost dezactivate de administratorul tău",
|
||||
"small-screen-note": "Utilizați un ecran foarte mic, și unele ecrane din acest meniu s-ar putea să nu fie optimale",
|
||||
"app-info-button": "Informații Aplicație",
|
||||
"backup-note": "Este recomandat să faceți o copie de siguranță a configurației înainte de a face modificări.",
|
||||
"reset-config-msg-l1": "Aceasta va elimina toate setările utilizatorilor din stocarea locală, dar nu va afecta fișierul 'conf.yml'.",
|
||||
"reset-config-msg-l2": "Ar trebui să faceți mai întâi o copie de siguranță a oricăror modificări pe care le-ați făcut local, dacă doriți să le utilizați în viitor.",
|
||||
"reset-config-msg-l3": "Sunteți sigur că doriți să continuați?",
|
||||
"data-cleared-msg": "Datele au fost șterse cu succes",
|
||||
"actions-label": "Acțiuni",
|
||||
"copy-config-label": "Copiază Configurația",
|
||||
"data-copied-msg": "Configurația a fost copiată în clipboard",
|
||||
"reset-config-label": "Resetează Configurația",
|
||||
"css-save-btn": "Salvează Modificările",
|
||||
"css-note-label": "Notă",
|
||||
"css-note-l1": "Va trebui să reîmprospătați pagina pentru ca modificările să aibă efect.",
|
||||
"css-note-l2": "Suprascrierile de stiluri sunt stocate doar local, deci este recomandat să faceți o copie a CSS-ului dvs.",
|
||||
"css-note-l3": "Pentru a elimina toate stilurile personalizate, ștergeți conținutul și apăsați 'Salvează Modificările'",
|
||||
"custom-css": {
|
||||
"title": "CSS Personalizat",
|
||||
"base-theme": "Tema de Bază"
|
||||
}
|
||||
},
|
||||
"alternate-views": {
|
||||
"alternate-view-heading": "Schimbă Vederea",
|
||||
"default": "Implicit",
|
||||
"workspace": "Spațiu de Lucru",
|
||||
"minimal": "Minimal"
|
||||
},
|
||||
"settings": {
|
||||
"theme-label": "Temă",
|
||||
"layout-label": "Aspect",
|
||||
"layout-auto": "Automat",
|
||||
"layout-horizontal": "Orizontal",
|
||||
"layout-vertical": "Vertical",
|
||||
"item-size-label": "Dimensiune Element",
|
||||
"item-size-small": "Mic",
|
||||
"item-size-medium": "Mediu",
|
||||
"item-size-large": "Mare",
|
||||
"config-launcher-label": "Config",
|
||||
"config-launcher-tooltip": "Actualizează Configurația",
|
||||
"sign-out-tooltip": "Deconectare",
|
||||
"sign-in-tooltip": "Conectare",
|
||||
"sign-in-welcome": "Bună {username}!",
|
||||
"hide": "Ascunde",
|
||||
"open": "Deschide"
|
||||
},
|
||||
"updates": {
|
||||
"app-version-note": "Versiune Dashy",
|
||||
"up-to-date": "Actualizat",
|
||||
"out-of-date": "Actualizare Disponibilă",
|
||||
"unsupported-version-l1": "Utilizați o versiune nesuportată de Dashy",
|
||||
"unsupported-version-l2": "Pentru cea mai bună experiență și patch-uri de securitate recente, vă rugăm să actualizați la"
|
||||
},
|
||||
"language-switcher": {
|
||||
"title": "Schimbă Limba Aplicației",
|
||||
"dropdown-label": "Selectați o Limbă",
|
||||
"save-button": "Salvează",
|
||||
"success-msg": "Limba Actualizată la"
|
||||
},
|
||||
"theme-maker": {
|
||||
"title": "Configurator de Temă",
|
||||
"export-button": "Exportă Variabile Personalizate",
|
||||
"reset-button": "Resetează Stilurile pentru",
|
||||
"show-all-button": "Arată Toate Variabilele",
|
||||
"change-fonts-button": "Schimbă Fonturile",
|
||||
"save-button": "Salvează",
|
||||
"cancel-button": "Anulează",
|
||||
"saved-toast": "{theme} Actualizat cu Succes",
|
||||
"copied-toast": "Datele temei pentru {theme} au fost copiate în clipboard",
|
||||
"reset-toast": "Culorile Personalizate pentru {theme} au fost Eliminate"
|
||||
},
|
||||
"config-editor": {
|
||||
"save-location-label": "Locația de Salvare",
|
||||
"location-local-label": "Aplică Local",
|
||||
"location-disk-label": "Scrie Modificările în Fișierul de Configurație",
|
||||
"save-button": "Salvează Modificările",
|
||||
"preview-button": "Previzualizează Modificările",
|
||||
"valid-label": "Configurația este Valabilă",
|
||||
"status-success-msg": "Sarcină Completată",
|
||||
"status-fail-msg": "Sarcină Eșuată",
|
||||
"success-msg-disk": "Fișierul de configurație a fost scris pe disc cu succes",
|
||||
"success-msg-local": "Modificările locale au fost salvate cu succes",
|
||||
"success-note-l1": "Aplicația ar trebui să se reconstruiască automat.",
|
||||
"success-note-l2": "Aceasta poate dura până la un minut.",
|
||||
"success-note-l3": "Va trebui să reîmprospătați pagina pentru ca modificările să aibă efect.",
|
||||
"error-msg-save-mode": "Vă rugăm să selectați un Mod de Salvare: Local sau Fișier",
|
||||
"error-msg-cannot-save": "A apărut o eroare la salvarea configurației",
|
||||
"error-msg-bad-json": "Eroare în JSON, posibil malformat",
|
||||
"warning-msg-validation": "Avertisment de Validare",
|
||||
"not-admin-note": "Nu puteți scrie modificările pe disc deoarece nu sunteți conectat ca administrator"
|
||||
},
|
||||
"app-rebuild": {
|
||||
"title": "Reconstruire Aplicație",
|
||||
"rebuild-note-l1": "O reconstruire este necesară pentru ca modificările scrise în fișierul conf.yml să aibă efect.",
|
||||
"rebuild-note-l2": "Aceasta ar trebui să se întâmple automat, dar dacă nu s-a întâmplat, o puteți declanșa manual aici.",
|
||||
"rebuild-note-l3": "Aceasta nu este necesară pentru modificările stocate local.",
|
||||
"rebuild-button": "Începeți Reconstruirea",
|
||||
"rebuilding-status-1": "Se reconstruiește...",
|
||||
"rebuilding-status-2": "Aceasta poate dura câteva minute",
|
||||
"error-permission": "Nu aveți permisiunea de a declanșa această acțiune",
|
||||
"success-msg": "Reconstruirea a fost completată cu succes",
|
||||
"fail-msg": "Operațiunea de reconstruire a eșuat",
|
||||
"reload-note": "Este necesară reîncărcarea paginii pentru ca modificările să aibă efect",
|
||||
"reload-button": "Reîncarcă Pagina"
|
||||
},
|
||||
"cloud-sync": {
|
||||
"title": "Backup și Restaurare în Cloud",
|
||||
"intro-l1": "Backup-ul și restaurarea în cloud este o caracteristică opțională, care vă permite să încărcați configurația pe internet și apoi să o restaurați pe orice alt dispozitiv sau instanță de Dashy.",
|
||||
"intro-l2": "Toate datele sunt criptate end-to-end cu AES, folosind parola dumneavoastră ca cheie.",
|
||||
"intro-l3": "Pentru mai multe informații, vă rugăm să consultați",
|
||||
"intro-docs": "documentația",
|
||||
"backup-title-setup": "Creați un Backup",
|
||||
"backup-title-update": "Actualizați Backup-ul",
|
||||
"password-label-setup": "Alegeți o Parolă",
|
||||
"password-label-update": "Introduceți Parola",
|
||||
"backup-button-setup": "Backup",
|
||||
"backup-button-update": "Actualizați Backup-ul",
|
||||
"backup-id-label": "ID-ul Dvs. de Backup",
|
||||
"backup-id-note": "Acesta este utilizat pentru a restaura din backup-uri mai târziu. Deci păstrați-l, împreună cu parola dvs., într-un loc sigur.",
|
||||
"restore-title": "Restaurare Backup",
|
||||
"restore-id-label": "ID de Restaurare",
|
||||
"restore-password-label": "Parola",
|
||||
"restore-button": "Restaurare",
|
||||
"backup-missing-password": "Lipsește Parola",
|
||||
"backup-error-unknown": "Imposibil de procesat solicitarea",
|
||||
"backup-error-password": "Parolă incorectă. Vă rugăm să introduceți parola actuală.",
|
||||
"backup-success-msg": "Finalizat cu Succes",
|
||||
"restore-success-msg": "Configurația a fost Restaurată cu Succes"
|
||||
},
|
||||
"menu": {
|
||||
"open-section-title": "Deschide În",
|
||||
"sametab": "Tab Curent",
|
||||
"newtab": "Tab Nou",
|
||||
"modal": "Modal Pop-Up",
|
||||
"workspace": "Vizualizare Spațiu de Lucru",
|
||||
"options-section-title": "Opțiuni",
|
||||
"edit-item": "Editare",
|
||||
"move-item": "Copiază sau Mută",
|
||||
"remove-item": "Șterge"
|
||||
},
|
||||
"context-menus": {
|
||||
"item": {
|
||||
"open-section-title": "Deschide În",
|
||||
"sametab": "Tab Curent",
|
||||
"newtab": "Tab Nou",
|
||||
"modal": "Modal Pop-Up",
|
||||
"workspace": "Vizualizare Spațiu de Lucru",
|
||||
"clipboard": "Copiază în Clipboard",
|
||||
"options-section-title": "Opțiuni",
|
||||
"edit-item": "Editare",
|
||||
"move-item": "Copiază sau Mută",
|
||||
"remove-item": "Șterge",
|
||||
"copied-toast": "URL-ul a fost copiat în clipboard"
|
||||
},
|
||||
"section": {
|
||||
"open-section": "Deschide Secțiunea",
|
||||
"edit-section": "Editare",
|
||||
"expand-collapse": "Extinde / Colapsează",
|
||||
"move-section": "Mută La",
|
||||
"remove-section": "Șterge"
|
||||
}
|
||||
},
|
||||
"footer": {
|
||||
"dev-by": "Dezvoltat de",
|
||||
"licensed-under": "Licențiat sub",
|
||||
"get-the": "Obțineți",
|
||||
"source-code": "Codul Sursă"
|
||||
},
|
||||
"interactive-editor": {
|
||||
"menu": {
|
||||
"start-editing-tooltip": "Intră în Editorul Interactiv",
|
||||
"edit-site-data-subheading": "Editează Datele Site-ului",
|
||||
"edit-page-info-btn": "Editează Informațiile Paginii",
|
||||
"edit-page-info-tooltip": "Titlul aplicației, descriere, link-uri de navigare, textul din footer, etc.",
|
||||
"edit-app-config-btn": "Editează Configurația Aplicației",
|
||||
"edit-app-config-tooltip": "Toate celelalte opțiuni de configurare a aplicației",
|
||||
"edit-pages-btn": "Editează Paginile",
|
||||
"edit-pages-tooltip": "Adaugă sau elimină vizualizări suplimentare",
|
||||
"config-save-methods-subheading": "Opțiuni de Salvare a Configurației",
|
||||
"save-locally-btn": "Salvează Local",
|
||||
"save-locally-tooltip": "Salvează configurația local, în stocarea browserului. Aceasta nu va afecta fișierul tău de configurare, dar schimbările vor fi salvate doar pe acest dispozitiv",
|
||||
"save-disk-btn": "Salvează pe Disc",
|
||||
"save-disk-tooltip": "Salvează configurația în fișierul conf.yml de pe disc. Aceasta va face un backup și apoi va suprascrie configurația existentă",
|
||||
"export-config-btn": "Exportă Configurația",
|
||||
"export-config-tooltip": "Vizualizează și exportă noua configurație, fie într-un fișier, fie în clipboard",
|
||||
"cloud-backup-btn": "Backup în Cloud",
|
||||
"cloud-backup-tooltip": "Salvează un backup criptat al configurației în cloud",
|
||||
"edit-raw-config-btn": "Editează Configurația Brută",
|
||||
"edit-raw-config-tooltip": "Vizualizează și modifică configurația brută prin editorul JSON",
|
||||
"cancel-changes-btn": "Anulează Modificările",
|
||||
"cancel-changes-tooltip": "Resetează modificările curente și ieși din Modul de Editare. Aceasta nu va afecta configurația salvată",
|
||||
"edit-mode-name": "Mod de Editare",
|
||||
"edit-mode-subtitle": "Ești în Modul de Editare",
|
||||
"edit-mode-description": "Aceasta înseamnă că poți face modificări la configurația ta și să previzualizezi rezultatele, dar până nu salvezi, niciuna dintre modificările tale nu va fi păstrată.",
|
||||
"save-stage-btn": "Salvează",
|
||||
"cancel-stage-btn": "Anulează",
|
||||
"save-locally-warning": "Dacă vei continua, modificările vor fi salvate doar în browserul tău. Ar trebui să exporți o copie a configurației tale pentru utilizare pe alte mașini. Dorești să continui?"
|
||||
},
|
||||
"edit-item": {
|
||||
"missing-title-err": "Este necesar un titlu pentru element"
|
||||
},
|
||||
"edit-section": {
|
||||
"edit-section-title": "Editează Secțiunea",
|
||||
"add-section-title": "Adaugă o Secțiune Nouă",
|
||||
"edit-tooltip": "Clic pentru a edita, sau clic dreapta pentru mai multe opțiuni",
|
||||
"remove-confirm": "Ești sigur că vrei să elimini această secțiune? Această acțiune poate fi anulată mai târziu."
|
||||
},
|
||||
"edit-app-config": {
|
||||
"warning-msg-title": "Procedează cu Atenție",
|
||||
"warning-msg-l1": "Următoarele opțiuni sunt pentru configurarea avansată a aplicației.",
|
||||
"warning-msg-l2": "Dacă nu ești sigur în legătură cu oricare dintre câmpuri, te rog să consulți",
|
||||
"warning-msg-docs": "documentația",
|
||||
"warning-msg-l3": "pentru a evita consecințele nedorite."
|
||||
},
|
||||
"export": {
|
||||
"export-title": "Exportă Configurația",
|
||||
"copy-clipboard-btn": "Copiază în Clipboard",
|
||||
"copy-clipboard-tooltip": "Copiază toată configurația aplicației în clipboard-ul sistemului, în format YAML",
|
||||
"download-file-btn": "Descarcă ca Fișier",
|
||||
"download-file-tooltip": "Descarcă toată configurația aplicației pe dispozitivul tău, într-un fișier YAML",
|
||||
"view-title": "Vizualizează Configurația"
|
||||
}
|
||||
},
|
||||
"widgets": {
|
||||
"general": {
|
||||
"loading": "Se încarcă...",
|
||||
"show-more": "Extinde Detaliile",
|
||||
"cpu-details": "Detalii CPU",
|
||||
"mem-details": "Detalii Memorie",
|
||||
"show-less": "Arată Mai Puțin",
|
||||
"open-link": "Continuă Citirea"
|
||||
},
|
||||
"pi-hole": {
|
||||
"status-heading": "Stare"
|
||||
},
|
||||
"stat-ping": {
|
||||
"up": "Online",
|
||||
"down": "Offline"
|
||||
},
|
||||
"net-data": {
|
||||
"cpu-chart-title": "Istoric CPU",
|
||||
"mem-chart-title": "Utilizare Memorie",
|
||||
"mem-breakdown-title": "Detalii Memorie",
|
||||
"load-chart-title": "Încărcare Sistem"
|
||||
},
|
||||
"glances": {
|
||||
"disk-space-free": "Liber",
|
||||
"disk-space-used": "Utilizat",
|
||||
"disk-mount-point": "Punct de Montare",
|
||||
"disk-file-system": "Sistem de Fișiere",
|
||||
"disk-io-read": "Citire",
|
||||
"disk-io-write": "Scriere",
|
||||
"system-load-desc": "Numărul de procese așteptând în coada de execuție, mediat pe toate nucleele"
|
||||
},
|
||||
"system-info": {
|
||||
"uptime": "Timp de Funcționare"
|
||||
},
|
||||
"flight-data": {
|
||||
"arrivals": "Sosiri",
|
||||
"departures": "Plecări"
|
||||
},
|
||||
"tfl-status": {
|
||||
"good-service-all": "Servicii Bune pe Toate Liniile",
|
||||
"good-service-rest": "Servicii Bune pe Celelalte Linii"
|
||||
},
|
||||
"synology-download": {
|
||||
"download": "Descărcare",
|
||||
"upload": "Încărcare",
|
||||
"downloaded": "Descărcat",
|
||||
"uploaded": "Încărcat",
|
||||
"remaining": "Rămas",
|
||||
"up": "Sus",
|
||||
"down": "Jos"
|
||||
},
|
||||
"gluetun-status": {
|
||||
"vpn-ip": "IP VPN",
|
||||
"country": "Țară",
|
||||
"region": "Regiune",
|
||||
"city": "Oraș",
|
||||
"post-code": "Cod Poștal",
|
||||
"location": "Locație",
|
||||
"timezone": "Fus Orar",
|
||||
"organization": "Organizație"
|
||||
},
|
||||
"nextcloud": {
|
||||
"active": "activ",
|
||||
"and": "și",
|
||||
"applications": "aplicații",
|
||||
"available": "disponibil",
|
||||
"away": "Plecat",
|
||||
"cache-full": "CACHE PLIN",
|
||||
"chat-room": "camera de chat",
|
||||
"delete-all": "Șterge tot",
|
||||
"delete-notification": "Șterge notificarea",
|
||||
"disabled": "dezactivat",
|
||||
"disk-quota": "Cotă de Disc",
|
||||
"disk-space": "Spațiu pe Disc",
|
||||
"dnd": "Nu Deranja",
|
||||
"email": "email",
|
||||
"enabled": "activat",
|
||||
"federated-shares-ucfirst": "Partajări Federate",
|
||||
"federated-shares": "partajări federate",
|
||||
"files": "fișier{plural}",
|
||||
"free": "liber",
|
||||
"groups": "grupuri",
|
||||
"hit-rate": "rata de accesare",
|
||||
"hits": "accesări",
|
||||
"home": "acasă",
|
||||
"in": "în",
|
||||
"keys": "chei",
|
||||
"last-24-hours": "ultimele 24 de ore",
|
||||
"last-5-minutes": "în ultimele 5 minute",
|
||||
"last-hour": "în ultima oră",
|
||||
"last-login": "Ultima autentificare",
|
||||
"last-restart": "Ultimul restart",
|
||||
"load-averages": "Mediile de Încărcare pe toate nucleele CPU",
|
||||
"local-shares": "Partajări Locale",
|
||||
"local": "local",
|
||||
"max-keys": "chei maxime",
|
||||
"memory-used": "memorie utilizată",
|
||||
"memory-utilisation": "utilizarea memoriei",
|
||||
"memory": "memorie",
|
||||
"misses": "rateuri",
|
||||
"no-notifications": "Fără notificări",
|
||||
"no-pending-updates": "fără actualizări în așteptare",
|
||||
"nothing-to-show": "Nimic de afișat aici în acest moment",
|
||||
"of-which": "din care",
|
||||
"of": "din",
|
||||
"offline": "Deconectat",
|
||||
"online": "Conectat",
|
||||
"other": "alt",
|
||||
"overall": "În total",
|
||||
"private-link": "link privat",
|
||||
"public-link": "link public",
|
||||
"quota-enabled": "Cota de Disc este {not}activată pentru acest utilizator",
|
||||
"received": "primit",
|
||||
"scripts": "scripturi",
|
||||
"sent": "trimis",
|
||||
"started": "Început",
|
||||
"storages-by-type": "Stocări pe tip",
|
||||
"storages": "stocare{plural}",
|
||||
"strings-use": "utilizare șiruri",
|
||||
"tasks": "Sarcini",
|
||||
"total-files": "total fișiere",
|
||||
"total-users": "total utilizatori",
|
||||
"total": "total",
|
||||
"until": "Până la",
|
||||
"updates-available-for": "Actualizări disponibile pentru",
|
||||
"updates-available": "actualizare{plural} disponibilă",
|
||||
"used": "utilizat",
|
||||
"user": "utilizator",
|
||||
"using": "utilizând",
|
||||
"version": "versiune",
|
||||
"wasted": "pierdut"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,8 +63,8 @@
|
||||
"privacy-and-security": "Конфіденційність та безпека",
|
||||
"privacy-and-security-l1": "Для детальної інформації про те, як Dashy керує вашими даними, див.",
|
||||
"privacy-and-security-privacy-policy": "Політика конфіденційності",
|
||||
"app-info.privacy-and-security-advice": "Щоб отримати поради щодо захисту вашої інформаційної панелі, ви можете звернутися до розділу",
|
||||
"app-info.privacy-and-security-advice-link": "Документи керування",
|
||||
"privacy-and-security-advice": "Щоб отримати поради щодо захисту вашої інформаційної панелі, ви можете звернутися до розділу",
|
||||
"privacy-and-security-advice-link": "Документи керування",
|
||||
"privacy-and-security-security-issue": "Якщо ви виявили потенційну проблему з безпекою, повідомте про це до розділу",
|
||||
"privacy-and-security-security-policy": "Політика безпеки",
|
||||
"license": "Ліцензія",
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"error-missing-password": "密码空缺",
|
||||
"error-incorrect-username": "用户不存在",
|
||||
"error-incorrect-password": "密码不正确",
|
||||
"success-message": "登陆成功。。。",
|
||||
"success-message": "登陆成功。",
|
||||
"logout-message": "注销",
|
||||
"already-logged-in-title": "已经成功登陆",
|
||||
"already-logged-in-text": "你的登陆身份",
|
||||
@@ -37,11 +37,11 @@
|
||||
"log-out-button": "注销",
|
||||
"proceed-guest-button": "以游客身份前往",
|
||||
"guest-intro-1": "该实例已启用访客访问.",
|
||||
"guest-intro-2": "访客只有访问权限,无法保存变更",
|
||||
"guest-intro-2": "访客只有访问权限,无法保存变更",
|
||||
"error": "错误",
|
||||
"error-no-user-configured": "没有启用验证,或者未配置用户",
|
||||
"error-no-user-configured": "没有启用验证,或者未配置用户",
|
||||
"error-go-home-button": "Go Home",
|
||||
"logged-in-guest": "以访客身份登陆,正在跳转...",
|
||||
"logged-in-guest": "以访客身份登陆,正在跳转...",
|
||||
"error-guest-access": "不允许访客访问"
|
||||
},
|
||||
"app-info": {
|
||||
@@ -63,8 +63,8 @@
|
||||
"privacy-and-security": "隐私与安全",
|
||||
"privacy-and-security-l1": "关于Dashy如何管理您的数据的详细信息,请参阅",
|
||||
"privacy-and-security-privacy-policy": "隐私政策",
|
||||
"app-info.privacy-and-security-advice": "如果您需要保护您的仪表盘,请参考",
|
||||
"app-info.privacy-and-security-advice-link": "管理文档",
|
||||
"privacy-and-security-advice": "如果您需要保护您的仪表盘,请参考",
|
||||
"privacy-and-security-advice-link": "管理文档",
|
||||
"privacy-and-security-security-issue": "如果您发现潜在的安全问题,请遵循我们的",
|
||||
"privacy-and-security-security-policy": "安全政策",
|
||||
"license": "许可证",
|
||||
@@ -78,22 +78,22 @@
|
||||
"config": {
|
||||
"main-tab": "主菜单",
|
||||
"view-config-tab": "视图设置",
|
||||
"edit-config-tab": "编辑设置",
|
||||
"edit-config-tab": "修改设置",
|
||||
"custom-css-tab": "自定义样式",
|
||||
"heading": "设置选项",
|
||||
"download-config-button": "下载配置",
|
||||
"edit-config-button": "编辑设置",
|
||||
"edit-css-button": "编辑自定义 CSS",
|
||||
"cloud-sync-button": "启用云端同步",
|
||||
"edit-cloud-sync-button": "编辑云端同步",
|
||||
"edit-config-button": "修改设置",
|
||||
"edit-css-button": "自定义CSS",
|
||||
"cloud-sync-button": "云端同步",
|
||||
"edit-cloud-sync-button": "修改云端同步",
|
||||
"rebuild-app-button": "重建应用",
|
||||
"change-language-button": "更改语言",
|
||||
"reset-settings-button": "恢复本地设置",
|
||||
"reset-settings-button": "恢复默认设置",
|
||||
"disabled-note": "您的管理员已禁用某些配置功能",
|
||||
"small-screen-note": "您正在使用非常小的屏幕,某些菜单屏幕可能不够优化",
|
||||
"app-info-button": "应用详情",
|
||||
"backup-note": "建议在进行更改之前备份你的配置。",
|
||||
"reset-config-msg-l1": "这将从本地存储中删除所有用户设置,但不会影响‘conf.yml’文件。",
|
||||
"reset-config-msg-l1": "这将从本地存储中删除所有用户设置,但不会影响 conf.yml 文件。",
|
||||
"reset-config-msg-l2": "如果想在以后使用它们,应该首先备份你所做的任何更改。",
|
||||
"reset-config-msg-l3": "确定执行吗?",
|
||||
"data-cleared-msg": "成功清空数据",
|
||||
@@ -103,9 +103,9 @@
|
||||
"reset-config-label": "重置设置",
|
||||
"css-save-btn": "保存更改",
|
||||
"css-note-label": "注意",
|
||||
"css-note-l1": "你需要刷新页面才能使更改生效。",
|
||||
"css-note-l2": "样式覆盖仅存储在本地,因此建议复制你的 CSS。",
|
||||
"css-note-l3": "要删除所有自定义样式,请删除内容并点击保存更改",
|
||||
"css-note-l1": "需刷新页面使其生效。",
|
||||
"css-note-l2": "自定义样式仅在本地有效,设置储存在当前浏览器,建议做好备份。",
|
||||
"css-note-l3": "如需删除自定义样式,清空上面内容并点击保存。",
|
||||
"custom-css": {
|
||||
"title": "自定义 CSS",
|
||||
"base-theme": "基础主题"
|
||||
@@ -132,8 +132,8 @@
|
||||
"sign-out-tooltip": "注销",
|
||||
"sign-in-tooltip": "登陆",
|
||||
"sign-in-welcome": "你好 {username}!",
|
||||
"hide": "Hide",
|
||||
"open": "Open"
|
||||
"hide": "隐藏",
|
||||
"open": "打开"
|
||||
},
|
||||
"updates": {
|
||||
"app-version-note": "Dashy 版本",
|
||||
@@ -195,9 +195,10 @@
|
||||
"reload-button": "刷新页面"
|
||||
},
|
||||
"cloud-sync": {
|
||||
"title": "云备份 & 云恢复",
|
||||
"title": "云备份&恢复",
|
||||
"intro-l1": "云备份和云恢复是一项试验性功能,你将配置上传到网络,然后在其他设备或 Dashy 实例上恢复。",
|
||||
"intro-l2": "所有数据都使用AES端对端加密,使用你的密码作为密钥。",
|
||||
"intro-docs": "文档",
|
||||
"intro-l3": "有关更多信息,请参阅",
|
||||
"backup-title-setup": "创建备份",
|
||||
"backup-title-update": "更新备份",
|
||||
@@ -268,11 +269,11 @@
|
||||
"edit-pages-tooltip": "添加或删除其他的视图",
|
||||
"config-save-methods-subheading": "配置保存选项",
|
||||
"save-locally-btn": "暂存本地",
|
||||
"save-locally-tooltip": "将设置保存在本地浏览器上. 这不会影响配置文件,但更改只会保留在当前设备上.",
|
||||
"save-locally-tooltip": "将设置保存在浏览器上。这不会影响配置文件,仅作用于当前的浏览器。",
|
||||
"save-disk-btn": "保存",
|
||||
"save-disk-tooltip": "将设置保存到服务端的conf.yml文件.它会备份之前的配置文件.",
|
||||
"save-disk-tooltip": "将设置保存到服务端的conf.yml文件,它会备份之前的配置文件.",
|
||||
"export-config-btn": "导出配置",
|
||||
"export-config-tooltip": "查看并导出新的配置 到 文件 或 剪贴板",
|
||||
"export-config-tooltip": "查看并导出新的配置到 文件 或 剪贴板",
|
||||
"cloud-backup-btn": "备份到云端",
|
||||
"cloud-backup-tooltip": "以加密的方式保存到云端",
|
||||
"edit-raw-config-btn": "编辑原始配置",
|
||||
@@ -284,10 +285,10 @@
|
||||
"edit-mode-description": "你可以对配置进行修改并预览,在保存之前,你的任何更改都不会被保留。",
|
||||
"save-stage-btn": "保存",
|
||||
"cancel-stage-btn": "取消",
|
||||
"save-locally-warning": "如果你继续,更改将仅保存在你的浏览器中。 你应该导出配置的副本以在其他机器上使用。 你想继续吗?"
|
||||
"save-locally-warning": "配置将保存到你当前的浏览器上。你也可以导出配置到其他设备上使用。是否继续?"
|
||||
},
|
||||
"edit-item": {
|
||||
"missing-title-err": "项目标题是必需的"
|
||||
"missing-title-err": "标题是必需的"
|
||||
},
|
||||
"edit-section": {
|
||||
"edit-section-title": "编辑 Section",
|
||||
@@ -314,7 +315,9 @@
|
||||
"widgets": {
|
||||
"general": {
|
||||
"loading": "加载中...",
|
||||
"show-more": "展开详情",
|
||||
"show-more": "显示更多",
|
||||
"cpu-details": "CPU 详情",
|
||||
"mem-details": "内存 详情",
|
||||
"show-less": "显示更少信息",
|
||||
"open-link": "继续读取"
|
||||
},
|
||||
|
||||
@@ -63,8 +63,8 @@
|
||||
"privacy-and-security": "隱私權和安全性",
|
||||
"privacy-and-security-l1": "若要了解 Dashy 是如何管理您的資料,請參閱",
|
||||
"privacy-and-security-privacy-policy": "隱私權政策",
|
||||
"app-info.privacy-and-security-advice": "關於提升儀錶板安全性的建議,請參閱",
|
||||
"app-info.privacy-and-security-advice-link": "管理文件",
|
||||
"privacy-and-security-advice": "關於提升儀錶板安全性的建議,請參閱",
|
||||
"privacy-and-security-advice-link": "管理文件",
|
||||
"privacy-and-security-security-issue": "若您找到潛在的安全問題,請回報,並遵照我們的",
|
||||
"privacy-and-security-security-policy": "安全政策",
|
||||
"license": "授權條款",
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<!-- License -->
|
||||
<h3>{{ $t('app-info.license') }}</h3>
|
||||
{{ $t('app-info.license-under') }} <a href="https://github.com/Lissy93/dashy/blob/master/LICENSE">MIT X11</a>.
|
||||
Copyright <a href="https://aliciasykes.com">Alicia Sykes</a> © 2021.<br>
|
||||
Copyright <a href="https://aliciasykes.com">Alicia Sykes</a> © {{new Date().getFullYear()}}.<br>
|
||||
{{ $t('app-info.licence-third-party') }} <a href="https://github.com/Lissy93/dashy/blob/master/.github/LEGAL.md">{{ $t('app-info.licence-third-party-link') }}</a>.<br>
|
||||
{{ $t('app-info.list-contributors') }} <a href="https://github.com/Lissy93/dashy/blob/master/docs/credits.md">{{ $t('app-info.list-contributors-link') }}</a>.
|
||||
<!-- App Version -->
|
||||
|
||||
@@ -155,12 +155,23 @@ export default {
|
||||
},
|
||||
/* When restored data is revieved, then save to local storage, and apply it in state */
|
||||
applyRestoredData(config, backupId) {
|
||||
// Store restored data in local storage
|
||||
localStorage.setItem(localStorageKeys.CONF_SECTIONS, JSON.stringify(config.sections));
|
||||
localStorage.setItem(localStorageKeys.APP_CONFIG, JSON.stringify(config.appConfig));
|
||||
localStorage.setItem(localStorageKeys.PAGE_INFO, JSON.stringify(config.pageInfo));
|
||||
if (config.appConfig.theme) {
|
||||
localStorage.setItem(localStorageKeys.THEME, config.appConfig.theme);
|
||||
const isSubPage = !!this.$store.state.currentConfigInfo.confId;
|
||||
if (isSubPage) { // Apply to sub-page only
|
||||
const subConfigId = this.$store.state.currentConfigInfo.confId;
|
||||
const sectionStorageKey = `${localStorageKeys.CONF_SECTIONS}-${subConfigId}`;
|
||||
const pageInfoStorageKey = `${localStorageKeys.PAGE_INFO}-${subConfigId}`;
|
||||
const themeStoreKey = `${localStorageKeys.THEME}-${subConfigId}`;
|
||||
localStorage.setItem(sectionStorageKey, JSON.stringify(config.sections));
|
||||
localStorage.setItem(pageInfoStorageKey, JSON.stringify(config.pageInfo));
|
||||
localStorage.setItem(themeStoreKey, config.appConfig.theme);
|
||||
} else { // Apply to main config
|
||||
localStorage.setItem(localStorageKeys.CONF_SECTIONS, JSON.stringify(config.sections));
|
||||
localStorage.setItem(localStorageKeys.APP_CONFIG, JSON.stringify(config.appConfig));
|
||||
localStorage.setItem(localStorageKeys.PAGE_INFO, JSON.stringify(config.pageInfo));
|
||||
localStorage.setItem(localStorageKeys.CONF_PAGES, JSON.stringify(config.pages || []));
|
||||
if (config.appConfig.theme) {
|
||||
localStorage.setItem(localStorageKeys.THEME, config.appConfig.theme);
|
||||
}
|
||||
}
|
||||
// Save hashed token in local storage
|
||||
this.setBackupIdLocally(backupId, this.restorePassword);
|
||||
|
||||
@@ -47,16 +47,17 @@
|
||||
</Button>
|
||||
<!-- Display app version and language -->
|
||||
<p class="language">{{ getLanguage() }}</p>
|
||||
<p v-if="$store.state.currentConfigInfo" class="config-location">
|
||||
Using Config From<br>
|
||||
{{ $store.state.currentConfigInfo.confPath }}
|
||||
<!-- Display location of config file -->
|
||||
<p class="config-location">
|
||||
Using config from
|
||||
<a :href="configPath">{{ configPath }}</a>
|
||||
</p>
|
||||
<AppVersion />
|
||||
</div>
|
||||
<!-- Display note if Config disabled, or if on mobile -->
|
||||
<p v-if="!enableConfig" class="config-disabled-note">{{ $t('config.disabled-note') }}</p>
|
||||
<p class="small-screen-note" style="display: none;">{{ $t('config.small-screen-note') }}</p>
|
||||
<div class="config-note">
|
||||
<div class="config-note" @click="openExportConfigModal">
|
||||
<span>{{ $t('config.backup-note') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,6 +117,11 @@ export default {
|
||||
enableConfig() {
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
configPath() {
|
||||
return this.$store.state.currentConfigInfo?.confPath
|
||||
|| process.env.VUE_APP_CONFIG_PATH
|
||||
|| '/conf.yml';
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Button,
|
||||
@@ -248,8 +254,12 @@ a.hyperlink-wrapper {
|
||||
p.app-version, p.language, p.config-location {
|
||||
margin: 0.5rem auto;
|
||||
font-size: 1rem;
|
||||
color: var(--transparent-white-50);
|
||||
color: var(--config-settings-color);
|
||||
cursor: default;
|
||||
opacity: var(--dimming-factor);
|
||||
a {
|
||||
color: var(--config-settings-color);
|
||||
}
|
||||
}
|
||||
|
||||
div.code-container {
|
||||
|
||||
@@ -115,7 +115,10 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.jsonData = this.config;
|
||||
const jsonData = { ...this.config };
|
||||
jsonData.sections = (jsonData.sections || []).map(({ filteredItems, ...section }) => section);
|
||||
if (!jsonData.pageInfo) jsonData.pageInfo = { title: 'Dashy' };
|
||||
this.jsonData = jsonData;
|
||||
if (!this.allowWriteToDisk) this.saveMode = 'local';
|
||||
},
|
||||
methods: {
|
||||
@@ -141,7 +144,11 @@ export default {
|
||||
this.$modal.hide(modalNames.CONF_EDITOR);
|
||||
},
|
||||
writeToDisk() {
|
||||
this.writeConfigToDisk(this.config);
|
||||
const newData = this.jsonData;
|
||||
this.writeConfigToDisk(newData);
|
||||
// this.$store.commit(StoreKeys.SET_APP_CONFIG, newData.appConfig);
|
||||
this.$store.commit(StoreKeys.SET_PAGE_INFO, newData.pageInfo);
|
||||
this.$store.commit(StoreKeys.SET_SECTIONS, newData.sections);
|
||||
},
|
||||
saveLocally() {
|
||||
const msg = this.$t('interactive-editor.menu.save-locally-warning');
|
||||
|
||||
@@ -94,6 +94,7 @@ export default {
|
||||
const raw = rawAppConfig;
|
||||
const isEmptyObject = (obj) => (typeof obj === 'object' && Object.keys(obj).length === 0);
|
||||
const isEmpty = (value) => (value === undefined || isEmptyObject(value));
|
||||
|
||||
// Delete empty values
|
||||
Object.keys(raw).forEach(key => {
|
||||
if (isEmpty(raw[key])) delete raw[key];
|
||||
|
||||
@@ -22,6 +22,14 @@
|
||||
<DownloadConfigIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<!-- Show path to which config file is being used -->
|
||||
<div class="config-path-info">
|
||||
<h3>Config Location</h3>
|
||||
<p>
|
||||
The base config file you are currently using is
|
||||
<a :href="configPath">{{ configPath }}</a>
|
||||
</p>
|
||||
</div>
|
||||
<!-- View Config in Tree Mode Section -->
|
||||
<h3>{{ $t('interactive-editor.export.view-title') }}</h3>
|
||||
<tree-view :data="config" class="config-tree-view" />
|
||||
@@ -61,6 +69,11 @@ export default {
|
||||
allowViewConfig() {
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
configPath() {
|
||||
return this.$store.state.currentConfigInfo?.confPath
|
||||
|| process.env.VUE_APP_CONFIG_PATH
|
||||
|| '/conf.yml';
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
convertJsonToYaml() {
|
||||
@@ -121,6 +134,13 @@ export default {
|
||||
border-bottom: 1px dashed var(--interactive-editor-color);
|
||||
button { margin: 0 1rem; }
|
||||
}
|
||||
.config-path-info {
|
||||
p, a {
|
||||
color: var(--interactive-editor-color);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
border-bottom: 1px dashed var(--interactive-editor-color);
|
||||
}
|
||||
.config-tree-view {
|
||||
padding: 0.5rem;
|
||||
font-family: var(--font-monospace);
|
||||
|
||||
@@ -64,7 +64,6 @@ export default {
|
||||
return this.$store.state.editMode;
|
||||
},
|
||||
sectionKey() {
|
||||
if (this.isEditMode) return undefined;
|
||||
return `collapsible-${this.uniqueKey}`;
|
||||
},
|
||||
collapseClass() {
|
||||
@@ -104,12 +103,23 @@ export default {
|
||||
watch: {
|
||||
checkboxState(newState) {
|
||||
this.isExpanded = newState;
|
||||
this.updateLocalStorage(); // Save every change immediately
|
||||
},
|
||||
uniqueKey() {
|
||||
this.checkboxState = this.isExpanded;
|
||||
uniqueKey(newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
this.refreshCollapseState(); // Refresh state when key changes
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
refreshCollapseState() {
|
||||
this.checkboxState = this.isExpanded;
|
||||
},
|
||||
updateLocalStorage() {
|
||||
const collapseState = this.locallyStoredCollapseStates();
|
||||
collapseState[this.uniqueKey] = this.checkboxState;
|
||||
localStorage.setItem(localStorageKeys.COLLAPSE_STATE, JSON.stringify(collapseState));
|
||||
},
|
||||
/* Either expand or collapse section, based on it's current state */
|
||||
toggle() {
|
||||
this.checkboxState = !this.checkboxState;
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
:target="anchorTarget"
|
||||
:class="`item ${makeClassList}`"
|
||||
v-tooltip="getTooltipOptions()"
|
||||
rel="noopener noreferrer" tabindex="0"
|
||||
:rel="`${item.rel || 'noopener noreferrer'}`"
|
||||
tabindex="0"
|
||||
:id="`link-${item.id}`"
|
||||
:style="customStyle"
|
||||
>
|
||||
@@ -255,8 +256,12 @@ export default {
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: keep-all;
|
||||
overflow: hidden;
|
||||
span.text {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,6 +390,7 @@ p.description {
|
||||
font-size: .9em;
|
||||
line-height: 1rem;
|
||||
height: 2rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +74,8 @@
|
||||
</div>
|
||||
<!-- Modal for opening in modal view -->
|
||||
<IframeModal
|
||||
:ref="`iframeModal`"
|
||||
:name="`iframeModal`"
|
||||
:ref="`iframeModal-${groupId}`"
|
||||
:name="`iframeModal-${groupId}`"
|
||||
@closed="$emit('itemClicked')"
|
||||
/>
|
||||
<!-- Edit item menu -->
|
||||
@@ -213,7 +213,7 @@ export default {
|
||||
methods: {
|
||||
/* Opens the iframe modal */
|
||||
triggerModal(url) {
|
||||
this.$refs.iframeModal.show(url);
|
||||
this.$refs[`iframeModal-${this.groupId}`].show(url);
|
||||
},
|
||||
/* Sorts items alphabetically using the title attribute */
|
||||
sortAlphabetically(items) {
|
||||
|
||||
153
src/components/PageStrcture/CriticalError.vue
Normal file
153
src/components/PageStrcture/CriticalError.vue
Normal file
@@ -0,0 +1,153 @@
|
||||
<template>
|
||||
<div class="critical-error-wrap" v-if="shouldShow">
|
||||
<button class="close" title="Close Warning" @click="close">🗙</button>
|
||||
<h3>{{ $t('critical-error.title') }}</h3>
|
||||
<p>{{ $t('critical-error.subtitle') }}</p>
|
||||
<h4>{{ $t('critical-error.sub-ensure-that') }}</h4>
|
||||
<ul>
|
||||
<li>The configuration file can be found at the specified location</li>
|
||||
<li>There are no CORS rules preventing client-side access</li>
|
||||
<li>The YAML is valid, parsable and matches the schema</li>
|
||||
</ul>
|
||||
<h4>{{ $t('critical-error.sub-error-details') }}</h4>
|
||||
<pre>{{ this.$store.state.criticalError }}</pre>
|
||||
<h4>{{ $t('critical-error.sub-next-steps') }}</h4>
|
||||
<ul>
|
||||
<li>Check the browser console for more details
|
||||
(<a href="https://github.com/Lissy93/dashy/blob/master/docs/troubleshooting.md#how-to-open-browser-console">see how</a>)
|
||||
</li>
|
||||
<li>View the
|
||||
<a href="https://github.com/Lissy93/dashy/blob/master/docs/troubleshooting.md">Troubleshooting Guide</a>
|
||||
and <a href="https://dashy.to/docs/">Docs</a>
|
||||
</li>
|
||||
<li>
|
||||
If you've verified the config is present, accessible and valid, and cannot find the solution
|
||||
in the troubleshooting, docs or GitHub issues,
|
||||
then <a href="https://github.com/Lissy93/dashy/issues/new/choose">open a ticket on GitHub</a>
|
||||
</li>
|
||||
<li>Click 'Ignore Critical Errors' below to not show this warning again</li>
|
||||
</ul>
|
||||
<button class="user-doesnt-care" @click="ignoreWarning">
|
||||
{{ $t('critical-error.ignore-button') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { localStorageKeys } from '@/utils/defaults';
|
||||
import Keys from '@/utils/StoreMutations';
|
||||
|
||||
export default {
|
||||
name: 'CriticalError',
|
||||
computed: {
|
||||
/* Determines if we should show this component.
|
||||
* If error present AND user hasn't disabled */
|
||||
shouldShow() {
|
||||
return this.$store.state.criticalError
|
||||
&& !localStorage[localStorageKeys.DISABLE_CRITICAL_WARNING];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/* Ignore all future errors, by putting a key in local storage */
|
||||
ignoreWarning() {
|
||||
localStorage.setItem(localStorageKeys.DISABLE_CRITICAL_WARNING, true);
|
||||
this.close();
|
||||
},
|
||||
/* Close this dialog, by removing this error from the local store */
|
||||
close() {
|
||||
this.$store.commit(Keys.CRITICAL_ERROR_MSG, null);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/styles/media-queries.scss';
|
||||
.critical-error-wrap {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 3;
|
||||
max-width: 50rem;
|
||||
background: var(--background-darker);
|
||||
padding: 1rem;
|
||||
border-radius: var(--curve-factor);
|
||||
color: var(--danger);
|
||||
border: 2px solid var(--danger);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
transition: all 0.2s ease-in-out;
|
||||
@include tablet-down {
|
||||
top: 50%;
|
||||
width: 85vw;
|
||||
}
|
||||
p, ul, h4, a {
|
||||
margin: 0;
|
||||
color: var(--white);
|
||||
}
|
||||
pre {
|
||||
color: var(--warning);
|
||||
font-size: 0.8rem;
|
||||
overflow: auto;
|
||||
background: var(--transparent-white-10);
|
||||
padding: 0.5rem;
|
||||
border-radius: var(--curve-factor);
|
||||
}
|
||||
h4 {
|
||||
margin: 0.5rem 0 0 0;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
h3 {
|
||||
font-size: 2.2rem;
|
||||
text-align: center;
|
||||
background: var(--danger);
|
||||
color: var(--white);
|
||||
margin: -1rem -1rem 1rem -1rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
ul {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
.user-doesnt-care {
|
||||
background: var(--background-darker);
|
||||
color: var(--white);
|
||||
border-radius: var(--curve-factor);
|
||||
border: none;
|
||||
text-decoration: underline;
|
||||
padding: 0.25rem 0.5rem;
|
||||
cursor: pointer;
|
||||
width: fit-content;
|
||||
margin: 0 auto;
|
||||
transition: all 0.2s ease-in-out;
|
||||
&:hover {
|
||||
background: var(--danger);
|
||||
color: var(--background-darker);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 1rem;
|
||||
background: var(--background);
|
||||
color: var(--primary);
|
||||
border: none;
|
||||
border-radius: var(--curve-factor);
|
||||
transition: all 0.2s ease-in-out;
|
||||
&:hover {
|
||||
background: var(--primary);
|
||||
color: var(--background);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,16 +1,13 @@
|
||||
<template>
|
||||
<!-- User Footer -->
|
||||
<footer v-if="text && text !== '' && visible" v-html="text"></footer>
|
||||
<!-- Default Footer -->
|
||||
<footer v-else-if="visible">
|
||||
<span v-if="$store.state.currentConfigInfo" class="path-to-config">
|
||||
Using: {{ $store.state.currentConfigInfo.confPath }}
|
||||
</span>
|
||||
<span>
|
||||
{{ $t('footer.dev-by') }} <a :href="authorUrl">{{authorName}}</a>.
|
||||
{{ $t('footer.licensed-under') }} <a :href="licenseUrl">{{license}}</a>
|
||||
{{ showCopyright? '©': '' }} {{date}}.
|
||||
{{ $t('footer.get-the') }} <a :href="repoUrl">{{ $t('footer.source-code') }}</a>.
|
||||
<footer v-if="visible">
|
||||
<!-- User-defined footer -->
|
||||
<span v-if="text" v-html="text"></span>
|
||||
<!-- Default footer -->
|
||||
<span v-else>
|
||||
<a :href="defaultInfo.projectUrl">Dashy</a> is free & open source
|
||||
- licensed under <a :href="defaultInfo.licenseUrl">{{defaultInfo.license}}</a>,
|
||||
© <a :href="defaultInfo.authorUrl">{{defaultInfo.authorName}}</a> {{defaultInfo.date}}.
|
||||
Get support on GitHub, at <a :href="defaultInfo.repoUrl">{{defaultInfo.repoName}}</a>.
|
||||
</span>
|
||||
</footer>
|
||||
</template>
|
||||
@@ -23,13 +20,20 @@ export default {
|
||||
name: 'Footer',
|
||||
props: {
|
||||
text: String,
|
||||
authorName: { type: String, default: 'Alicia Sykes' },
|
||||
authorUrl: { type: String, default: 'https://aliciasykes.com' },
|
||||
license: { type: String, default: 'MIT' },
|
||||
licenseUrl: { type: String, default: 'https://gist.github.com/Lissy93/143d2ee01ccc5c052a17' },
|
||||
date: { type: String, default: `${new Date().getFullYear()}` },
|
||||
showCopyright: { type: Boolean, default: true },
|
||||
repoUrl: { type: String, default: 'https://github.com/lissy93/dashy' },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
defaultInfo: {
|
||||
authorName: 'Alicia Sykes',
|
||||
authorUrl: 'https://as93.net',
|
||||
license: 'MIT',
|
||||
licenseUrl: 'https://gist.github.com/Lissy93/143d2ee01ccc5c052a17',
|
||||
date: `${new Date().getFullYear()}`,
|
||||
repoUrl: 'https://github.com/lissy93/dashy',
|
||||
repoName: 'Lissy93/Dashy',
|
||||
projectUrl: 'https://dashy.to',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
visible() {
|
||||
@@ -56,7 +60,7 @@ footer {
|
||||
display: none;
|
||||
}
|
||||
span.path-to-config {
|
||||
float: right;
|
||||
float: left;
|
||||
font-size: 0.75rem;
|
||||
margin: 0.1rem 0.5rem 0 0;
|
||||
opacity: var(--dimming-factor);
|
||||
|
||||
@@ -66,7 +66,7 @@ export default {
|
||||
span.subtitle {
|
||||
color: var(--heading-text-color);
|
||||
font-style: italic;
|
||||
text-shadow: 1px 1px 2px #130f23;
|
||||
text-shadow: 1px 1px 2px #130f2347;
|
||||
opacity: var(--dimming-factor);
|
||||
}
|
||||
img.site-logo {
|
||||
|
||||
@@ -319,6 +319,10 @@ div.action-buttons {
|
||||
min-width: 6rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: 1rem 0.5rem 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,8 +37,10 @@ export default {
|
||||
input: '',
|
||||
};
|
||||
},
|
||||
props: {
|
||||
iconSize: String,
|
||||
computed: {
|
||||
iconSize() {
|
||||
return this.$store.getters.iconSize;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
IconSmall,
|
||||
|
||||
@@ -5,19 +5,19 @@
|
||||
<IconDeafault
|
||||
@click="updateDisplayLayout('auto')"
|
||||
v-tooltip="tooltip($t('settings.layout-auto'))"
|
||||
:class="`layout-icon ${displayLayout === 'auto' ? 'selected' : ''}`"
|
||||
:class="`layout-icon ${layout === 'auto' ? 'selected' : ''}`"
|
||||
tabindex="-2"
|
||||
/>
|
||||
<IconHorizontal
|
||||
@click="updateDisplayLayout('horizontal')"
|
||||
v-tooltip="tooltip($t('settings.layout-horizontal'))"
|
||||
:class="`layout-icon ${displayLayout === 'horizontal' ? 'selected' : ''}`"
|
||||
:class="`layout-icon ${layout === 'horizontal' ? 'selected' : ''}`"
|
||||
tabindex="-2"
|
||||
/>
|
||||
<IconVertical
|
||||
@click="updateDisplayLayout('vertical')"
|
||||
v-tooltip="tooltip($t('settings.layout-vertical'))"
|
||||
:class="`layout-icon ${displayLayout === 'vertical' ? 'selected' : ''}`"
|
||||
:class="`layout-icon ${layout === 'vertical' ? 'selected' : ''}`"
|
||||
tabindex="-2"
|
||||
/>
|
||||
</div>
|
||||
@@ -40,6 +40,11 @@ export default {
|
||||
IconHorizontal,
|
||||
IconVertical,
|
||||
},
|
||||
computed: {
|
||||
layout() {
|
||||
return this.$store.getters.layout;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateDisplayLayout(layout) {
|
||||
this.$store.commit(StoreKeys.SET_ITEM_LAYOUT, layout);
|
||||
|
||||
@@ -1,36 +1,70 @@
|
||||
<template>
|
||||
<transition name="slide-fade">
|
||||
<div class="kb-sc-info" v-if="!shouldHide">
|
||||
<h5>There are keyboard shortcuts! ⌨️🙌</h5>
|
||||
<h5>{{ popupContent.title }}</h5>
|
||||
<div class="close" title="Hide forever [Esc]" @click="hideWelcomeHelper()">x</div>
|
||||
<p title="Press [Esc] to hide this tip forever. See there's even a shortcut for that! 🚀">
|
||||
Just start typing to filter. Then use the tab key to cycle through results,
|
||||
and press enter to launch the selected item, or alt + enter to open in a modal.
|
||||
You can hit Esc at anytime to clear the search. Easy 🥳
|
||||
</p>
|
||||
<p :title="popupContent.hoverText">{{ popupContent.message }}</p>
|
||||
<p :title="popupContent.hoverText">{{ popupContent.messageContinued }}</p>
|
||||
<div class="action-buttons">
|
||||
<button @click="exportConfig">Export Local Config</button>
|
||||
<button @click="saveConfig">Save Changes to Disk</button>
|
||||
<button @click="resetLocalConfig">Reset Local Changes</button>
|
||||
<button @click="hideWelcomeHelper">Dismiss this Notification</button>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { localStorageKeys } from '@/utils/defaults';
|
||||
import { localStorageKeys, modalNames } from '@/utils/defaults';
|
||||
import StoreKeys from '@/utils/StoreMutations';
|
||||
import configSavingMixin from '@/mixins/ConfigSaving';
|
||||
|
||||
export default {
|
||||
name: 'KeyboardShortcutInfo',
|
||||
mixins: [configSavingMixin],
|
||||
data() {
|
||||
return {
|
||||
shouldHide: true, // False = show/ true = hide. Intuitive, eh?
|
||||
timeDelay: 3000, // Short delay in ms before popup appears
|
||||
timeDelay: 2000, // Short delay in ms before popup appears
|
||||
popupContent: {
|
||||
title: '⚠️ You\'re using a local config',
|
||||
message: `This means that your settings are saved in this browser only,
|
||||
and won't persist across devices.`,
|
||||
messageContinued: `To ensure you don't loose your changes,
|
||||
it's recommended to download a copy of your config, so you can restore it later.`,
|
||||
hoverText: 'Press [Esc] to hide this warning',
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
exportConfig() {
|
||||
this.$modal.show(modalNames.EXPORT_CONFIG_MENU);
|
||||
this.shouldHide = true;
|
||||
},
|
||||
saveConfig() {
|
||||
const localConfig = this.$store.state.config;
|
||||
this.writeConfigToDisk(localConfig);
|
||||
this.shouldHide = true;
|
||||
},
|
||||
resetLocalConfig() {
|
||||
const msg = `${this.$t('config.reset-config-msg-l1')} `
|
||||
+ `${this.$t('config.reset-config-msg-l2')}\n\n${this.$t('config.reset-config-msg-l3')}`;
|
||||
const isTheUserSure = confirm(msg); // eslint-disable-line no-alert, no-restricted-globals
|
||||
if (isTheUserSure) {
|
||||
localStorage.clear();
|
||||
this.$toasted.show(this.$t('config.data-cleared-msg'));
|
||||
this.$store.dispatch(StoreKeys.INITIALIZE_CONFIG);
|
||||
this.shouldHide = true;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Returns true if the key exists in session storage, otherwise false
|
||||
* And the !! just converts 'false' to false, as strings resolve to true
|
||||
*/
|
||||
shouldHideWelcomeMessage() {
|
||||
return !!localStorage[localStorageKeys.HIDE_WELCOME_BANNER];
|
||||
return !!localStorage[localStorageKeys.HIDE_INFO_NOTIFICATION];
|
||||
},
|
||||
/**
|
||||
* Update session storage, so that it won't be shown again
|
||||
@@ -38,7 +72,7 @@ export default {
|
||||
*/
|
||||
hideWelcomeHelper() {
|
||||
this.shouldHide = true;
|
||||
localStorage.setItem(localStorageKeys.HIDE_WELCOME_BANNER, true);
|
||||
localStorage.setItem(localStorageKeys.HIDE_INFO_NOTIFICATION, true);
|
||||
window.removeEventListener('keyup', this.keyPressEvent);
|
||||
},
|
||||
/* Passed to window function, to add/ remove event listener */
|
||||
@@ -114,6 +148,23 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 1em;
|
||||
button {
|
||||
padding: 0.2rem;
|
||||
background: var(--welcome-popup-background);
|
||||
color: var(--welcome-popup-text-color);
|
||||
border: 1px solid var(--welcome-popup-text-color);
|
||||
border-radius: var(--curve-factor);
|
||||
transition: all 0.2s ease-in-out;
|
||||
&:hover {
|
||||
background: var(--welcome-popup-text-color);
|
||||
color: var(--welcome-popup-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Animations, animations everywhere */
|
||||
.slide-fade-enter-active {
|
||||
transition: all 1s ease;
|
||||
@@ -95,7 +95,8 @@ export default {
|
||||
},
|
||||
/* If configured, launch specific app when hotkey pressed */
|
||||
handleHotKey(key) {
|
||||
const usersHotKeys = this.getCustomKeyShortcuts();
|
||||
const sections = this.$store.getters.sections || [];
|
||||
const usersHotKeys = this.getCustomKeyShortcuts(sections);
|
||||
usersHotKeys.forEach((hotkey) => {
|
||||
if (hotkey.hotkey === parseInt(key, 10)) {
|
||||
if (hotkey.url) window.open(hotkey.url, '_blank');
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="options-outer">
|
||||
<div :class="`options-container ${!settingsVisible ? 'hide' : ''}`">
|
||||
<ThemeSelector />
|
||||
<LayoutSelector :displayLayout="displayLayout" />
|
||||
<LayoutSelector :displayLayout="$store.getters.layout" />
|
||||
<ItemSizeSelector :iconSize="iconSize" />
|
||||
<ConfigLauncher />
|
||||
<AuthButtons v-if="userState !== 0" :userType="userState" />
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
:value="$store.getters.theme"
|
||||
class="theme-dropdown"
|
||||
:tabindex="-2"
|
||||
@input="themeChanged"
|
||||
@input="themeChangedInUI"
|
||||
/>
|
||||
</div>
|
||||
<IconPalette
|
||||
@@ -28,18 +28,13 @@
|
||||
<script>
|
||||
|
||||
import CustomThemeMaker from '@/components/Settings/CustomThemeMaker';
|
||||
import {
|
||||
LoadExternalTheme,
|
||||
ApplyLocalTheme,
|
||||
ApplyCustomVariables,
|
||||
} from '@/utils/ThemeHelper';
|
||||
import Defaults, { localStorageKeys } from '@/utils/defaults';
|
||||
import Keys from '@/utils/StoreMutations';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import IconPalette from '@/assets/interface-icons/config-color-palette.svg';
|
||||
import ThemingMixin from '@/mixins/ThemingMixin';
|
||||
|
||||
export default {
|
||||
name: 'ThemeSelector',
|
||||
mixins: [ThemingMixin],
|
||||
props: {
|
||||
hidePallete: Boolean,
|
||||
},
|
||||
@@ -47,101 +42,16 @@ export default {
|
||||
CustomThemeMaker,
|
||||
IconPalette,
|
||||
},
|
||||
watch: {
|
||||
/* When theme in VueX store changes, then update theme */
|
||||
themeFromStore(newTheme) {
|
||||
this.selectedTheme = newTheme;
|
||||
this.updateTheme(newTheme);
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedTheme: '',
|
||||
themeConfiguratorOpen: false, // Control the opening of theme config popup
|
||||
themeHelper: new LoadExternalTheme(),
|
||||
ApplyLocalTheme,
|
||||
ApplyCustomVariables,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
/* Get appConfig from store */
|
||||
appConfig() {
|
||||
return this.$store.getters.appConfig;
|
||||
},
|
||||
/* Get users theme from store */
|
||||
themeFromStore() {
|
||||
return this.$store.getters.theme;
|
||||
},
|
||||
/* Combines all theme names (builtin and user defined) together */
|
||||
themeNames: function themeNames() {
|
||||
const externalThemeNames = Object.keys(this.externalThemes);
|
||||
const specialThemes = ['custom'];
|
||||
return [...this.extraThemeNames, ...externalThemeNames,
|
||||
...Defaults.builtInThemes, ...specialThemes];
|
||||
},
|
||||
extraThemeNames() {
|
||||
const userThemes = this.appConfig.cssThemes || [];
|
||||
if (typeof userThemes === 'string') return [userThemes];
|
||||
return userThemes;
|
||||
},
|
||||
/* Returns an array of links to external CSS from the Config */
|
||||
externalThemes() {
|
||||
const availibleThemes = {};
|
||||
if (this.appConfig && this.appConfig.externalStyleSheet) {
|
||||
const externals = this.appConfig.externalStyleSheet;
|
||||
if (Array.isArray(externals)) {
|
||||
externals.forEach((ext, i) => {
|
||||
availibleThemes[`External Stylesheet ${i + 1}`] = ext;
|
||||
});
|
||||
} else if (typeof externals === 'string') {
|
||||
availibleThemes['External Stylesheet'] = this.appConfig.externalStyleSheet;
|
||||
} else {
|
||||
ErrorHandler('External stylesheets must be of type string or string[]');
|
||||
}
|
||||
}
|
||||
// availibleThemes.Default = '#';
|
||||
return availibleThemes;
|
||||
},
|
||||
},
|
||||
computed: {},
|
||||
mounted() {
|
||||
const initialTheme = this.getInitialTheme();
|
||||
this.selectedTheme = initialTheme;
|
||||
// Quicker loading, if the theme is local we can apply it immidiatley
|
||||
if (this.isThemeLocal(initialTheme)) {
|
||||
this.updateTheme(initialTheme);
|
||||
}
|
||||
|
||||
// If it's an external stylesheet, then wait for promise to resolve
|
||||
if (this.externalThemes && Object.entries(this.externalThemes).length > 0) {
|
||||
const added = Object.keys(this.externalThemes).map(
|
||||
name => this.themeHelper.add(name, this.externalThemes[name]),
|
||||
);
|
||||
// Once, added, then apply users initial theme
|
||||
Promise.all(added).then(() => {
|
||||
this.updateTheme(initialTheme);
|
||||
});
|
||||
}
|
||||
this.initializeTheme();
|
||||
},
|
||||
methods: {
|
||||
/* Called when dropdown changed
|
||||
* Updates store, which will in turn update theme through watcher
|
||||
*/
|
||||
themeChanged() {
|
||||
const pageId = this.$store.state.currentConfigInfo?.pageId || null;
|
||||
this.$store.commit(Keys.SET_THEME, { theme: this.selectedTheme, pageId });
|
||||
this.updateTheme(this.selectedTheme);
|
||||
},
|
||||
/* Returns the initial theme */
|
||||
getInitialTheme() {
|
||||
const localTheme = localStorage[localStorageKeys.THEME];
|
||||
if (localTheme && localTheme !== 'undefined') return localTheme;
|
||||
return this.appConfig.theme || Defaults.theme;
|
||||
},
|
||||
/* Determines if a given theme is local / not a custom user stylesheet */
|
||||
isThemeLocal(themeToCheck) {
|
||||
const localThemes = [...Defaults.builtInThemes, ...this.extraThemeNames];
|
||||
return localThemes.includes(themeToCheck);
|
||||
},
|
||||
/* Opens the theme color configurator popup */
|
||||
openThemeConfigurator() {
|
||||
this.$store.commit(Keys.SET_MODAL_OPEN, true);
|
||||
@@ -154,24 +64,6 @@ export default {
|
||||
this.themeConfiguratorOpen = false;
|
||||
}
|
||||
},
|
||||
/* Updates theme. Checks if the new theme is local or external,
|
||||
and calls appropirate updating function. Updates local storage */
|
||||
updateTheme(newTheme) {
|
||||
if (newTheme === 'Default') {
|
||||
this.resetToDefault();
|
||||
this.themeHelper.theme = 'Default';
|
||||
} else if (this.isThemeLocal(newTheme)) {
|
||||
this.ApplyLocalTheme(newTheme);
|
||||
} else {
|
||||
this.themeHelper.theme = newTheme;
|
||||
}
|
||||
this.ApplyCustomVariables(newTheme);
|
||||
// localStorage.setItem(localStorageKeys.THEME, newTheme);
|
||||
},
|
||||
/* Removes any applied themes */
|
||||
resetToDefault() {
|
||||
document.getElementsByTagName('html')[0].removeAttribute('data-theme');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -26,7 +26,7 @@ export default {
|
||||
/* URL/ IP or hostname to the AdGuardHome instance, without trailing slash */
|
||||
hostname() {
|
||||
if (!this.options.hostname) this.error('You must specify the path to your AdGuard server');
|
||||
return this.options.hostname;
|
||||
return this.parseAsEnvVar(this.options.hostname);
|
||||
},
|
||||
showFullInfo() {
|
||||
return this.options.showFullInfo;
|
||||
@@ -39,7 +39,9 @@ export default {
|
||||
},
|
||||
authHeaders() {
|
||||
if (this.options.username && this.options.password) {
|
||||
const encoded = window.btoa(`${this.options.username}:${this.options.password}`);
|
||||
const password = this.parseAsEnvVar(this.options.password);
|
||||
const username = this.parseAsEnvVar(this.options.username);
|
||||
const encoded = window.btoa(`${username}:${password}`);
|
||||
return { Authorization: `Basic ${encoded}` };
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -38,7 +38,7 @@ export default {
|
||||
/* URL/ IP or hostname to the AdGuardHome instance, without trailing slash */
|
||||
hostname() {
|
||||
if (!this.options.hostname) this.error('You must specify the path to your AdGuard server');
|
||||
return this.options.hostname;
|
||||
return this.parseAsEnvVar(this.options.hostname);
|
||||
},
|
||||
showOnOffStatusOnly() {
|
||||
return this.options.showOnOffStatusOnly;
|
||||
@@ -48,7 +48,9 @@ export default {
|
||||
},
|
||||
authHeaders() {
|
||||
if (this.options.username && this.options.password) {
|
||||
const encoded = window.btoa(`${this.options.username}:${this.options.password}`);
|
||||
const username = this.parseAsEnvVar(this.options.username);
|
||||
const password = this.parseAsEnvVar(this.options.password);
|
||||
const encoded = window.btoa(`${username}:${password}`);
|
||||
return { Authorization: `Basic ${encoded}` };
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -20,14 +20,16 @@ export default {
|
||||
/* URL/ IP or hostname to the AdGuardHome instance, without trailing slash */
|
||||
hostname() {
|
||||
if (!this.options.hostname) this.error('You must specify the path to your AdGuard server');
|
||||
return this.options.hostname;
|
||||
return this.parseAsEnvVar(this.options.hostname);
|
||||
},
|
||||
endpoint() {
|
||||
return `${this.hostname}/control/stats`;
|
||||
},
|
||||
authHeaders() {
|
||||
if (this.options.username && this.options.password) {
|
||||
const encoded = window.btoa(`${this.options.username}:${this.options.password}`);
|
||||
const username = this.parseAsEnvVar(this.options.username);
|
||||
const password = this.parseAsEnvVar(this.options.password);
|
||||
const encoded = window.btoa(`${username}:${password}`);
|
||||
return { Authorization: `Basic ${encoded}` };
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -36,11 +36,13 @@ export default {
|
||||
/* URL/ IP or hostname to the AdGuardHome instance, without trailing slash */
|
||||
hostname() {
|
||||
if (!this.options.hostname) this.error('You must specify the path to your AdGuard server');
|
||||
return this.options.hostname;
|
||||
return this.parseAsEnvVar(this.options.hostname);
|
||||
},
|
||||
authHeaders() {
|
||||
if (this.options.username && this.options.password) {
|
||||
const encoded = window.btoa(`${this.options.username}:${this.options.password}`);
|
||||
const username = this.parseAsEnvVar(this.options.username);
|
||||
const password = this.parseAsEnvVar(this.options.password);
|
||||
const encoded = window.btoa(`${username}:${password}`);
|
||||
return { Authorization: `Basic ${encoded}` };
|
||||
}
|
||||
return {};
|
||||
|
||||
@@ -113,7 +113,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
hostname() {
|
||||
return this.options.hostname || widgetApiEndpoints.anonAddy;
|
||||
return this.parseAsEnvVar(this.options.hostname) || widgetApiEndpoints.anonAddy;
|
||||
},
|
||||
apiVersion() {
|
||||
return this.options.apiVersion || 'v1';
|
||||
@@ -132,7 +132,7 @@ export default {
|
||||
},
|
||||
apiKey() {
|
||||
if (!this.options.apiKey) this.error('An apiKey is required');
|
||||
return this.options.apiKey;
|
||||
return this.parseAsEnvVar(this.options.apiKey);
|
||||
},
|
||||
hideMeta() {
|
||||
return this.options.hideMeta;
|
||||
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
},
|
||||
apiKey() {
|
||||
if (!this.options.apiKey) this.error('Missing API Key');
|
||||
return this.options.apiKey;
|
||||
return this.parseAsEnvVar(this.options.apiKey);
|
||||
},
|
||||
endpoint() {
|
||||
return `${widgetApiEndpoints.blacklistCheck}/${this.ipAddress}`;
|
||||
|
||||
@@ -38,12 +38,12 @@ export default {
|
||||
/* The username to fetch data from - REQUIRED */
|
||||
username() {
|
||||
if (!this.options.username) this.error('You must specify a username');
|
||||
return this.options.username;
|
||||
return this.parseAsEnvVar(this.options.username);
|
||||
},
|
||||
/* Optionally override hostname, if using a self-hosted instance */
|
||||
hostname() {
|
||||
if (this.options.hostname) return this.options.hostname;
|
||||
return widgetApiEndpoints.codeStats;
|
||||
return this.parseAsEnvVar(widgetApiEndpoints.codeStats);
|
||||
},
|
||||
hideMeta() {
|
||||
return this.options.hideMeta || false;
|
||||
|
||||
@@ -63,11 +63,11 @@ export default {
|
||||
computed: {
|
||||
apiKey() {
|
||||
if (!this.options.apiKey) this.error('Missing API Key');
|
||||
return this.options.apiKey;
|
||||
return this.parseAsEnvVar(this.options.apiKey);
|
||||
},
|
||||
domain() {
|
||||
if (!this.options.domain) this.error('Missing Domain Name Key');
|
||||
return this.options.domain;
|
||||
return this.parseAsEnvVar(this.options.domain);
|
||||
},
|
||||
endpoint() {
|
||||
return `${widgetApiEndpoints.domainMonitor}/?domain=${this.domain}&r=whois&apikey=${this.apiKey}`;
|
||||
|
||||
@@ -106,7 +106,7 @@ export default {
|
||||
if (!this.options.apiKey) {
|
||||
this.error('An API key is required, please see the docs for more info');
|
||||
}
|
||||
return this.options.apiKey;
|
||||
return this.parseAsEnvVar(this.options.apiKey);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -45,7 +45,7 @@ export default {
|
||||
computed: {
|
||||
/* The users API key for exchangerate-api.com */
|
||||
apiKey() {
|
||||
return this.options.apiKey;
|
||||
return this.parseAsEnvVar(this.options.apiKey);
|
||||
},
|
||||
/* The currency to convert results into */
|
||||
inputCurrency() {
|
||||
|
||||
@@ -71,7 +71,7 @@ export default {
|
||||
this.error('An API key must be supplied');
|
||||
return '';
|
||||
}
|
||||
return usersChoice;
|
||||
return this.parseAsEnvVar(usersChoice);
|
||||
},
|
||||
/* The direction of flights: Arrival, Departure or Both */
|
||||
direction() {
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
<template>
|
||||
<div class="readme-stats">
|
||||
<img class="stats-card" v-if="!hideProfileCard" :src="profileCard" alt="Profile Card" />
|
||||
<img class="stats-card" v-if="!hideLanguagesCard" :src="topLanguagesCard" alt="Languages" />
|
||||
<a v-if="!hideProfileCard" :href="profileCardLink" target="_blank">
|
||||
<img class="stats-card" :src="profileCard" alt="Profile Card" />
|
||||
</a>
|
||||
<a v-if="!hideLanguagesCard" :href="profileCardLink" target="_blank">
|
||||
<img class="stats-card" :src="topLanguagesCard" alt="Languages" />
|
||||
</a>
|
||||
<template v-if="repos">
|
||||
<img class="stats-card" v-for="(repo, i) in repoCards" :key="i" :src="repo" :alt="repo" />
|
||||
<a v-for="(repo, i) in repoCards" :key="i" :href="repo.cardHref" target="_blank">
|
||||
<img class="stats-card" :src="repo.cardSrc" :alt="repo" />
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -61,6 +67,9 @@ export default {
|
||||
profileCard() {
|
||||
return `${widgetApiEndpoints.readMeStats}?username=${this.username}${this.cardConfig}`;
|
||||
},
|
||||
profileCardLink() {
|
||||
return `https://github.com/${this.username}`;
|
||||
},
|
||||
topLanguagesCard() {
|
||||
return `${widgetApiEndpoints.readMeStats}/top-langs/?username=${this.username}`
|
||||
+ `${this.cardConfig}&langs_count=12`;
|
||||
@@ -70,8 +79,11 @@ export default {
|
||||
this.repos.forEach((repo) => {
|
||||
const username = repo.split('/')[0];
|
||||
const repoName = repo.split('/')[1];
|
||||
cards.push(`${widgetApiEndpoints.readMeStats}/pin/?username=${username}&repo=${repoName}`
|
||||
+ `${this.cardConfig}&show_owner=true`);
|
||||
cards.push({
|
||||
cardSrc: `${widgetApiEndpoints.readMeStats}/pin/?username=${username}`
|
||||
+ `&repo=${repoName}${this.cardConfig}&show_owner=true`,
|
||||
cardHref: `https://github.com/${username}/${repoName}`,
|
||||
});
|
||||
});
|
||||
return cards;
|
||||
},
|
||||
|
||||
@@ -112,6 +112,7 @@ export default {
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
width: fit-content;
|
||||
margin: 0.25rem auto;
|
||||
padding: 0.1rem 0.25rem;
|
||||
|
||||
150
src/components/Widgets/GlCpuSpeedometer.vue
Normal file
150
src/components/Widgets/GlCpuSpeedometer.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div class="glances-cpu-gauge-wrapper">
|
||||
<GaugeChart class="gl-speedometer" :value="gaugeValue"
|
||||
:baseColor="baseColor" :shadowColor="shadowColor" :gaugeColor="gaugeColor"
|
||||
:startAngle="startAngle" :endAngle="endAngle" :innerRadius="innerRadius"
|
||||
:separatorThickness="separatorThickness">
|
||||
<p class="percentage">{{ gaugeValue }}%</p>
|
||||
</GaugeChart>
|
||||
<p class="show-more-btn" @click="toggleMoreInfo">
|
||||
{{ showMoreInfo ? $t('widgets.general.show-less') : $t('widgets.general.cpu-details') }}
|
||||
</p>
|
||||
<div class="more-info" v-if="moreInfo && showMoreInfo">
|
||||
<div class="more-info-row" v-for="(info, key) in moreInfo" :key="key">
|
||||
<p class="label">{{ info.label }}</p>
|
||||
<p class="value">{{ info.value }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import GlancesMixin from '@/mixins/GlancesMixin';
|
||||
import GaugeChart from '@/components/Charts/Gauge';
|
||||
import { capitalize } from '@/utils/MiscHelpers';
|
||||
|
||||
export default {
|
||||
mixins: [WidgetMixin, GlancesMixin],
|
||||
components: {
|
||||
GaugeChart,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
gaugeValue: 0,
|
||||
baseColor: '#101010ED',
|
||||
shadowColor: '#00000000',
|
||||
gaugeColor: [
|
||||
{ offset: 0, color: '#20e253' },
|
||||
{ offset: 35, color: '#f6f000' },
|
||||
{ offset: 65, color: '#fca016' },
|
||||
{ offset: 90, color: '#f80363' },
|
||||
],
|
||||
showMoreInfo: false,
|
||||
moreInfo: null,
|
||||
startAngle: -135,
|
||||
endAngle: 135,
|
||||
innerRadius: 80,
|
||||
separatorThickness: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
endpoint() {
|
||||
return this.makeGlancesUrl('cpu');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
processData(cpuData) {
|
||||
this.gaugeValue = cpuData.total;
|
||||
const moreInfo = [];
|
||||
const ignore = ['total', 'cpucore', 'time_since_update',
|
||||
'interrupts', 'soft_interrupts', 'ctx_switches', 'syscalls'];
|
||||
Object.keys(cpuData).forEach((key) => {
|
||||
if (!ignore.includes(key) && cpuData[key]) {
|
||||
moreInfo.push({ label: capitalize(key), value: `${cpuData[key].toFixed(1)}%` });
|
||||
}
|
||||
});
|
||||
this.moreInfo = moreInfo;
|
||||
},
|
||||
toggleMoreInfo() {
|
||||
this.showMoreInfo = !this.showMoreInfo;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.overrideUpdateInterval = 2;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.glances-cpu-gauge-wrapper {
|
||||
max-width: 15rem;
|
||||
margin: 0rem auto;
|
||||
|
||||
p.percentage {
|
||||
color: var(--widget-text-color);
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
font-size: 1.3rem;
|
||||
margin: 3.5rem 0;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.more-info {
|
||||
background: var(--widget-accent-color);
|
||||
border-radius: var(--curve-factor);
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: 0.5rem auto;
|
||||
|
||||
.more-info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
p.label, p.value {
|
||||
color: var(--widget-text-color);
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
p.value {
|
||||
font-family: var(--font-monospace);
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px dashed var(--widget-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.show-more-btn {
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
width: fit-content;
|
||||
margin: -1.1rem auto 0 auto;
|
||||
padding: 0.1rem 0.25rem;
|
||||
border: 1px solid transparent;
|
||||
color: var(--widget-text-color);
|
||||
opacity: var(--dimming-factor);
|
||||
border-radius: var(--curve-factor);
|
||||
|
||||
&:hover {
|
||||
border: 1px solid var(--widget-text-color);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
background: var(--widget-text-color);
|
||||
color: var(--widget-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
/* global override for the Guage tick lines */
|
||||
.gl-speedometer svg line {
|
||||
stroke: var(--widget-text-color);
|
||||
opacity: .3;
|
||||
}
|
||||
</style>
|
||||
150
src/components/Widgets/GlMemSpeedometer.vue
Normal file
150
src/components/Widgets/GlMemSpeedometer.vue
Normal file
@@ -0,0 +1,150 @@
|
||||
<template>
|
||||
<div class="glances-cpu-gauge-wrapper">
|
||||
<GaugeChart class="gl-speedometer" :value="gaugeValue"
|
||||
:baseColor="baseColor" :shadowColor="shadowColor" :gaugeColor="gaugeColor"
|
||||
:startAngle="startAngle" :endAngle="endAngle" :innerRadius="innerRadius"
|
||||
:separatorThickness="separatorThickness">
|
||||
<p class="percentage">{{ gaugeValue }}%</p>
|
||||
</GaugeChart>
|
||||
<p class="show-more-btn" @click="toggleMoreInfo">
|
||||
{{ showMoreInfo ? $t('widgets.general.show-less') : $t('widgets.general.mem-details') }}
|
||||
</p>
|
||||
<div class="more-info" v-if="moreInfo && showMoreInfo">
|
||||
<div class="more-info-row" v-for="(info, key) in moreInfo" :key="key">
|
||||
<p class="label">{{ info.label }}</p>
|
||||
<p class="value">{{ info.value }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import GlancesMixin from '@/mixins/GlancesMixin';
|
||||
import GaugeChart from '@/components/Charts/Gauge';
|
||||
import { capitalize, convertBytes } from '@/utils/MiscHelpers';
|
||||
|
||||
export default {
|
||||
mixins: [WidgetMixin, GlancesMixin],
|
||||
components: {
|
||||
GaugeChart,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
gaugeValue: 0,
|
||||
baseColor: '#101010ED',
|
||||
shadowColor: '#00000000',
|
||||
gaugeColor: [
|
||||
{ offset: 0, color: '#20e253' },
|
||||
{ offset: 35, color: '#f6f000' },
|
||||
{ offset: 65, color: '#fca016' },
|
||||
{ offset: 90, color: '#f80363' },
|
||||
],
|
||||
showMoreInfo: false,
|
||||
moreInfo: null,
|
||||
startAngle: -135,
|
||||
endAngle: 135,
|
||||
innerRadius: 80,
|
||||
separatorThickness: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
endpoint() {
|
||||
return this.makeGlancesUrl('mem');
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
processData(memData) {
|
||||
this.gaugeValue = memData.percent;
|
||||
const moreInfo = [];
|
||||
const ignore = ['percent'];
|
||||
Object.keys(memData).forEach((key) => {
|
||||
if (!ignore.includes(key) && memData[key]) {
|
||||
moreInfo.push({ label: capitalize(key), value: convertBytes(memData[key]) });
|
||||
}
|
||||
});
|
||||
this.moreInfo = moreInfo;
|
||||
},
|
||||
toggleMoreInfo() {
|
||||
this.showMoreInfo = !this.showMoreInfo;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.overrideUpdateInterval = 2;
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.glances-cpu-gauge-wrapper {
|
||||
max-width: 15rem;
|
||||
margin: 0rem auto;
|
||||
|
||||
p.percentage {
|
||||
color: var(--widget-text-color);
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
font-size: 1.3rem;
|
||||
margin: 3.5rem 0;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.more-info {
|
||||
background: var(--widget-accent-color);
|
||||
border-radius: var(--curve-factor);
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: 0.5rem auto;
|
||||
|
||||
.more-info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
p.label,
|
||||
p.value {
|
||||
color: var(--widget-text-color);
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
||||
p.value {
|
||||
font-family: var(--font-monospace);
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px dashed var(--widget-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.show-more-btn {
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
text-align: center;
|
||||
width: fit-content;
|
||||
margin: -1.1rem auto 0 auto;
|
||||
padding: 0.1rem 0.25rem;
|
||||
border: 1px solid transparent;
|
||||
color: var(--widget-text-color);
|
||||
opacity: var(--dimming-factor);
|
||||
border-radius: var(--curve-factor);
|
||||
|
||||
&:hover {
|
||||
border: 1px solid var(--widget-text-color);
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
background: var(--widget-text-color);
|
||||
color: var(--widget-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
/* global override for the Guage tick lines */
|
||||
.gl-speedometer svg line {
|
||||
stroke: var(--widget-text-color);
|
||||
opacity: .3;
|
||||
}
|
||||
</style>
|
||||
@@ -58,7 +58,7 @@ export default {
|
||||
},
|
||||
hostname() {
|
||||
if (!this.options.hostname) this.error('`hostname` is required');
|
||||
return this.options.hostname;
|
||||
return this.parseAsEnvVar(this.options.hostname);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
this.error('An API key is required, please see the docs for more info');
|
||||
}
|
||||
if (typeof this.options.apiKey === 'string') {
|
||||
return [this.options.apiKey];
|
||||
return [this.parseAsEnvVar(this.options.apiKey)];
|
||||
}
|
||||
return this.options.apiKey;
|
||||
},
|
||||
|
||||
@@ -74,6 +74,7 @@ export default {
|
||||
this.jokeLine2 = data.delivery;
|
||||
} else if (this.jokeType === 'single') {
|
||||
this.jokeLine1 = data.joke;
|
||||
this.jokeLine2 = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -30,11 +30,11 @@ export default {
|
||||
computed: {
|
||||
endpoint() {
|
||||
if (!this.options.host) this.error('linkgding Host is required');
|
||||
return `${this.options.host}/api/bookmarks`;
|
||||
return `${this.parseAsEnvVar(this.options.host)}/api/bookmarks`;
|
||||
},
|
||||
apiKey() {
|
||||
if (!this.options.apiKey) this.error('linkgding apiKey is required');
|
||||
return this.options.apiKey;
|
||||
return this.parseAsEnvVar(this.options.apiKey);
|
||||
},
|
||||
filtertags() {
|
||||
return this.options.tags;
|
||||
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
computed: {
|
||||
apiKey() {
|
||||
if (!this.options.apiKey) this.error('An API key is required, see docs for more info');
|
||||
return this.options.apiKey;
|
||||
return this.parseAsEnvVar(this.options.apiKey);
|
||||
},
|
||||
country() {
|
||||
return this.options.country ? `&country=${this.options.country}` : '';
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</span>
|
||||
<span v-if="canDeleteNotification('delete')">
|
||||
<a @click="deleteNotification(notification.notification_id)"
|
||||
class="action secondary">{{ tt('delete-notification') }}</a>
|
||||
class="action secondary">{{ tt('delete-notification') }}</a>
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<em v-html="formatNumber(shares.num_shares)"></em>
|
||||
<strong>{{ tt('local') }}</strong> <small> {{ tt('and') }}</small>
|
||||
<em v-html="formatNumber(shares.num_fed_shares_sent
|
||||
+ shares.num_fed_shares_received)"></em>
|
||||
+ shares.num_fed_shares_received)"></em>
|
||||
<strong>
|
||||
{{ tt('federated-shares') }}
|
||||
</strong>
|
||||
|
||||
@@ -36,13 +36,14 @@ export default {
|
||||
computed: {
|
||||
/* Let user select which comic to display: random, latest or a specific number */
|
||||
hostname() {
|
||||
const usersChoice = this.options.hostname;
|
||||
const usersChoice = this.parseAsEnvVar(this.options.hostname);
|
||||
if (!usersChoice) this.error('You must specify the hostname for your Pi-Hole server');
|
||||
return usersChoice || 'http://pi.hole';
|
||||
},
|
||||
apiKey() {
|
||||
if (!this.options.apiKey) this.error('API Key is required, please see the docs');
|
||||
return this.options.apiKey;
|
||||
const usersChoice = this.parseAsEnvVar(this.options.apiKey);
|
||||
if (!usersChoice) this.error('API Key is required, please see the docs');
|
||||
return usersChoice;
|
||||
},
|
||||
endpoint() {
|
||||
return `${this.hostname}/admin/api.php?summary&auth=${this.apiKey}`;
|
||||
|
||||
@@ -34,22 +34,22 @@ export default {
|
||||
computed: {
|
||||
clusterUrl() {
|
||||
if (!this.options.cluster_url) this.error('The cluster URL is required.');
|
||||
return this.options.cluster_url || '';
|
||||
return this.parseAsEnvVar(this.options.cluster_url) || '';
|
||||
},
|
||||
userName() {
|
||||
if (!this.options.user_name) this.error('The user name is required.');
|
||||
return this.options.user_name || '';
|
||||
return this.parseAsEnvVar(this.options.user_name) || '';
|
||||
},
|
||||
tokenName() {
|
||||
if (!this.options.token_name) this.error('The token name is required.');
|
||||
return this.options.token_name || '';
|
||||
return this.parseAsEnvVar(this.options.token_name) || '';
|
||||
},
|
||||
tokenUuid() {
|
||||
if (!this.options.token_uuid) this.error('The token uuid is required.');
|
||||
return this.options.token_uuid || '';
|
||||
return this.parseAsEnvVar(this.options.token_uuid) || '';
|
||||
},
|
||||
node() {
|
||||
return this.options.node || '';
|
||||
return this.parseAsEnvVar(this.options.node) || '';
|
||||
},
|
||||
nodeData() {
|
||||
return this.options.node_data || false;
|
||||
@@ -94,7 +94,7 @@ export default {
|
||||
}
|
||||
},
|
||||
processData(data) {
|
||||
this.data = data.data.sort((a, b) => a.vmid > b.vmid);
|
||||
this.data = data.data.sort((a, b) => Number(a.vmid) > Number(b.vmid));
|
||||
if (this.hideTemplates) {
|
||||
this.data = this.data.filter(item => item.template !== 1);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,8 @@ export default {
|
||||
const formatType = (ht) => capitalize(ht.replaceAll('_', ' '));
|
||||
holidays.forEach((holiday) => {
|
||||
results.push({
|
||||
name: holiday.name.filter(p => p.lang == this.options.lang)[0].text || holiday.name[0].text,
|
||||
name: holiday.name
|
||||
.filter(p => p.lang === this.options.lang)[0].text || holiday.name[0].text,
|
||||
date: makeDate(holiday.date),
|
||||
type: formatType(holiday.holidayType),
|
||||
observed: holiday.observedOn ? makeDate(holiday.observedOn) : '',
|
||||
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
},
|
||||
provider() {
|
||||
// Can be either `ip-api`, `ipapi.co` or `ipgeolocation`
|
||||
return this.options.provider || 'ipapi.co';
|
||||
return this.parseAsEnvVar(this.options.provider) || 'ipapi.co';
|
||||
},
|
||||
},
|
||||
data() {
|
||||
|
||||
@@ -51,7 +51,7 @@ export default {
|
||||
return this.options.rssUrl || '';
|
||||
},
|
||||
apiKey() {
|
||||
return this.options.apiKey;
|
||||
return this.parseAsEnvVar(this.options.apiKey);
|
||||
},
|
||||
parseLocally() {
|
||||
return this.options.parseLocally;
|
||||
|
||||
@@ -93,7 +93,7 @@ export default {
|
||||
return this.options.leagueId;
|
||||
},
|
||||
apiKey() {
|
||||
return this.options.apiKey || '50130162';
|
||||
return this.parseAsEnvVar(this.options.apiKey) || '50130162';
|
||||
},
|
||||
limit() {
|
||||
return this.options.limit || 20;
|
||||
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
},
|
||||
/* The users API key for AlphaVantage */
|
||||
apiKey() {
|
||||
return this.options.apiKey;
|
||||
return this.parseAsEnvVar(this.options.apiKey);
|
||||
},
|
||||
/* The formatted GET request API endpoint to fetch stock data from */
|
||||
endpoint() {
|
||||
|
||||
@@ -45,15 +45,15 @@ export default {
|
||||
computed: {
|
||||
hostname() {
|
||||
if (!this.options.hostname) this.error('A hostname is required');
|
||||
return this.options.hostname;
|
||||
return this.parseAsEnvVar(this.options.hostname);
|
||||
},
|
||||
username() {
|
||||
if (!this.options.username) this.error('A username is required');
|
||||
return this.options.username;
|
||||
return this.parseAsEnvVar(this.options.username);
|
||||
},
|
||||
password() {
|
||||
if (!this.options.password) this.error('A password is required');
|
||||
return this.options.password;
|
||||
return this.parseAsEnvVar(this.options.password);
|
||||
},
|
||||
endpointLogin() {
|
||||
return `${this.hostname}/webapi/auth.cgi?api=SYNO.API.Auth&version=3&method=login&account=${this.username}&passwd=${this.password}&session=DownloadStation&format=sid`;
|
||||
|
||||
238
src/components/Widgets/UptimeKuma.vue
Normal file
238
src/components/Widgets/UptimeKuma.vue
Normal file
@@ -0,0 +1,238 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="monitors">
|
||||
<div v-for="(monitor, index) in monitors" :key="index" class="item-wrapper">
|
||||
<div class="item monitor-row">
|
||||
<div class="title-title"><span class="text">{{ monitor.name }}</span></div>
|
||||
<div class="monitors-container">
|
||||
<div class="status-container">
|
||||
<span class="status-pill" :class="[monitor.statusClass]">{{ monitor.status }}</span>
|
||||
</div>
|
||||
<div class="status-container">
|
||||
<span class="response-time">{{ monitor.responseTime }}ms</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="errorMessage">
|
||||
<div class="error-message">
|
||||
<span class="text">{{ errorMessage }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* A simple example which you can use as a template for creating your own widget.
|
||||
* Takes two optional parameters (`text` and `count`), and fetches a list of images
|
||||
* from dummyapis.com, then renders the results to the UI.
|
||||
*/
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
|
||||
export default {
|
||||
mixins: [WidgetMixin],
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
monitors: null,
|
||||
errorMessage: null,
|
||||
errorMessageConstants: {
|
||||
missingApiKey: 'No API key set',
|
||||
missingUrl: 'No URL set',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.fetchData();
|
||||
},
|
||||
computed: {
|
||||
/* Get API key for access to instance */
|
||||
apiKey() {
|
||||
return this.parseAsEnvVar(this.options.apiKey);
|
||||
},
|
||||
/* Get instance URL */
|
||||
url() {
|
||||
return this.parseAsEnvVar(this.options.url);
|
||||
},
|
||||
/* Create authorisation header for the instance from the apiKey */
|
||||
authHeaders() {
|
||||
if (!this.options.apiKey) {
|
||||
return {};
|
||||
}
|
||||
const encoded = window.btoa(`:${this.options.apiKey}`);
|
||||
return { Authorization: `Basic ${encoded}` };
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/* The update() method extends mixin, used to update the data.
|
||||
* It's called by parent component, when the user presses update
|
||||
*/
|
||||
update() {
|
||||
this.startLoading();
|
||||
this.fetchData();
|
||||
},
|
||||
/* Make the data request to the computed API endpoint */
|
||||
fetchData() {
|
||||
const { authHeaders, url } = this;
|
||||
|
||||
if (!this.optionsValid({ authHeaders, url })) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.makeRequest(url, authHeaders)
|
||||
.then(this.processData);
|
||||
},
|
||||
/* Convert API response data into a format to be consumed by the UI */
|
||||
processData(response) {
|
||||
const monitorRows = this.getMonitorRows(response);
|
||||
|
||||
const monitors = new Map();
|
||||
|
||||
for (let index = 0; index < monitorRows.length; index += 1) {
|
||||
const row = monitorRows[index];
|
||||
this.processRow(row, monitors);
|
||||
}
|
||||
|
||||
this.monitors = Array.from(monitors.values());
|
||||
},
|
||||
getMonitorRows(response) {
|
||||
return response.split('\n').filter(row => row.startsWith('monitor_'));
|
||||
},
|
||||
processRow(row, monitors) {
|
||||
const dataType = this.getRowDataType(row);
|
||||
const monitorName = this.getRowMonitorName(row);
|
||||
|
||||
if (!monitors.has(monitorName)) {
|
||||
monitors.set(monitorName, { name: monitorName });
|
||||
}
|
||||
|
||||
const monitor = monitors.get(monitorName);
|
||||
const value = this.getRowValue(row);
|
||||
|
||||
const updated = this.setMonitorValue(dataType, monitor, value);
|
||||
|
||||
monitors.set(monitorName, updated);
|
||||
},
|
||||
setMonitorValue(key, monitor, value) {
|
||||
const copy = { ...monitor };
|
||||
switch (key) {
|
||||
case 'monitor_cert_days_remaining': {
|
||||
copy.certDaysRemaining = value;
|
||||
break;
|
||||
}
|
||||
case 'monitor_cert_is_valid': {
|
||||
copy.certValid = value;
|
||||
break;
|
||||
}
|
||||
case 'monitor_response_time': {
|
||||
copy.responseTime = value;
|
||||
break;
|
||||
}
|
||||
case 'monitor_status': {
|
||||
copy.status = value === '1' ? 'Up' : 'Down';
|
||||
copy.statusClass = copy.status.toLowerCase();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return copy;
|
||||
},
|
||||
getRowValue(row) {
|
||||
return this.getValueWithRegex(row, /\b\d+\b$/);
|
||||
},
|
||||
getRowMonitorName(row) {
|
||||
return this.getValueWithRegex(row, /monitor_name="([^"]+)"/);
|
||||
},
|
||||
getRowDataType(row) {
|
||||
return this.getValueWithRegex(row, /^(.*?)\{/);
|
||||
},
|
||||
getValueWithRegex(string, regex) {
|
||||
const result = string.match(regex);
|
||||
|
||||
const isArray = Array.isArray(result);
|
||||
|
||||
if (!isArray) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return result.length > 1 ? result[1] : result[0];
|
||||
},
|
||||
optionsValid({ url, authHeaders }) {
|
||||
const errors = [];
|
||||
if (url === undefined) {
|
||||
errors.push(this.errorMessageConstants.missingUrl);
|
||||
}
|
||||
|
||||
if (authHeaders === undefined) {
|
||||
errors.push(this.errorMessageConstants.missingApiKey);
|
||||
}
|
||||
|
||||
if (errors.length === 0) { return true; }
|
||||
|
||||
this.errorMessage = errors.join('\n');
|
||||
return false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.status-pill {
|
||||
border-radius: 50em;
|
||||
box-sizing: border-box;
|
||||
font-size: 0.75em;
|
||||
display: inline-block;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
padding: .35em .65em;
|
||||
margin: 1em 0.5em;
|
||||
min-width: 64px;
|
||||
|
||||
&.up {
|
||||
background-color: rgb(92, 221, 139);
|
||||
color: black;
|
||||
}
|
||||
|
||||
&.down {
|
||||
background-color: rgb(220, 53, 69);
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
div.item.monitor-row:hover {
|
||||
background-color: var(--item-background);
|
||||
color: var(--current-color);
|
||||
opacity: 1;
|
||||
|
||||
div.title-title>span.text {
|
||||
color: var(--current-color);
|
||||
}
|
||||
}
|
||||
|
||||
.monitors-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.monitor-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0.35em 0.5em;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
},
|
||||
address() {
|
||||
if (!this.options.address) this.error('You must specify a public address');
|
||||
return this.options.address;
|
||||
return this.parseAsEnvVar(this.options.address);
|
||||
},
|
||||
network() {
|
||||
return this.options.network || 'main';
|
||||
|
||||
@@ -46,8 +46,12 @@ export default {
|
||||
return this.options.units || 'metric';
|
||||
},
|
||||
endpoint() {
|
||||
const { apiKey, city } = this.options;
|
||||
return `${widgetApiEndpoints.weather}?q=${city}&appid=${apiKey}&units=${this.units}`;
|
||||
const apiKey = this.parseAsEnvVar(this.options.apiKey);
|
||||
const { city, lat, lon } = this.options;
|
||||
const params = (lat && lon)
|
||||
? `lat=${lat}&lon=${lon}&appid=${apiKey}&units=${this.units}`
|
||||
: `q=${city}&appid=${apiKey}&units=${this.units}`;
|
||||
return `${widgetApiEndpoints.weather}?${params}`;
|
||||
},
|
||||
tempDisplayUnits() {
|
||||
switch (this.units) {
|
||||
@@ -106,7 +110,11 @@ export default {
|
||||
checkProps() {
|
||||
const ops = this.options;
|
||||
if (!ops.apiKey) this.error('Missing API key for OpenWeatherMap');
|
||||
if (!ops.city) this.error('A city name is required to fetch weather');
|
||||
|
||||
if ((!ops.lat || !ops.lon) && !ops.city) {
|
||||
this.error('A city name or lat + lon is required to fetch weather');
|
||||
}
|
||||
|
||||
if (ops.units && ops.units !== 'metric' && ops.units !== 'imperial') {
|
||||
this.error('Invalid units specified, must be either \'metric\' or \'imperial\'');
|
||||
}
|
||||
|
||||
@@ -67,12 +67,14 @@ const COMPAT = {
|
||||
'gl-alerts': 'GlAlerts',
|
||||
'gl-current-cores': 'GlCpuCores',
|
||||
'gl-current-cpu': 'GlCpuGauge',
|
||||
'gl-cpu-speedometer': 'GlCpuSpeedometer',
|
||||
'gl-cpu-history': 'GlCpuHistory',
|
||||
'gl-disk-io': 'GlDiskIo',
|
||||
'gl-disk-space': 'GlDiskSpace',
|
||||
'gl-ip-address': 'GlIpAddress',
|
||||
'gl-load-history': 'GlLoadHistory',
|
||||
'gl-current-mem': 'GlMemGauge',
|
||||
'gl-mem-speedometer': 'GlMemSpeedometer',
|
||||
'gl-mem-history': 'GlMemHistory',
|
||||
'gl-network-interfaces': 'GlNetworkInterfaces',
|
||||
'gl-network-traffic': 'GlNetworkTraffic',
|
||||
@@ -113,6 +115,7 @@ const COMPAT = {
|
||||
'synology-download': 'SynologyDownload',
|
||||
'system-info': 'SystemInfo',
|
||||
'tfl-status': 'TflStatus',
|
||||
'uptime-kuma': 'UptimeKuma',
|
||||
'wallet-balance': 'WalletBalance',
|
||||
weather: 'Weather',
|
||||
'weather-forecast': 'WeatherForecast',
|
||||
@@ -203,14 +206,16 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '@/styles/media-queries.scss';
|
||||
@import "@/styles/media-queries.scss";
|
||||
|
||||
.widget-base {
|
||||
position: relative;
|
||||
padding: 0.75rem 0.5rem 0.5rem 0.5rem;
|
||||
background: var(--widget-base-background);
|
||||
box-shadow: var(--widget-base-shadow, none);
|
||||
|
||||
// Refresh and full-page action buttons
|
||||
button.action-btn {
|
||||
button.action-btn {
|
||||
height: 1rem;
|
||||
min-width: auto;
|
||||
width: 1.75rem;
|
||||
@@ -221,21 +226,26 @@ export default {
|
||||
border: none;
|
||||
opacity: var(--dimming-factor);
|
||||
color: var(--widget-text-color);
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: var(--widget-background-color);
|
||||
}
|
||||
|
||||
&.update-btn {
|
||||
right: -0.25rem;
|
||||
}
|
||||
|
||||
&.open-btn {
|
||||
right: 1.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
// Optional widget label
|
||||
.widget-label {
|
||||
color: var(--widget-text-color);
|
||||
}
|
||||
|
||||
// Actual widget container
|
||||
.widget-wrap {
|
||||
&.has-error {
|
||||
@@ -243,9 +253,11 @@ export default {
|
||||
opacity: 0.5;
|
||||
border-radius: var(--curve-factor);
|
||||
background: #ffff0040;
|
||||
|
||||
&:hover { background: none; }
|
||||
}
|
||||
}
|
||||
|
||||
// Error message output
|
||||
.widget-error {
|
||||
p.error-msg {
|
||||
@@ -254,12 +266,14 @@ export default {
|
||||
font-size: 1rem;
|
||||
margin: 0 auto 0.5rem auto;
|
||||
}
|
||||
|
||||
p.error-output {
|
||||
font-family: var(--font-monospace);
|
||||
color: var(--widget-text-color);
|
||||
font-size: 0.85rem;
|
||||
margin: 0.5rem auto;
|
||||
}
|
||||
|
||||
p.retry-link {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
@@ -268,14 +282,17 @@ export default {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Loading spinner
|
||||
.loading {
|
||||
margin: 0.2rem auto;
|
||||
text-align: center;
|
||||
|
||||
svg.loader {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide widget contents while loading
|
||||
&.is-loading {
|
||||
.widget-wrap {
|
||||
@@ -283,5 +300,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||
|
||||
@@ -41,11 +40,17 @@ export default {
|
||||
methods: {
|
||||
/* Make GET request to CoinGecko API endpoint */
|
||||
fetchData() {
|
||||
axios.get(this.endpoint)
|
||||
.then((response) => {
|
||||
this.processData(response.data);
|
||||
fetch(this.endpoint)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
this.error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.catch((dataFetchError) => {
|
||||
.then(data => {
|
||||
this.processData(data);
|
||||
})
|
||||
.catch(dataFetchError => {
|
||||
this.error('Unable to fetch data', dataFetchError);
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -71,7 +76,7 @@ export default {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.xkcd-wrapper {
|
||||
.xkcd-title {
|
||||
.xkcd-title {
|
||||
font-size: 1.2rem;
|
||||
margin: 0.25rem auto;
|
||||
color: var(--widget-text-color);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* A Vue directive to trigger an event when the user
|
||||
* clicks anywhere other than the specified elements
|
||||
* Used to close context menus popup modals and tips
|
||||
* Dashy: Licensed under MIT - (C) Alicia Sykes 2022
|
||||
* Dashy: Licensed under MIT - (C) Alicia Sykes 2024
|
||||
*/
|
||||
|
||||
const instances = []; // List of click event instances
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* A Vue directive to call event when element is long-pressed
|
||||
* Used to open context menus on touch-enabled devices
|
||||
* Inspired by: FeliciousX/vue-directive-long-press
|
||||
* Dashy: Licensed under MIT - (C) Alicia Sykes 2022
|
||||
* Dashy: Licensed under MIT - (C) Alicia Sykes 2024
|
||||
*/
|
||||
|
||||
const LONG_PRESS_DEFAULT_DELAY = 750;
|
||||
|
||||
30
src/main.js
30
src/main.js
@@ -13,14 +13,17 @@ import TreeView from 'vue-json-tree-view';
|
||||
|
||||
// Import base Dashy components and utils
|
||||
import Dashy from '@/App.vue'; // Main Dashy Vue app
|
||||
import router from '@/router'; // Router, for navigation
|
||||
import store from '@/store'; // Store, for local state management
|
||||
import router from '@/router'; // Router, for navigation
|
||||
import serviceWorker from '@/utils/InitServiceWorker'; // Service worker initialization
|
||||
import { messages } from '@/utils/languages'; // Language texts
|
||||
import ErrorReporting from '@/utils/ErrorReporting'; // Error reporting initializer (off)
|
||||
import clickOutside from '@/directives/ClickOutside'; // Directive for closing popups, modals, etc
|
||||
import { toastedOptions, tooltipOptions, language as defaultLanguage } from '@/utils/defaults';
|
||||
import { initKeycloakAuth, isKeycloakEnabled } from '@/utils/KeycloakAuth';
|
||||
import { initHeaderAuth, isHeaderAuthEnabled } from '@/utils/HeaderAuth';
|
||||
import Keys from '@/utils/StoreMutations';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
|
||||
// Initialize global Vue components
|
||||
Vue.use(VueI18n);
|
||||
@@ -58,11 +61,20 @@ const mount = () => new Vue({
|
||||
store, router, render, i18n,
|
||||
}).$mount('#app');
|
||||
|
||||
// If Keycloak not enabled, then proceed straight to the app
|
||||
if (!isKeycloakEnabled()) {
|
||||
mount();
|
||||
} else { // Keycloak is enabled, redirect to KC login page
|
||||
initKeycloakAuth()
|
||||
.then(() => mount())
|
||||
.catch(() => window.location.reload());
|
||||
}
|
||||
store.dispatch(Keys.INITIALIZE_CONFIG).then(() => {
|
||||
if (isKeycloakEnabled()) { // If Keycloak is enabled, initialize auth
|
||||
initKeycloakAuth()
|
||||
.then(() => mount())
|
||||
.catch((e) => {
|
||||
ErrorHandler('Failed to authenticate with Keycloak', e);
|
||||
});
|
||||
} else if (isHeaderAuthEnabled()) { // If header auth is enabled, initialize auth
|
||||
initHeaderAuth()
|
||||
.then(() => mount())
|
||||
.catch((e) => {
|
||||
ErrorHandler('Failed to authenticate with server', e);
|
||||
});
|
||||
} else { // If no third-party auth, just mount the app as normal
|
||||
mount();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -21,12 +21,22 @@ export default {
|
||||
return;
|
||||
}
|
||||
// 1. Get the config, and strip appConfig if is sub-page
|
||||
const isSubPag = !!this.$store.state.currentConfigInfo;
|
||||
const isSubPag = !!this.$store.state.currentConfigInfo.confId;
|
||||
const jsonConfig = config;
|
||||
if (isSubPag) delete jsonConfig.appConfig;
|
||||
jsonConfig.sections = jsonConfig.sections.map(({ filteredItems, ...section }) => section);
|
||||
// If a sub-config, then remove appConfig, and check path isn't an external URL
|
||||
if (isSubPag) {
|
||||
delete jsonConfig.appConfig;
|
||||
if (this.$store.state.currentConfigInfo.confPath.includes('http')) {
|
||||
ErrorHandler('Cannot save to an external URL');
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 2. Convert JSON into YAML
|
||||
const yamlOptions = {};
|
||||
const yaml = jsYaml.dump(jsonConfig, yamlOptions);
|
||||
const strjsonConfig = JSON.stringify(jsonConfig);
|
||||
const jsonObj = JSON.parse(strjsonConfig);
|
||||
const yaml = jsYaml.dump(jsonObj, yamlOptions);
|
||||
// 3. Prepare the request
|
||||
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
|
||||
const endpoint = `${baseUrl}${serviceEndpoints.save}`;
|
||||
@@ -63,20 +73,39 @@ export default {
|
||||
ErrorHandler('Unable to save changes locally, this feature has been disabled');
|
||||
return;
|
||||
}
|
||||
localStorage.setItem(localStorageKeys.CONF_SECTIONS, JSON.stringify(config.sections));
|
||||
localStorage.setItem(localStorageKeys.PAGE_INFO, JSON.stringify(config.pageInfo));
|
||||
localStorage.setItem(localStorageKeys.APP_CONFIG, JSON.stringify(config.appConfig));
|
||||
|
||||
const isSubPag = !!this.$store.state.currentConfigInfo.confId;
|
||||
if (isSubPag) { // Save for sub-page only
|
||||
const configId = this.$store.state.currentConfigInfo.confId;
|
||||
const localStorageKeySections = `${localStorageKeys.CONF_SECTIONS}-${configId}`;
|
||||
const localStorageKeyPageInfo = `${localStorageKeys.PAGE_INFO}-${configId}`;
|
||||
localStorage.setItem(localStorageKeySections, JSON.stringify(config.sections));
|
||||
localStorage.setItem(localStorageKeyPageInfo, JSON.stringify(config.pageInfo));
|
||||
} else { // Or save to main config
|
||||
localStorage.setItem(localStorageKeys.CONF_SECTIONS, JSON.stringify(config.sections));
|
||||
localStorage.setItem(localStorageKeys.PAGE_INFO, JSON.stringify(config.pageInfo));
|
||||
localStorage.setItem(localStorageKeys.APP_CONFIG, JSON.stringify(config.appConfig));
|
||||
}
|
||||
|
||||
if (config.appConfig.theme) {
|
||||
localStorage.setItem(localStorageKeys.THEME, config.appConfig.theme);
|
||||
}
|
||||
InfoHandler('Config has succesfully been saved in browser storage', 'Config Update');
|
||||
InfoHandler('Config has successfully been saved in browser storage', 'Config Update');
|
||||
this.showToast(this.$t('config-editor.success-msg-local'), true);
|
||||
this.$store.commit(StoreKeys.SET_EDIT_MODE, false);
|
||||
},
|
||||
carefullyClearLocalStorage() {
|
||||
// Delete the main keys
|
||||
localStorage.removeItem(localStorageKeys.PAGE_INFO);
|
||||
localStorage.removeItem(localStorageKeys.APP_CONFIG);
|
||||
localStorage.removeItem(localStorageKeys.CONF_SECTIONS);
|
||||
// Then, if we've got any sub-pages, delete those too
|
||||
(this.$store.getters.pages || []).forEach((page) => {
|
||||
const localStorageKeySections = `${localStorageKeys.CONF_SECTIONS}-${page.id}`;
|
||||
const localStorageKeyPageInfo = `${localStorageKeys.PAGE_INFO}-${page.id}`;
|
||||
localStorage.removeItem(localStorageKeySections);
|
||||
localStorage.removeItem(localStorageKeyPageInfo);
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,7 +6,6 @@ import Defaults, { localStorageKeys, iconCdns } from '@/utils/defaults';
|
||||
import Keys from '@/utils/StoreMutations';
|
||||
import { searchTiles } from '@/utils/Search';
|
||||
import { checkItemVisibility } from '@/utils/CheckItemVisibility';
|
||||
import { GetTheme, ApplyLocalTheme, ApplyCustomVariables } from '@/utils/ThemeHelper';
|
||||
|
||||
const HomeMixin = {
|
||||
props: {
|
||||
@@ -29,29 +28,40 @@ const HomeMixin = {
|
||||
return this.$store.state.modalOpen;
|
||||
},
|
||||
pageId() {
|
||||
return (this.subPageInfo && this.subPageInfo.pageId) ? this.subPageInfo.pageId : 'home';
|
||||
return this.$store.state.currentConfigInfo?.confId || 'home';
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
searchValue: '',
|
||||
}),
|
||||
async mounted() {
|
||||
await this.getConfigForRoute();
|
||||
},
|
||||
watch: {
|
||||
async $route() {
|
||||
await this.getConfigForRoute();
|
||||
this.setTheme();
|
||||
this.loadUpConfig();
|
||||
},
|
||||
pageInfo: {
|
||||
handler(newPageInfo) {
|
||||
if (newPageInfo && newPageInfo.title) {
|
||||
document.title = newPageInfo.title;
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
async created() {
|
||||
this.loadUpConfig();
|
||||
},
|
||||
methods: {
|
||||
async getConfigForRoute() {
|
||||
this.$store.commit(Keys.SET_CURRENT_SUB_PAGE, this.subPageInfo);
|
||||
if (this.subPageInfo && this.subPageInfo.confPath) { // Get config for sub-page
|
||||
await this.$store.dispatch(Keys.INITIALIZE_MULTI_PAGE_CONFIG, this.subPageInfo.confPath);
|
||||
} else { // Otherwise, use main config
|
||||
this.$store.commit(Keys.USE_MAIN_CONFIG);
|
||||
}
|
||||
/* When page loaded / sub-page changed, initiate config fetch */
|
||||
async loadUpConfig() {
|
||||
const subPage = this.determineConfigFile();
|
||||
await this.$store.dispatch(Keys.INITIALIZE_CONFIG, subPage);
|
||||
},
|
||||
/* Based on the current route, get which config to display, null will use default */
|
||||
determineConfigFile() {
|
||||
const pagePath = this.$router.currentRoute.path;
|
||||
const isSubPage = new RegExp((/(home|workspace|minimal)\/[a-zA-Z0-9-]+/g)).test(pagePath);
|
||||
const subPageName = isSubPage ? pagePath.split('/').pop() : null;
|
||||
return subPageName;
|
||||
},
|
||||
/* TEMPORARY: If on sub-page, check if custom theme is set and return it */
|
||||
getSubPageTheme() {
|
||||
@@ -63,9 +73,9 @@ const HomeMixin = {
|
||||
}
|
||||
},
|
||||
setTheme() {
|
||||
const theme = this.getSubPageTheme() || GetTheme();
|
||||
ApplyLocalTheme(theme);
|
||||
ApplyCustomVariables(theme);
|
||||
// const theme = this.getSubPageTheme() || GetTheme();
|
||||
// ApplyLocalTheme(theme);
|
||||
// ApplyCustomVariables(theme);
|
||||
},
|
||||
updateModalVisibility(modalState) {
|
||||
this.$store.commit('SET_MODAL_OPEN', modalState);
|
||||
@@ -74,6 +84,14 @@ const HomeMixin = {
|
||||
searching(searchValue) {
|
||||
this.searchValue = searchValue || '';
|
||||
},
|
||||
/* Returns a unique ID based on the page and section name */
|
||||
makeSectionId(section) {
|
||||
const normalize = (str) => (
|
||||
str ? str.trim().toLowerCase().replace(/[^a-zA-Z0-9]/g, '-')
|
||||
: `unnamed-${(`000${Math.floor(Math.random() * 1000)}`).slice(-3)}`
|
||||
);
|
||||
return `${this.pageId || 'unknown-page'}-${normalize(section.name)}`;
|
||||
},
|
||||
/* Returns true if there is one or more sections in the config */
|
||||
checkTheresData(sections) {
|
||||
const localSections = localStorage[localStorageKeys.CONF_SECTIONS];
|
||||
|
||||
143
src/mixins/ThemingMixin.js
Normal file
143
src/mixins/ThemingMixin.js
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* This mixin can be extended by any component or view which needs to manage themes
|
||||
* It handles fetching and applying themes from the store, updating themes,
|
||||
* applying custom CSS variables and loading external stylesheets.
|
||||
* */
|
||||
|
||||
import Keys from '@/utils/StoreMutations';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { builtInThemes, localStorageKeys, mainCssVars } from '@/utils/defaults';
|
||||
|
||||
const ThemingMixin = {
|
||||
data: () => ({
|
||||
selectedTheme: '', // Used only to bind current them to theme dropdown
|
||||
}),
|
||||
computed: {
|
||||
/* This is the theme from the central store. When it changes, the UI will update */
|
||||
themeFromStore() {
|
||||
return this.$store.getters.theme;
|
||||
},
|
||||
appConfig() {
|
||||
return this.$store.getters.appConfig;
|
||||
},
|
||||
/* Any extra user-defined themes, to add to dropdown */
|
||||
extraThemeNames() {
|
||||
const userThemes = this.appConfig?.cssThemes || [];
|
||||
if (typeof userThemes === 'string') return [userThemes];
|
||||
return userThemes;
|
||||
},
|
||||
/* If user specified external stylesheet(s), format and return */
|
||||
externalThemes() {
|
||||
const availableThemes = {};
|
||||
if (this.appConfig?.externalStyleSheet) {
|
||||
const externals = this.appConfig.externalStyleSheet;
|
||||
if (Array.isArray(externals)) {
|
||||
externals.forEach((ext, i) => {
|
||||
availableThemes[`External Stylesheet ${i + 1}`] = ext;
|
||||
});
|
||||
} else if (typeof externals === 'string') {
|
||||
availableThemes['External Stylesheet'] = this.appConfig.externalStyleSheet;
|
||||
} else {
|
||||
ErrorHandler('External stylesheets must be of type string or string[]');
|
||||
}
|
||||
}
|
||||
return availableThemes;
|
||||
},
|
||||
/* Combines all theme names for dropdown (built-in, user-defined and stylesheets) */
|
||||
themeNames() {
|
||||
const externalThemeNames = Object.keys(this.externalThemes);
|
||||
return [...this.extraThemeNames, ...externalThemeNames, ...builtInThemes];
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
/* When theme in VueX store changes, then update theme */
|
||||
themeFromStore(newTheme) {
|
||||
if (newTheme) {
|
||||
this.resetToDefault();
|
||||
this.selectedTheme = newTheme;
|
||||
this.updateTheme(newTheme);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/* Called when user changes theme through the UI
|
||||
* Updates store, which will in turn update theme through watcher
|
||||
*/
|
||||
themeChangedInUI() {
|
||||
this.$store.commit(Keys.SET_THEME, this.selectedTheme); // Update store
|
||||
this.updateTheme(this.selectedTheme); // Apply theme to UI
|
||||
},
|
||||
/**
|
||||
* Gets any custom styles the user has applied, wither from local storage, or from the config
|
||||
* @returns {object} An array of objects, one for each theme, containing kvps for variables
|
||||
*/
|
||||
getCustomColors() {
|
||||
const localColors = JSON.parse(localStorage[localStorageKeys.CUSTOM_COLORS] || '{}');
|
||||
const configColors = this.appConfig.customColors || {};
|
||||
return Object.assign(configColors, localColors);
|
||||
},
|
||||
/* Gets user custom color preferences for current theme, and applies to DOM */
|
||||
applyCustomVariables(theme) {
|
||||
mainCssVars.forEach((vName) => { document.documentElement.style.removeProperty(`--${vName}`); });
|
||||
const themeColors = this.getCustomColors()[theme];
|
||||
if (themeColors) {
|
||||
Object.keys(themeColors).forEach((customVar) => {
|
||||
document.documentElement.style.setProperty(`--${customVar}`, themeColors[customVar]);
|
||||
});
|
||||
}
|
||||
},
|
||||
/* Sets the theme, by updating data-theme attribute on the html tag */
|
||||
applyLocalTheme(newTheme) {
|
||||
const htmlTag = document.getElementsByTagName('html')[0];
|
||||
if (htmlTag.hasAttribute('data-theme')) htmlTag.removeAttribute('data-theme');
|
||||
htmlTag.setAttribute('data-theme', newTheme);
|
||||
},
|
||||
/* If using an external stylesheet, load it in */
|
||||
applyRemoteTheme(href) {
|
||||
this.resetToDefault();
|
||||
const element = document.createElement('link');
|
||||
element.setAttribute('rel', 'stylesheet');
|
||||
element.setAttribute('type', 'text/css');
|
||||
element.setAttribute('id', 'user-defined-stylesheet');
|
||||
element.setAttribute('href', href);
|
||||
document.getElementsByTagName('head')[0].appendChild(element);
|
||||
},
|
||||
/* Determines if a given theme is local / not a custom user stylesheet */
|
||||
isThemeLocal(themeToCheck) {
|
||||
const localThemes = [...builtInThemes, ...this.extraThemeNames];
|
||||
return localThemes.includes(themeToCheck);
|
||||
},
|
||||
/* Updates theme. Checks if the new theme is local or external,
|
||||
and calls appropriate updating function. Updates local storage */
|
||||
updateTheme(newTheme) {
|
||||
if (newTheme.toLowerCase() === 'default') {
|
||||
this.resetToDefault();
|
||||
} else if (this.isThemeLocal(newTheme)) {
|
||||
this.applyLocalTheme(newTheme);
|
||||
} else if (this.externalThemes[newTheme]) {
|
||||
this.applyRemoteTheme(this.externalThemes[newTheme]);
|
||||
}
|
||||
this.applyCustomVariables(newTheme);
|
||||
},
|
||||
/* Removes any applied themes, and deletes any externally loaded stylesheets */
|
||||
resetToDefault() {
|
||||
const externalStyles = document.getElementById('user-defined-stylesheet');
|
||||
if (externalStyles) document.getElementsByTagName('head')[0].removeChild(externalStyles);
|
||||
document.getElementsByTagName('html')[0].removeAttribute('data-theme');
|
||||
},
|
||||
/* Call within mounted hook within a page to apply the correct theme */
|
||||
initializeTheme() {
|
||||
const initialTheme = this.themeFromStore;
|
||||
this.selectedTheme = initialTheme;
|
||||
const hasExternal = this.externalThemes && Object.entries(this.externalThemes).length > 0;
|
||||
|
||||
if (this.isThemeLocal(initialTheme)) {
|
||||
this.updateTheme(initialTheme);
|
||||
} else if (hasExternal) {
|
||||
this.applyRemoteTheme(this.externalThemes[initialTheme]);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default ThemingMixin;
|
||||
@@ -2,7 +2,6 @@
|
||||
* Mixin that all pre-built and custom widgets extend from.
|
||||
* Manages loading state, error handling, data updates and user options
|
||||
*/
|
||||
import axios from 'axios';
|
||||
import { Progress } from 'rsup-progress';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { serviceEndpoints } from '@/utils/defaults';
|
||||
@@ -106,31 +105,76 @@ const WidgetMixin = {
|
||||
const method = protocol || 'GET';
|
||||
const url = this.useProxy ? this.proxyReqEndpoint : endpoint;
|
||||
const data = JSON.stringify(body || {});
|
||||
const CustomHeaders = options || null;
|
||||
const headers = this.useProxy
|
||||
? { 'Target-URL': endpoint, CustomHeaders: JSON.stringify(CustomHeaders) } : CustomHeaders;
|
||||
|
||||
const CustomHeaders = options || {};
|
||||
const headers = new Headers();
|
||||
|
||||
// If using a proxy, set the 'Target-URL' header
|
||||
if (this.useProxy) {
|
||||
headers.append('Target-URL', endpoint);
|
||||
}
|
||||
// Apply widget-specific custom headers
|
||||
Object.entries(CustomHeaders).forEach(([key, value]) => {
|
||||
headers.append(key, value);
|
||||
});
|
||||
|
||||
// If the request is a GET, delete the body
|
||||
const bodyContent = method.toUpperCase() === 'GET' ? undefined : data;
|
||||
|
||||
const timeout = this.options.timeout || this.defaultTimeout;
|
||||
|
||||
// Setup Fetch request configuration
|
||||
const requestConfig = {
|
||||
method, url, headers, data, timeout,
|
||||
method,
|
||||
headers,
|
||||
body: bodyContent,
|
||||
signal: undefined, // This will be set below
|
||||
};
|
||||
// Make request
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
requestConfig.signal = controller.signal;
|
||||
|
||||
// Make request using Fetch API
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.request(requestConfig)
|
||||
.then((response) => {
|
||||
if (response.data.success === false) {
|
||||
this.error('Proxy returned error from target server', response.data.message);
|
||||
fetch(url, requestConfig)
|
||||
.then(async response => {
|
||||
const responseData = await response.json();
|
||||
if (responseData.error) {
|
||||
this.error('Proxy returned error from target server', responseData.error?.message);
|
||||
}
|
||||
resolve(response.data);
|
||||
if (responseData.success === false) {
|
||||
this.error('Proxy didn\'t return success from target server', responseData.message);
|
||||
}
|
||||
resolve(responseData);
|
||||
})
|
||||
.catch((dataFetchError) => {
|
||||
this.error('Unable to fetch data', dataFetchError);
|
||||
reject(dataFetchError);
|
||||
.catch(error => {
|
||||
if (error.name === 'AbortError') {
|
||||
this.error('Request timed out', error);
|
||||
} else {
|
||||
this.error('Unable to fetch data', error);
|
||||
}
|
||||
reject(error);
|
||||
})
|
||||
.finally(() => {
|
||||
clearTimeout(timeoutId);
|
||||
this.finishLoading();
|
||||
});
|
||||
});
|
||||
},
|
||||
/* Check if a value is an environment variable, return its value if so. */
|
||||
parseAsEnvVar(str) {
|
||||
if (typeof str !== 'string') return str;
|
||||
if (str.includes('VUE_APP_')) {
|
||||
const envVar = process.env[str];
|
||||
if (!envVar) {
|
||||
this.error(`Environment variable ${str} not found`);
|
||||
} else {
|
||||
return envVar;
|
||||
}
|
||||
}
|
||||
return str;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -14,23 +14,9 @@ import Home from '@/views/Home.vue';
|
||||
|
||||
// Import helper functions, config data and defaults
|
||||
import { isAuthEnabled, isLoggedIn, isGuestAccessEnabled } from '@/utils/Auth';
|
||||
import { makePageSlug, makePageName } from '@/utils/ConfigHelpers';
|
||||
import { metaTagData, startingView, routePaths } from '@/utils/defaults';
|
||||
import { metaTagData, startingView as defaultStartingView, routePaths } from '@/utils/defaults';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
|
||||
// Import data from users conf file. Note that rebuild is required for this to update.
|
||||
import conf from '../public/conf.yml';
|
||||
|
||||
if (!conf) {
|
||||
ErrorHandler('You\'ve not got any data in your config file yet.');
|
||||
}
|
||||
|
||||
// Assign top-level config fields, check not null
|
||||
const config = conf || {};
|
||||
const pages = config.pages || [];
|
||||
const pageInfo = config.pageInfo || {};
|
||||
const appConfig = config.appConfig || {};
|
||||
|
||||
Vue.use(Router);
|
||||
const progress = new Progress({ color: 'var(--progress-bar)' });
|
||||
|
||||
@@ -42,16 +28,15 @@ const isAuthenticated = () => {
|
||||
return (!authEnabled || userLoggedIn || guestEnabled);
|
||||
};
|
||||
|
||||
/* Get the users chosen starting view from app config, or return default */
|
||||
const getStartingView = () => appConfig.startingView || startingView;
|
||||
// Get the default starting view from environmental variable
|
||||
const startingView = process.env.VUE_APP_STARTING_VIEW || defaultStartingView;
|
||||
|
||||
/**
|
||||
* Returns the component that should be rendered at the base path,
|
||||
* Defaults to Home, but the user can change this to Workspace of Minimal
|
||||
*/
|
||||
const getStartingComponent = () => {
|
||||
const usersPreference = getStartingView();
|
||||
switch (usersPreference) {
|
||||
switch (startingView) {
|
||||
case 'minimal': return () => import('./views/Minimal.vue');
|
||||
case 'workspace': return () => import('./views/Workspace.vue');
|
||||
default: return Home;
|
||||
@@ -59,71 +44,23 @@ const getStartingComponent = () => {
|
||||
};
|
||||
|
||||
/* Returns the meta tags for each route */
|
||||
const makeMetaTags = (defaultTitle) => ({
|
||||
title: pageInfo.title || defaultTitle,
|
||||
metaTags: metaTagData,
|
||||
});
|
||||
|
||||
const makeSubConfigPath = (rawPath) => {
|
||||
if (!rawPath) return '';
|
||||
if (rawPath.startsWith('/') || rawPath.startsWith('http')) return rawPath;
|
||||
else return `/${rawPath}`;
|
||||
};
|
||||
|
||||
/* For each additional config file, create routes for home, minimal and workspace views */
|
||||
const makeMultiPageRoutes = (userPages) => {
|
||||
// If no multi pages specified, or is not array, then return nothing
|
||||
if (!userPages || !Array.isArray(userPages)) return [];
|
||||
const multiPageRoutes = [];
|
||||
// For each user page, create an additional route
|
||||
userPages.forEach((page) => {
|
||||
if (!page.name || !page.path) { // Sumin not right, show warning
|
||||
ErrorHandler('Additional pages must have both a `name` and `path`');
|
||||
}
|
||||
// Props to be passed to home mixin
|
||||
const subPageInfo = {
|
||||
subPageInfo: {
|
||||
confPath: makeSubConfigPath(page.path),
|
||||
pageId: makePageName(page.name),
|
||||
pageTitle: page.name,
|
||||
},
|
||||
};
|
||||
// Create route for default homepage
|
||||
multiPageRoutes.push({
|
||||
path: makePageSlug(page.name, 'home'),
|
||||
name: `${subPageInfo.subPageInfo.pageId}-home`,
|
||||
component: Home,
|
||||
props: subPageInfo,
|
||||
});
|
||||
// Create route for the workspace view
|
||||
multiPageRoutes.push({
|
||||
path: makePageSlug(page.name, 'workspace'),
|
||||
name: `${subPageInfo.subPageInfo.pageId}-workspace`,
|
||||
component: () => import('./views/Workspace.vue'),
|
||||
props: subPageInfo,
|
||||
});
|
||||
// Create route for the minimal view
|
||||
multiPageRoutes.push({
|
||||
path: makePageSlug(page.name, 'minimal'),
|
||||
name: `${subPageInfo.subPageInfo.pageId}-minimal`,
|
||||
component: () => import('./views/Minimal.vue'),
|
||||
props: subPageInfo,
|
||||
});
|
||||
});
|
||||
return multiPageRoutes;
|
||||
const makeMetaTags = (defaultTitle) => {
|
||||
const userTitle = process.env.VUE_APP_TITLE || '';
|
||||
const title = userTitle ? `${userTitle} | ${defaultTitle}` : defaultTitle;
|
||||
return { title, metaTags: metaTagData };
|
||||
};
|
||||
|
||||
/* Routing mode, can be either 'hash', 'history' or 'abstract' */
|
||||
const mode = appConfig.routingMode || 'history';
|
||||
const mode = process.env.VUE_APP_ROUTING_MODE || 'history';
|
||||
|
||||
/* List of all routes, props, components and metadata */
|
||||
const router = new Router({
|
||||
mode,
|
||||
routes: [
|
||||
...makeMultiPageRoutes(pages),
|
||||
// ...makeMultiPageRoutes(pages),
|
||||
{ // The default view can be customized by the user
|
||||
path: '/',
|
||||
name: `landing-page-${getStartingView()}`,
|
||||
name: `landing-page-${startingView}`,
|
||||
component: getStartingComponent(),
|
||||
meta: makeMetaTags('Home Page'),
|
||||
},
|
||||
@@ -197,7 +134,7 @@ const router = new Router({
|
||||
* if so, then ensure that they are correctly logged in as a valid user
|
||||
* If not logged in, prevent all access and redirect them to login page
|
||||
* */
|
||||
router.beforeEach((to, from, next) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
progress.start();
|
||||
if (to.name !== 'login' && !isAuthenticated()) next({ name: 'login' });
|
||||
else next();
|
||||
|
||||
240
src/store.js
240
src/store.js
@@ -4,22 +4,22 @@ 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';
|
||||
import { makePageName, formatConfigPath, componentVisibility } from '@/utils/ConfigHelpers';
|
||||
import { applyItemId } from '@/utils/SectionHelpers';
|
||||
import filterUserSections from '@/utils/CheckSectionVisibility';
|
||||
import ErrorHandler, { InfoHandler, InfoKeys } from '@/utils/ErrorHandler';
|
||||
import { isUserAdmin } from '@/utils/Auth';
|
||||
import { localStorageKeys } from './utils/defaults';
|
||||
import { isUserAdmin, makeBasicAuthHeaders, isLoggedInAsGuest } from '@/utils/Auth';
|
||||
import { localStorageKeys, theme as defaultTheme } from './utils/defaults';
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
const {
|
||||
INITIALIZE_CONFIG,
|
||||
INITIALIZE_MULTI_PAGE_CONFIG,
|
||||
INITIALIZE_ROOT_CONFIG,
|
||||
SET_CONFIG,
|
||||
SET_REMOTE_CONFIG,
|
||||
SET_CURRENT_SUB_PAGE,
|
||||
SET_ROOT_CONFIG,
|
||||
SET_CURRENT_CONFIG_INFO,
|
||||
SET_IS_USING_LOCAL_CONFIG,
|
||||
SET_MODAL_OPEN,
|
||||
SET_LANGUAGE,
|
||||
SET_ITEM_LAYOUT,
|
||||
@@ -41,15 +41,24 @@ const {
|
||||
INSERT_ITEM,
|
||||
UPDATE_CUSTOM_CSS,
|
||||
CONF_MENU_INDEX,
|
||||
CRITICAL_ERROR_MSG,
|
||||
} = Keys;
|
||||
|
||||
const emptyConfig = {
|
||||
appConfig: {},
|
||||
pageInfo: { title: 'Dashy' },
|
||||
sections: [],
|
||||
};
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
config: {}, // The current config, rendered to the UI
|
||||
remoteConfig: {}, // The configuration stored on the server
|
||||
config: {}, // The current config being used, and rendered to the UI
|
||||
rootConfig: null, // Always the content of main config file, never used directly
|
||||
editMode: false, // While true, the user can drag and edit items + sections
|
||||
modalOpen: false, // KB shortcut functionality will be disabled when modal is open
|
||||
currentConfigInfo: undefined, // For multi-page support, will store info about config file
|
||||
currentConfigInfo: {}, // For multi-page support, will store info about config file
|
||||
isUsingLocalConfig: false, // If true, will use local config instead of fetched
|
||||
criticalError: null, // Will store a message, if a critical error occurs
|
||||
navigateConfToTab: undefined, // Used to switch active tab in config modal
|
||||
},
|
||||
getters: {
|
||||
@@ -68,17 +77,14 @@ const store = new Vuex.Store({
|
||||
return filterUserSections(state.config.sections || []);
|
||||
},
|
||||
pages(state) {
|
||||
return state.remoteConfig.pages || [];
|
||||
return state.config.pages || [];
|
||||
},
|
||||
theme(state) {
|
||||
let localTheme = null;
|
||||
if (state.currentConfigInfo?.pageId) {
|
||||
const themeStoreKey = `${localStorageKeys.THEME}-${state.currentConfigInfo?.pageId}`;
|
||||
localTheme = localStorage[themeStoreKey];
|
||||
} else {
|
||||
localTheme = localStorage[localStorageKeys.THEME];
|
||||
}
|
||||
return localTheme || state.config.appConfig.theme;
|
||||
const localStorageKey = state.currentConfigInfo.confId
|
||||
? `${localStorageKeys.THEME}-${state.currentConfigInfo.confId}` : localStorageKeys.THEME;
|
||||
const localTheme = localStorage[localStorageKey];
|
||||
// Return either theme from local storage, or from appConfig
|
||||
return localTheme || state.config.appConfig.theme || defaultTheme;
|
||||
},
|
||||
webSearch(state, getters) {
|
||||
return getters.appConfig.webSearch || {};
|
||||
@@ -108,7 +114,8 @@ const store = new Vuex.Store({
|
||||
}
|
||||
// Disable everything
|
||||
if (appConfig.disableConfiguration
|
||||
|| (appConfig.disableConfigurationForNonAdmin && !isUserAdmin())) {
|
||||
|| (appConfig.disableConfigurationForNonAdmin && !isUserAdmin())
|
||||
|| isLoggedInAsGuest()) {
|
||||
perms.allowWriteToDisk = false;
|
||||
perms.allowSaveLocally = false;
|
||||
perms.allowViewConfig = false;
|
||||
@@ -139,21 +146,36 @@ const store = new Vuex.Store({
|
||||
return foundSection;
|
||||
},
|
||||
layout(state) {
|
||||
return state.config.appConfig.layout || 'auto';
|
||||
const pageId = state.currentConfigInfo.confId;
|
||||
const layoutStoreKey = pageId
|
||||
? `${localStorageKeys.LAYOUT_ORIENTATION}-${pageId}` : localStorageKeys.LAYOUT_ORIENTATION;
|
||||
const appConfigLayout = state.config.appConfig.layout;
|
||||
return localStorage.getItem(layoutStoreKey) || appConfigLayout || 'auto';
|
||||
},
|
||||
iconSize(state) {
|
||||
return state.config.appConfig.iconSize || 'medium';
|
||||
const pageId = state.currentConfigInfo.confId;
|
||||
const sizeStoreKey = pageId
|
||||
? `${localStorageKeys.ICON_SIZE}-${pageId}` : localStorageKeys.ICON_SIZE;
|
||||
const appConfigSize = state.config.appConfig.iconSize;
|
||||
return localStorage.getItem(sizeStoreKey) || appConfigSize || 'medium';
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
/* Set the master config */
|
||||
[SET_ROOT_CONFIG](state, config) {
|
||||
if (!config.appConfig) config.appConfig = {};
|
||||
state.config = config;
|
||||
},
|
||||
/* The config to display and edit. Will differ from ROOT_CONFIG when using multi-page */
|
||||
[SET_CONFIG](state, config) {
|
||||
if (!config.appConfig) config.appConfig = {};
|
||||
state.config = config;
|
||||
},
|
||||
[SET_REMOTE_CONFIG](state, config) {
|
||||
const notNullConfig = config || {};
|
||||
if (!notNullConfig.appConfig) notNullConfig.appConfig = {};
|
||||
state.remoteConfig = notNullConfig;
|
||||
[SET_CURRENT_CONFIG_INFO](state, subConfigInfo) {
|
||||
state.currentConfigInfo = subConfigInfo;
|
||||
},
|
||||
[SET_IS_USING_LOCAL_CONFIG](state, isUsingLocalConfig) {
|
||||
state.isUsingLocalConfig = isUsingLocalConfig;
|
||||
},
|
||||
[SET_LANGUAGE](state, lang) {
|
||||
const newConfig = state.config;
|
||||
@@ -169,6 +191,10 @@ const store = new Vuex.Store({
|
||||
state.editMode = editMode;
|
||||
}
|
||||
},
|
||||
[CRITICAL_ERROR_MSG](state, message) {
|
||||
if (message) ErrorHandler(message);
|
||||
state.criticalError = message;
|
||||
},
|
||||
[UPDATE_ITEM](state, payload) {
|
||||
const { itemId, newItem } = payload;
|
||||
const newConfig = { ...state.config };
|
||||
@@ -276,12 +302,13 @@ const store = new Vuex.Store({
|
||||
config.sections = applyItemId(config.sections);
|
||||
state.config = config;
|
||||
},
|
||||
[SET_THEME](state, themOps) {
|
||||
const { theme, pageId } = themOps;
|
||||
[SET_THEME](state, theme) {
|
||||
const newConfig = { ...state.config };
|
||||
newConfig.appConfig.theme = theme;
|
||||
state.config = newConfig;
|
||||
const themeStoreKey = pageId ? `${localStorageKeys.THEME}-${pageId}` : localStorageKeys.THEME;
|
||||
const pageId = state.currentConfigInfo.confId;
|
||||
const themeStoreKey = pageId
|
||||
? `${localStorageKeys.THEME}-${pageId}` : localStorageKeys.THEME;
|
||||
localStorage.setItem(themeStoreKey, theme);
|
||||
InfoHandler('Theme updated', InfoKeys.VISUAL);
|
||||
},
|
||||
@@ -292,11 +319,23 @@ const store = new Vuex.Store({
|
||||
InfoHandler('Color palette updated', InfoKeys.VISUAL);
|
||||
},
|
||||
[SET_ITEM_LAYOUT](state, layout) {
|
||||
state.config.appConfig.layout = layout;
|
||||
const newConfig = { ...state.config };
|
||||
newConfig.appConfig.layout = layout;
|
||||
state.config = newConfig;
|
||||
const pageId = state.currentConfigInfo.confId;
|
||||
const layoutStoreKey = pageId
|
||||
? `${localStorageKeys.LAYOUT_ORIENTATION}-${pageId}` : localStorageKeys.LAYOUT_ORIENTATION;
|
||||
localStorage.setItem(layoutStoreKey, layout);
|
||||
InfoHandler('Layout updated', InfoKeys.VISUAL);
|
||||
},
|
||||
[SET_ITEM_SIZE](state, iconSize) {
|
||||
state.config.appConfig.iconSize = iconSize;
|
||||
const newConfig = { ...state.config };
|
||||
newConfig.appConfig.iconSize = iconSize;
|
||||
state.config = newConfig;
|
||||
const pageId = state.currentConfigInfo.confId;
|
||||
const sizeStoreKey = pageId
|
||||
? `${localStorageKeys.ICON_SIZE}-${pageId}` : localStorageKeys.ICON_SIZE;
|
||||
localStorage.setItem(sizeStoreKey, iconSize);
|
||||
InfoHandler('Item size updated', InfoKeys.VISUAL);
|
||||
},
|
||||
[UPDATE_CUSTOM_CSS](state, customCss) {
|
||||
@@ -306,42 +345,117 @@ const store = new Vuex.Store({
|
||||
[CONF_MENU_INDEX](state, index) {
|
||||
state.navigateConfToTab = index;
|
||||
},
|
||||
[SET_CURRENT_SUB_PAGE](state, subPageObject) {
|
||||
if (!subPageObject) {
|
||||
// Set theme back to primary when navigating to index page
|
||||
const defaulTheme = localStorage.getItem(localStorageKeys.PRIMARY_THEME);
|
||||
if (defaulTheme) state.config.appConfig.theme = defaulTheme;
|
||||
}
|
||||
state.currentConfigInfo = subPageObject;
|
||||
},
|
||||
[USE_MAIN_CONFIG](state) {
|
||||
if (state.remoteConfig) {
|
||||
state.config = state.remoteConfig;
|
||||
} else {
|
||||
this.dispatch(Keys.INITIALIZE_CONFIG);
|
||||
}
|
||||
/* Set config to rootConfig, by calling initialize with no params */
|
||||
async [USE_MAIN_CONFIG]() {
|
||||
this.dispatch(Keys.INITIALIZE_CONFIG);
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
/* Called when app first loaded. Reads config and sets state */
|
||||
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);
|
||||
/* Fetches the root config file, only ever called by INITIALIZE_CONFIG */
|
||||
async [INITIALIZE_ROOT_CONFIG]({ commit }) {
|
||||
const configFilePath = process.env.VUE_APP_CONFIG_PATH || '/conf.yml';
|
||||
try {
|
||||
// Attempt to fetch the YAML file
|
||||
const response = await axios.get(configFilePath, makeBasicAuthHeaders());
|
||||
let data;
|
||||
try {
|
||||
data = yaml.load(response.data);
|
||||
} catch (parseError) {
|
||||
commit(CRITICAL_ERROR_MSG, `Failed to parse YAML: ${parseError.message}`);
|
||||
return { ...emptyConfig };
|
||||
}
|
||||
// Replace missing root properties with empty objects
|
||||
if (!data.appConfig) data.appConfig = {};
|
||||
if (!data.pageInfo) data.pageInfo = {};
|
||||
if (!data.sections) data.sections = [];
|
||||
// Set the state, and return data
|
||||
commit(SET_ROOT_CONFIG, data);
|
||||
commit(CRITICAL_ERROR_MSG, null);
|
||||
return data;
|
||||
} catch (fetchError) {
|
||||
if (fetchError.response) {
|
||||
commit(
|
||||
CRITICAL_ERROR_MSG,
|
||||
'Failed to fetch configuration: Server responded with status '
|
||||
+ `${fetchError.response?.status || 'mystery status'}`,
|
||||
);
|
||||
} else if (fetchError.request) {
|
||||
commit(CRITICAL_ERROR_MSG, 'Failed to fetch configuration: No response from server');
|
||||
} else {
|
||||
commit(CRITICAL_ERROR_MSG, `Failed to fetch configuration: ${fetchError.message}`);
|
||||
}
|
||||
return { ...emptyConfig };
|
||||
}
|
||||
},
|
||||
/* Fetch config for a sub-page (sections and pageInfo only) */
|
||||
async [INITIALIZE_MULTI_PAGE_CONFIG]({ commit, state }, configPath) {
|
||||
axios.get(configPath).then((response) => {
|
||||
const subConfig = yaml.load(response.data);
|
||||
const pageTheme = subConfig.appConfig?.theme;
|
||||
subConfig.appConfig = state.config.appConfig; // Always use parent appConfig
|
||||
if (pageTheme) subConfig.appConfig.theme = pageTheme; // Apply page theme override
|
||||
commit(SET_CONFIG, subConfig);
|
||||
}).catch((err) => {
|
||||
ErrorHandler(`Unable to load config from '${configPath}'`, err);
|
||||
});
|
||||
/**
|
||||
* Fetches config and updates state
|
||||
* If not on sub-page, will trigger the fetch of main config, then use that
|
||||
* If using sub-page config, then fetch that sub-config, then
|
||||
* override certain fields (appConfig, pages) and update config
|
||||
*/
|
||||
async [INITIALIZE_CONFIG]({ commit, state }, subConfigId) {
|
||||
const rootConfig = state.rootConfig || await this.dispatch(Keys.INITIALIZE_ROOT_CONFIG);
|
||||
|
||||
commit(SET_IS_USING_LOCAL_CONFIG, false);
|
||||
if (!subConfigId) { // Use root config as config
|
||||
commit(SET_CONFIG, rootConfig);
|
||||
commit(SET_CURRENT_CONFIG_INFO, {});
|
||||
|
||||
let localSections = [];
|
||||
const localSectionsRaw = localStorage[localStorageKeys.CONF_SECTIONS];
|
||||
if (localSectionsRaw) {
|
||||
try {
|
||||
const json = JSON.parse(localSectionsRaw);
|
||||
if (json.length >= 1) localSections = json;
|
||||
} catch (e) {
|
||||
commit(CRITICAL_ERROR_MSG, 'Malformed section data in local storage');
|
||||
}
|
||||
}
|
||||
if (localSections.length > 0) {
|
||||
rootConfig.sections = localSections;
|
||||
commit(SET_IS_USING_LOCAL_CONFIG, true);
|
||||
}
|
||||
return rootConfig;
|
||||
} else {
|
||||
// Find and format path to fetch sub-config from
|
||||
const subConfigPath = formatConfigPath(rootConfig?.pages?.find(
|
||||
(page) => makePageName(page.name) === subConfigId,
|
||||
)?.path);
|
||||
|
||||
if (!subConfigPath) {
|
||||
commit(CRITICAL_ERROR_MSG, `Unable to find config for '${subConfigId}'`);
|
||||
return { ...emptyConfig };
|
||||
}
|
||||
axios.get(subConfigPath, makeBasicAuthHeaders()).then((response) => {
|
||||
// Parse the YAML
|
||||
const configContent = yaml.load(response.data) || {};
|
||||
// Certain values must be inherited from root config
|
||||
const theme = configContent?.appConfig?.theme || rootConfig.appConfig?.theme || 'default';
|
||||
configContent.appConfig = rootConfig.appConfig;
|
||||
configContent.pages = rootConfig.pages;
|
||||
configContent.appConfig.theme = theme;
|
||||
|
||||
// Load local sections if they exist
|
||||
const localSectionsRaw = localStorage[`${localStorageKeys.CONF_SECTIONS}-${subConfigId}`];
|
||||
if (localSectionsRaw) {
|
||||
try {
|
||||
const json = JSON.parse(localSectionsRaw);
|
||||
if (json.length >= 1) {
|
||||
configContent.sections = json;
|
||||
commit(SET_IS_USING_LOCAL_CONFIG, true);
|
||||
}
|
||||
} catch (e) {
|
||||
commit(CRITICAL_ERROR_MSG, 'Malformed section data in local storage for sub-config');
|
||||
}
|
||||
}
|
||||
// Set the config
|
||||
commit(SET_CONFIG, configContent);
|
||||
commit(SET_CURRENT_CONFIG_INFO, { confPath: subConfigPath, confId: subConfigId });
|
||||
}).catch((err) => {
|
||||
commit(CRITICAL_ERROR_MSG, `Unable to load config from '${subConfigPath}'`, err);
|
||||
});
|
||||
}
|
||||
return { ...emptyConfig };
|
||||
},
|
||||
},
|
||||
modules: {},
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
--transparent-white-70: #ffffffb3;
|
||||
--transparent-white-50: #ffffff80;
|
||||
--transparent-white-30: #ffffff4d;
|
||||
--transparent-white-10: #ffffff0f;
|
||||
|
||||
/* Color variables for specific components
|
||||
* all variables are optional, since they inherit initial values from above*
|
||||
|
||||
@@ -1619,6 +1619,236 @@ html[data-theme='lissy'] {
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='glass'],
|
||||
html[data-theme='glass-2'],
|
||||
html[data-theme='neomorphic'] {
|
||||
--primary: #fff;
|
||||
--item-group-outer-background: rgba(0, 0, 0, 0.25);
|
||||
--item-group-background: transparent;
|
||||
--item-group-heading-text-color: #fff;
|
||||
--item-group-heading-text-color-hover: #ffffffd6;
|
||||
--item-group-shadow: 5px 2px 20px rgba(0, 0, 0, 0.5);
|
||||
--background: #190842;
|
||||
--background-darker: #190842;
|
||||
--settings-background: transparent;
|
||||
--search-container-background: transparent;
|
||||
--font-headings: 'Segoe UI', 'Ariel', 'sans-serif';
|
||||
--font-body: 'Roboto', 'Segoe UI', 'Ariel', 'sans-serif';
|
||||
--minimal-view-background-color: transparent;
|
||||
--minimal-view-group-background: rgba(255, 255, 255, 0.15);
|
||||
--minimal-view-section-heading-background: rgba(255, 255, 255, 0.15);
|
||||
--minimal-view-section-heading-color: rgba(255, 255, 255, 0.15);
|
||||
--config-settings-background: #16073de3;
|
||||
--cloud-backup-background: #16073de3;
|
||||
|
||||
@mixin item-transition-styles($bg: transparent, $hover-bg: rgba(255, 255, 255, 0.15), $hover-shadow: rgba(0, 0, 0, 0.75)) {
|
||||
background: $bg;
|
||||
border: 1px solid transparent;
|
||||
box-shadow: none;
|
||||
transition: 0.2s all ease-in-out;
|
||||
|
||||
&:hover {
|
||||
border-radius: 0.35rem;
|
||||
box-shadow: 0 4px 30px $hover-shadow;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.19);
|
||||
background: $hover-bg;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin transform-scale($normal-scale: 1, $hover-scale: 1.25) {
|
||||
transition: 0.1s all ease-in-out;
|
||||
transform: scale($normal-scale);
|
||||
|
||||
&:hover {
|
||||
transform: scale($hover-scale);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
background-size: cover;
|
||||
background-color: #090317;
|
||||
.home {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-outer, header, .dashy-modal, .dashy-modal .tabs {
|
||||
background: transparent;
|
||||
// backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
// Minimal view components
|
||||
.minimal-section-inner, div.minimal-section-heading {
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(145, 145, 145, 0.45);
|
||||
border-bottom: none;
|
||||
|
||||
&.selected {
|
||||
border: 1px solid rgba(145, 145, 145, 0.45);
|
||||
background: var(--minimal-view-group-background);
|
||||
}
|
||||
}
|
||||
|
||||
.minimal-section-heading {
|
||||
color: var(--minimal-view-section-heading-background);
|
||||
|
||||
&.selected {
|
||||
.section-icon, .section-title {
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
--glass-button-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
--glass-button-hover-shadow: 2px 2px 5px rgba(0, 0, 0, 0.7);
|
||||
|
||||
// Forms and inputs
|
||||
button.save-button,
|
||||
.action-buttons button,
|
||||
.cloud-backup-restore-wrapper button,
|
||||
.tab__nav__item,
|
||||
div.input-container input.input-field,
|
||||
form.normal input,
|
||||
.nav-outer nav .nav-item,
|
||||
div.edit-mode-bottom-banner .edit-banner-section button,
|
||||
.v-select.theme-dropdown.vs__dropdown-toggle,
|
||||
.theme-dropdown div.vs__dropdown-toggle,
|
||||
.config-buttons > svg,
|
||||
.display-options svg,
|
||||
form.minimal input,
|
||||
.critical-error-wrap button.user-doesnt-care,
|
||||
a.config-button, button.config-button {
|
||||
border-radius: 0.35rem;
|
||||
box-shadow: var(--glass-button-shadow);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.19);
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
transition: all 0.2s ease-in-out;
|
||||
text-decoration: none;
|
||||
&:hover, &.selected {
|
||||
box-shadow: var(--glass-button-hover-shadow);
|
||||
border: 1px solid rgba(255, 255, 255, 0.25) !important;
|
||||
background: #ffffff42 !important;
|
||||
color: var(--primary) !important;
|
||||
path { fill: var(--primary); }
|
||||
}
|
||||
}
|
||||
|
||||
.tab__nav__items {
|
||||
gap: 1rem;
|
||||
margin: 0.5rem 0 0;
|
||||
.tab__nav__item {
|
||||
padding: 0.5rem 0.5rem;
|
||||
&:hover, .active, .active:hover {
|
||||
background: #ffffff42 !important;
|
||||
span { color: var(--primary) !important; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-options-container .config-buttons, div.cloud-backup-restore-wrapper {
|
||||
background: none;
|
||||
}
|
||||
|
||||
// Item and collapsable specific styles
|
||||
.item {
|
||||
@include item-transition-styles(transparent, rgba(255, 255, 255, 0.15), rgba(0, 0, 0, 0.75));
|
||||
.item-icon {
|
||||
@include transform-scale(1.1, 1.25);
|
||||
}
|
||||
}
|
||||
|
||||
.collapsable {
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid rgba(0, 0, 0, 0.45);
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
// Modal specific styles
|
||||
.dashy-modal {
|
||||
box-shadow: 0 20px 40px -2px #000000b8, 1px 1px 6px #000000a6 !important;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
background: var(--config-settings-background);
|
||||
}
|
||||
|
||||
.theme-configurator-wrapper, .view-switcher {
|
||||
backdrop-filter: blur(10px);
|
||||
background: var(--config-settings-background);
|
||||
border: 1px solid rgba(255, 255, 255, 0.19);
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.edit-mode-top-banner {
|
||||
backdrop-filter: blur(10px);
|
||||
background: #ffffff6b;
|
||||
border-bottom: 1px solid black;
|
||||
|
||||
span { color: #eaff9d; }
|
||||
}
|
||||
|
||||
div.edit-mode-bottom-banner, .add-new-section {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
backdrop-filter: blur(50px);
|
||||
}
|
||||
|
||||
.critical-error-wrap {
|
||||
backdrop-filter: blur(15px);
|
||||
background: #0f0528c4;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='glass'] {
|
||||
body {
|
||||
background: url('https://zeabur.com/images/bg.png') center center no-repeat;
|
||||
background-size: cover;
|
||||
background-color: #090317;
|
||||
.home {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='glass-2'] {
|
||||
body {
|
||||
background: url('https://i.ibb.co/FnLH6bj/dashy-glass.jpg') center center no-repeat;
|
||||
background-size: cover;
|
||||
background-color: #090317;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='neomorphic'] {
|
||||
--primary: #fff;
|
||||
--item-group-outer-background: rgba(255, 255, 255, 0.15);
|
||||
--item-group-background: transparent;
|
||||
--item-group-heading-text-color: #fff;
|
||||
--item-group-shadow: 5px 2px 20px rgba(0, 0, 0, 0.5);
|
||||
--background: #5b56f7;
|
||||
// --background: #4bdbfd;
|
||||
--background-darker: #12103c;
|
||||
--settings-background: transparent;
|
||||
--search-container-background: transparent;
|
||||
--font-headings: 'Segoe UI', 'Ariel', 'sans-serif';
|
||||
--font-body: 'Roboto', 'Segoe UI', 'Ariel', 'sans-serif';
|
||||
--minimal-view-background-color: transparent;
|
||||
--minimal-view-group-background: rgba(255, 255, 255, 0.15);
|
||||
--minimal-view-section-heading-background: rgba(255, 255, 255, 0.15);
|
||||
--minimal-view-section-heading-color: rgba(255, 255, 255, 0.15);
|
||||
--config-settings-background: #1fb8f4e3;
|
||||
--cloud-backup-background: #16073de3;
|
||||
|
||||
--glass-button-shadow: 0px 1px 5px rgba(0, 0, 0, 0.5);
|
||||
--glass-button-hover-shadow: 2px 2px 5px rgba(0, 0, 0, 0.7);
|
||||
body {
|
||||
background: var(--background);
|
||||
}
|
||||
.item:hover { box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5); }
|
||||
.collapsable { border: 1px solid rgba(255, 255, 255, 0.25) !important; }
|
||||
}
|
||||
|
||||
|
||||
html[data-theme='cherry-blossom'] {
|
||||
--primary: #e1e8ee;
|
||||
--background: #11171d;
|
||||
@@ -1777,7 +2007,7 @@ html[data-theme="tama"] {
|
||||
|
||||
// large tile spacing adjustment
|
||||
.there-are-items:has(.item-wrapper.wrap-size-large) { padding-top: .25rem; }
|
||||
.item-wrapper.wrap-size-large { margin: 0rem .5rem .5rem 0rem; }
|
||||
.item-wrapper.wrap-size-large { margin: 0rem .5rem .5rem 0rem; overflow: hidden; }
|
||||
.item.size-large .tile-title { padding: 0rem 0rem 0rem .7rem; }
|
||||
|
||||
// Hide open method icon
|
||||
|
||||
@@ -22,6 +22,11 @@ html {
|
||||
}
|
||||
}
|
||||
|
||||
#dashy {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Hide text, and show 'Loading...' while Vue is initializing tags */
|
||||
[v-cloak] > * { display:none }
|
||||
[v-cloak]::before { content: "loading…" }
|
||||
@@ -103,6 +108,7 @@ html {
|
||||
border-radius: var(--curve-factor-small);
|
||||
color: var(--description-tooltip-color);
|
||||
padding: var(--tooltip-padding);
|
||||
overflow: hidden;
|
||||
}
|
||||
.tooltip-arrow {
|
||||
width: 0;
|
||||
|
||||
@@ -39,37 +39,52 @@ const getUsers = () => {
|
||||
* @returns {String} The hashed token
|
||||
*/
|
||||
const generateUserToken = (user) => {
|
||||
if (!user.user || !user.hash) {
|
||||
ErrorHandler('Invalid user object. Must have `user` and `hash` parameters');
|
||||
if (!user.user || (!user.hash && !user.password)) {
|
||||
ErrorHandler('Invalid user object. Must have `user` and either a `hash` or `password` param');
|
||||
return undefined;
|
||||
}
|
||||
const passHash = user.hash || sha256(process.env[user.password]).toString().toUpperCase();
|
||||
const strAndUpper = (input) => input.toString().toUpperCase();
|
||||
const sha = sha256(strAndUpper(user.user) + strAndUpper(user.hash));
|
||||
const sha = sha256(strAndUpper(user.user) + strAndUpper(passHash));
|
||||
return strAndUpper(sha);
|
||||
};
|
||||
|
||||
export const getCookieToken = () => {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${cookieKeys.AUTH_TOKEN}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
return null;
|
||||
};
|
||||
|
||||
export const makeBasicAuthHeaders = () => {
|
||||
const token = getCookieToken();
|
||||
const bearerAuth = (token && token.length > 5) ? `Bearer ${token}` : null;
|
||||
|
||||
const username = process.env.VUE_APP_BASIC_AUTH_USERNAME
|
||||
|| localStorage[localStorageKeys.USERNAME]
|
||||
|| 'user';
|
||||
const password = process.env.VUE_APP_BASIC_AUTH_PASSWORD || bearerAuth;
|
||||
const basicAuth = `Basic ${btoa(`${username}:${password}`)}`;
|
||||
|
||||
const headers = password
|
||||
? { headers: { Authorization: basicAuth, 'WWW-Authenticate': 'true' } }
|
||||
: {};
|
||||
return headers;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the user is currently authenticated
|
||||
* @returns {Boolean} Will return true if the user is logged in, else false
|
||||
*/
|
||||
export const isLoggedIn = () => {
|
||||
const users = getUsers();
|
||||
let userAuthenticated = document.cookie.split(';').some((cookie) => {
|
||||
if (cookie && cookie.split('=').length > 1) {
|
||||
const cookieKey = cookie.split('=')[0].trim();
|
||||
const cookieValue = cookie.split('=')[1].trim();
|
||||
if (cookieKey === cookieKeys.AUTH_TOKEN) {
|
||||
userAuthenticated = users.some((user) => {
|
||||
if (generateUserToken(user) === cookieValue) {
|
||||
localStorage.setItem(localStorageKeys.USERNAME, user.user);
|
||||
return true;
|
||||
} else return false;
|
||||
});
|
||||
return userAuthenticated;
|
||||
} else return false;
|
||||
const cookieToken = getCookieToken();
|
||||
return users.some((user) => {
|
||||
if (generateUserToken(user) === cookieToken) {
|
||||
localStorage.setItem(localStorageKeys.USERNAME, user.user);
|
||||
return true;
|
||||
} else return false;
|
||||
});
|
||||
return userAuthenticated;
|
||||
};
|
||||
|
||||
/* Returns true if authentication is enabled */
|
||||
@@ -106,7 +121,18 @@ export const checkCredentials = (username, pass, users, messages) => {
|
||||
} else {
|
||||
users.forEach((user) => {
|
||||
if (user.user.toLowerCase() === username.toLowerCase()) { // User found
|
||||
if (user.hash.toLowerCase() === sha256(pass).toString().toLowerCase()) {
|
||||
if (user.password) {
|
||||
if (!user.password.startsWith('VUE_APP_')) {
|
||||
ErrorHandler('Invalid password format. Please use VUE_APP_ prefix');
|
||||
response = { correct: false, msg: messages.incorrectPassword };
|
||||
} else if (!process.env[user.password]) {
|
||||
ErrorHandler(`Missing environmental variable for ${user.password}`);
|
||||
} else if (process.env[user.password] === pass) {
|
||||
response = { correct: true, msg: messages.successMsg };
|
||||
} else {
|
||||
response = { correct: false, msg: messages.incorrectPassword };
|
||||
}
|
||||
} else if (user.hash && user.hash.toLowerCase() === sha256(pass).toString().toLowerCase()) {
|
||||
response = { correct: true, msg: messages.successMsg }; // Password is correct
|
||||
} else { // User found, but password is not a match
|
||||
response = { correct: false, msg: messages.incorrectPassword };
|
||||
@@ -161,9 +187,9 @@ export const getCurrentUser = () => {
|
||||
* Checks if the user is viewing the dashboard as a guest
|
||||
* Returns true if guest mode enabled, and user not logged in
|
||||
* */
|
||||
export const isLoggedInAsGuest = (currentUser) => {
|
||||
export const isLoggedInAsGuest = () => {
|
||||
const guestEnabled = isGuestAccessEnabled();
|
||||
const loggedIn = isLoggedIn() && currentUser;
|
||||
const loggedIn = isLoggedIn();
|
||||
return guestEnabled && !loggedIn;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,11 +16,9 @@ import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { applyItemId } from '@/utils/SectionHelpers';
|
||||
import $store from '@/store';
|
||||
|
||||
import buildConf from '../../public/conf.yml';
|
||||
|
||||
export default class ConfigAccumulator {
|
||||
constructor() {
|
||||
this.conf = $store.state.remoteConfig;
|
||||
this.conf = $store.state.config;
|
||||
}
|
||||
|
||||
pages() {
|
||||
@@ -33,8 +31,6 @@ export default class ConfigAccumulator {
|
||||
// Set app config from file
|
||||
if (this.conf && this.conf.appConfig) {
|
||||
appConfigFile = this.conf.appConfig;
|
||||
} else if (buildConf && buildConf.appConfig) {
|
||||
appConfigFile = buildConf.appConfig;
|
||||
}
|
||||
// Fill in defaults if anything missing
|
||||
let usersAppConfig = defaultAppConfig;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
||||
// import $store from '@/store';
|
||||
import filterUserSections from '@/utils/CheckSectionVisibility';
|
||||
import { languages } from '@/utils/languages';
|
||||
import {
|
||||
visibleComponents,
|
||||
localStorageKeys,
|
||||
theme as defaultTheme,
|
||||
language as defaultLanguage,
|
||||
} from '@/utils/defaults';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
@@ -26,6 +26,13 @@ export const makePageSlug = (pageName, pageType) => {
|
||||
return `/${pageType}/${formattedName}`;
|
||||
};
|
||||
|
||||
/* Put fetch path for additional configs in correct format */
|
||||
export const formatConfigPath = (configPath) => {
|
||||
if (configPath.includes('http')) return configPath;
|
||||
if (configPath.substring(0, 1) !== '/') return `/${configPath}`;
|
||||
return configPath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initiates the Accumulator class and generates a complete config object
|
||||
* Self-executing function, returns the full user config as a JSON object
|
||||
@@ -67,34 +74,12 @@ export const componentVisibility = (appConfig) => {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the users saved theme, first looks for local storage theme,
|
||||
* then looks at user's appConfig, and finally checks the defaults
|
||||
* @returns {string} Name of theme to apply
|
||||
*/
|
||||
export const getTheme = () => {
|
||||
const localTheme = localStorage[localStorageKeys.THEME];
|
||||
const appConfigTheme = config.appConfig.theme;
|
||||
return localTheme || appConfigTheme || defaultTheme;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets any custom styles the user has applied, wither from local storage, or from the config
|
||||
* @returns {object} An array of objects, one for each theme, containing kvps for variables
|
||||
*/
|
||||
export const getCustomColors = () => {
|
||||
const localColors = JSON.parse(localStorage[localStorageKeys.CUSTOM_COLORS] || '{}');
|
||||
const configColors = config.appConfig.customColors || {};
|
||||
return Object.assign(configColors, localColors);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a list of items which the user has assigned a hotkey to
|
||||
* So that when the hotkey is pressed, the app/ service can be launched
|
||||
*/
|
||||
export const getCustomKeyShortcuts = () => {
|
||||
export const getCustomKeyShortcuts = (sections) => {
|
||||
const results = [];
|
||||
const sections = config.sections || [];
|
||||
sections.forEach((section) => {
|
||||
const itemsWithHotKeys = section.items.filter(item => item.hotkey);
|
||||
results.push(itemsWithHotKeys.map(item => ({ hotkey: item.hotkey, url: item.url })));
|
||||
|
||||
@@ -500,14 +500,9 @@
|
||||
"users": {
|
||||
"title": "Users",
|
||||
"type": "array",
|
||||
"description": "Usernames and hashed credentials for frontend authentication",
|
||||
"description": "Usernames and hashed credentials for frontend authentication. Needs to be set at build-time.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"user",
|
||||
"hash"
|
||||
],
|
||||
"properties": {
|
||||
"user": {
|
||||
"title": "Username",
|
||||
@@ -521,6 +516,12 @@
|
||||
"minLength": 64,
|
||||
"maxLength": 64
|
||||
},
|
||||
"password": {
|
||||
"title": "Password",
|
||||
"type": "string",
|
||||
"description": "The environmental variable pointing to a plaintext password for that user. Must start with VUE_APP_",
|
||||
"pattern": "^VUE_APP_.*"
|
||||
},
|
||||
"type": {
|
||||
"title": "Privileges",
|
||||
"type": "string",
|
||||
@@ -531,6 +532,43 @@
|
||||
"description": "User type, denoting privilege level, either admin or normal",
|
||||
"default": "normal"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["user"],
|
||||
"oneOf": [
|
||||
{ "required": ["hash"] },
|
||||
{ "required": ["password"] }
|
||||
]
|
||||
}
|
||||
},
|
||||
"enableHeaderAuth": {
|
||||
"title": "Enable HeaderAuth?",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If set to true, enable Header Authentication. See appConfig.auth.headerAuth"
|
||||
},
|
||||
"headerAuth": {
|
||||
"type": "object",
|
||||
"description": "Configuration for headerAuth",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"proxyWhitelist"
|
||||
],
|
||||
"properties": {
|
||||
"userHeader": {
|
||||
"title": "User Header",
|
||||
"type": "string",
|
||||
"description": "Header name which contains username",
|
||||
"default": "REMOTE_USER"
|
||||
},
|
||||
"proxyWhitelist": {
|
||||
"title": "Upstream Proxy Auth Trust",
|
||||
"type": "array",
|
||||
"description": "Upstream proxy servers to expect authenticated requests from",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "IPs of upstream proxies that will be trusted"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -982,6 +1020,11 @@
|
||||
"type": "number",
|
||||
"description": "A numeric shortcut key, between 0 and 9. Useful for quickly launching frequently used applications"
|
||||
},
|
||||
"rel": {
|
||||
"title": "rel",
|
||||
"type": "string",
|
||||
"description": "The rel attribute for the link. For specifying the relationship between the current document and the linked document"
|
||||
},
|
||||
"tags": {
|
||||
"title": "Tags",
|
||||
"type": "array",
|
||||
@@ -1086,6 +1129,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"filteredItems": {
|
||||
"title": "Filtered Items - temp",
|
||||
"type": "array",
|
||||
"description": "This attribute will be deprecated in the next release - do not use!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export const welcomeMsg = () => {
|
||||
/* Prints warning message, usually when there is a configuration error */
|
||||
export const warningMsg = (message, stack) => {
|
||||
console.info(
|
||||
`\n%c⚠️ Warning ⚠️%c \n${message} \n\n%cThis is likely not an issue with Dashy, but rather your configuration. If you think it is a bug, please open a ticket on GitHub: https://git.io/JukXk`,
|
||||
`\n%c⚠️ Warning ⚠️%c \n${message} \n\n%cThis is likely not an issue with Dashy, but rather your configuration.\nIf you think it is a bug, please open a ticket on GitHub: https://git.io/JukXk`,
|
||||
"color:#ceb73f; background: #ceb73f33; font-size:1.5rem; padding:0.15rem; margin: 1rem auto; font-family: Rockwell, Tahoma, 'Trebuchet MS', Helvetica; border: 2px solid #ceb73f; border-radius: 4px; font-weight: bold; text-shadow: 1px 1px 1px #000000bf;",
|
||||
'font-weight: bold; font-size: 1rem;color: #ceb73f;',
|
||||
"color: #ceb73f; font-size: 0.75rem; font-family: Tahoma, 'Trebuchet MS', Helvetica;",
|
||||
|
||||
@@ -9,12 +9,12 @@
|
||||
|
||||
/* eslint-disable global-require */
|
||||
|
||||
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
||||
import $store from '@/store';
|
||||
import { sentryDsn } from '@/utils/defaults';
|
||||
|
||||
const ErrorReporting = (Vue, router) => {
|
||||
// Fetch users config
|
||||
const appConfig = new ConfigAccumulator().appConfig() || {};
|
||||
const appConfig = $store.getters.appConfig || {};
|
||||
// Check if error reporting is enabled. Only proceed if user has turned it on.
|
||||
if (appConfig.enableErrorReporting) {
|
||||
// Get current app version
|
||||
|
||||
79
src/utils/HeaderAuth.js
Normal file
79
src/utils/HeaderAuth.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import axios from 'axios';
|
||||
import sha256 from 'crypto-js/sha256';
|
||||
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
||||
import { cookieKeys, localStorageKeys, serviceEndpoints } from '@/utils/defaults';
|
||||
import { InfoHandler, ErrorHandler, InfoKeys } from '@/utils/ErrorHandler';
|
||||
import { logout as authLogout } from '@/utils/Auth';
|
||||
|
||||
const getAppConfig = () => {
|
||||
const Accumulator = new ConfigAccumulator();
|
||||
const config = Accumulator.config();
|
||||
return config.appConfig || {};
|
||||
};
|
||||
|
||||
class HeaderAuth {
|
||||
constructor() {
|
||||
const { auth } = getAppConfig();
|
||||
const {
|
||||
userHeader, proxyWhitelist,
|
||||
} = auth.headerAuth;
|
||||
this.userHeader = userHeader;
|
||||
this.proxyWhitelist = proxyWhitelist;
|
||||
this.users = auth.users;
|
||||
}
|
||||
|
||||
login() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
|
||||
axios.get(`${baseUrl}${serviceEndpoints.getUser}`).then((response) => {
|
||||
if (!response.data) {
|
||||
reject(Error('Error, expected data nout returned'));
|
||||
} else if (response.data.errorMsg) {
|
||||
reject(response.data.errorMsg);
|
||||
} else {
|
||||
try {
|
||||
this.users.forEach((user) => {
|
||||
if (user.user.toLowerCase() === response.data.user.toLowerCase()) { // User found
|
||||
const strAndUpper = (input) => input.toString().toUpperCase();
|
||||
const sha = strAndUpper(sha256(strAndUpper(user.user) + strAndUpper(user.hash)));
|
||||
document.cookie = `${cookieKeys.AUTH_TOKEN}=${sha};`;
|
||||
localStorage.setItem(localStorageKeys.USERNAME, user.user);
|
||||
InfoHandler(`Successfully signed in as ${response.data.user}`, InfoKeys.AUTH);
|
||||
resolve(response.data.user);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
ErrorHandler('Error while trying to login using header authentication', e);
|
||||
reject(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line class-methods-use-this
|
||||
logout() {
|
||||
authLogout();
|
||||
}
|
||||
}
|
||||
|
||||
export const isHeaderAuthEnabled = () => {
|
||||
const { auth } = getAppConfig();
|
||||
if (!auth) return false;
|
||||
return auth.enableHeaderAuth || false;
|
||||
};
|
||||
|
||||
let headerAuth;
|
||||
|
||||
export const initHeaderAuth = () => {
|
||||
headerAuth = new HeaderAuth();
|
||||
return headerAuth.login();
|
||||
};
|
||||
|
||||
// TODO: Find where this is implemented
|
||||
export const getHeaderAuth = () => {
|
||||
if (!headerAuth) {
|
||||
ErrorHandler("HeaderAuth not initialized, can't get instance of class");
|
||||
}
|
||||
return headerAuth;
|
||||
};
|
||||
@@ -27,7 +27,7 @@ const determineIntersection = (source = [], target = []) => {
|
||||
/* Returns false if the displayData of a section/item
|
||||
should not be rendered for the current user/ guest */
|
||||
export const isVisibleToUser = (displayData, currentUser) => {
|
||||
const isGuest = isLoggedInAsGuest(currentUser); // Check if current user is a guest
|
||||
const isGuest = isLoggedInAsGuest(); // Check if current user is a guest
|
||||
|
||||
// Checks if user explicitly has access to a certain section
|
||||
const checkVisibility = () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Dashy: Licensed under MIT, (C) Alicia Sykes 2021 <https://aliciasykes.com> */
|
||||
/* Dashy: Licensed under MIT, (C) Alicia Sykes 2024 <https://aliciasykes.com> */
|
||||
|
||||
/* Tile filtering utility */
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// A list of mutation names
|
||||
const KEY_NAMES = [
|
||||
'INITIALIZE_CONFIG',
|
||||
'INITIALIZE_ROOT_CONFIG',
|
||||
'INITIALIZE_MULTI_PAGE_CONFIG',
|
||||
'SET_CONFIG',
|
||||
'SET_REMOTE_CONFIG',
|
||||
'SET_ROOT_CONFIG',
|
||||
'SET_CURRENT_CONFIG_INFO',
|
||||
'SET_IS_USING_LOCAL_CONFIG',
|
||||
'SET_CURRENT_SUB_PAGE',
|
||||
'SET_MODAL_OPEN',
|
||||
'SET_LANGUAGE',
|
||||
@@ -26,6 +29,7 @@ const KEY_NAMES = [
|
||||
'INSERT_ITEM',
|
||||
'UPDATE_CUSTOM_CSS',
|
||||
'CONF_MENU_INDEX',
|
||||
'CRITICAL_ERROR_MSG',
|
||||
];
|
||||
|
||||
// Convert array of key names into an object, and export
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { getTheme, getCustomColors } from '@/utils/ConfigHelpers';
|
||||
import { mainCssVars } from '@/utils/defaults';
|
||||
|
||||
/* Returns users current theme */
|
||||
export const GetTheme = () => getTheme();
|
||||
|
||||
/* Gets user custom color preferences for current theme, and applies to DOM */
|
||||
export const ApplyCustomVariables = (theme) => {
|
||||
mainCssVars.forEach((vName) => { document.documentElement.style.removeProperty(`--${vName}`); });
|
||||
const themeColors = getCustomColors()[theme];
|
||||
if (themeColors) {
|
||||
Object.keys(themeColors).forEach((customVar) => {
|
||||
document.documentElement.style.setProperty(`--${customVar}`, themeColors[customVar]);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/* Sets the theme, by updating data-theme attribute on the html tag */
|
||||
export const ApplyLocalTheme = (newTheme) => {
|
||||
const htmlTag = document.getElementsByTagName('html')[0];
|
||||
if (htmlTag.hasAttribute('data-theme')) htmlTag.removeAttribute('data-theme');
|
||||
htmlTag.setAttribute('data-theme', newTheme);
|
||||
};
|
||||
|
||||
/**
|
||||
* A function for pre-loading, and easy switching of external stylesheets
|
||||
* External CSS is preloaded to avoid FOUC
|
||||
*/
|
||||
export const LoadExternalTheme = function th() {
|
||||
/* Preload selected external theme */
|
||||
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) => {
|
||||
link.onload = e => {
|
||||
const { sheet } = e.target;
|
||||
sheet.disabled = true;
|
||||
resolve(sheet);
|
||||
};
|
||||
link.onerror = reject;
|
||||
});
|
||||
};
|
||||
|
||||
/* Check theme is selected, and it exists */
|
||||
const checkTheme = (themes, name) => {
|
||||
if ((!name) || (name !== 'custom' && !themes[name])) {
|
||||
ErrorHandler(`Theme: '${name || '[not selected]'}' does not exist.`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/* Disable all but selected theme */
|
||||
const selectTheme = (themes, name) => {
|
||||
if (checkTheme(themes, name)) {
|
||||
const t = themes; // To avoid ESLint complaining about mutating a param
|
||||
Object.keys(themes).forEach(n => { t[n].disabled = (n !== name); });
|
||||
}
|
||||
};
|
||||
|
||||
const themes = {};
|
||||
|
||||
return {
|
||||
add(name, href) { return preloadTheme(href).then(s => { themes[name] = s; }); },
|
||||
set theme(name) { selectTheme(themes, name); },
|
||||
get theme() { return Object.keys(themes).find(n => !themes[n].disabled); },
|
||||
};
|
||||
};
|
||||
@@ -28,9 +28,9 @@ module.exports = {
|
||||
openingMethod: 'newtab',
|
||||
/* The page paths for each route within the app for the router */
|
||||
routePaths: {
|
||||
home: '/home',
|
||||
minimal: '/minimal',
|
||||
workspace: '/workspace',
|
||||
home: '/home/:config?/',
|
||||
minimal: '/minimal/:config?/',
|
||||
workspace: '/workspace/:config?/',
|
||||
about: '/about',
|
||||
login: '/login',
|
||||
download: '/download',
|
||||
@@ -44,10 +44,12 @@ module.exports = {
|
||||
rebuild: '/config-manager/rebuild',
|
||||
systemInfo: '/system-info',
|
||||
corsProxy: '/cors-proxy',
|
||||
getUser: '/get-user',
|
||||
},
|
||||
/* List of built-in themes, to be displayed within the theme-switcher dropdown */
|
||||
builtInThemes: [
|
||||
'default',
|
||||
'glass',
|
||||
'callisto',
|
||||
'material',
|
||||
'material-dark',
|
||||
@@ -85,6 +87,8 @@ module.exports = {
|
||||
'adventure-basic',
|
||||
'basic',
|
||||
'tama',
|
||||
'neomorphic',
|
||||
'glass-2',
|
||||
],
|
||||
/* Default color options for the theme configurator swatches */
|
||||
swatches: [
|
||||
@@ -112,7 +116,7 @@ module.exports = {
|
||||
/* Key names for local storage identifiers */
|
||||
localStorageKeys: {
|
||||
LANGUAGE: 'language',
|
||||
HIDE_WELCOME_BANNER: 'hideWelcomeHelpers',
|
||||
HIDE_INFO_NOTIFICATION: 'hideWelcomeHelpers',
|
||||
LAYOUT_ORIENTATION: 'layoutOrientation',
|
||||
COLLAPSE_STATE: 'collapseState',
|
||||
ICON_SIZE: 'iconSize',
|
||||
@@ -120,6 +124,7 @@ module.exports = {
|
||||
PRIMARY_THEME: 'primaryTheme',
|
||||
CUSTOM_COLORS: 'customColors',
|
||||
CONF_SECTIONS: 'confSections',
|
||||
CONF_PAGES: 'confPages',
|
||||
CONF_WIDGETS: 'confSections',
|
||||
PAGE_INFO: 'pageInfo',
|
||||
APP_CONFIG: 'appConfig',
|
||||
@@ -130,6 +135,7 @@ module.exports = {
|
||||
MOST_USED: 'mostUsed',
|
||||
LAST_USED: 'lastUsed',
|
||||
KEYCLOAK_INFO: 'keycloakInfo',
|
||||
DISABLE_CRITICAL_WARNING: 'disableCriticalWarning',
|
||||
},
|
||||
/* Key names for cookie identifiers */
|
||||
cookieKeys: {
|
||||
@@ -184,7 +190,7 @@ module.exports = {
|
||||
// delay: { show: 380, hide: 0 },
|
||||
},
|
||||
/* Server location of the Backup & Sync cloud function */
|
||||
backupEndpoint: 'https://dashy-sync-service.as93.net',
|
||||
backupEndpoint: 'https://sync-service.dashy.to',
|
||||
/* Available services for fetching favicon icon for user apps */
|
||||
faviconApiEndpoints: {
|
||||
allesedv: 'https://f1.allesedv.com/128/$URL',
|
||||
@@ -215,7 +221,7 @@ module.exports = {
|
||||
/* API endpoints for widgets that need to fetch external data */
|
||||
widgetApiEndpoints: {
|
||||
anonAddy: 'https://app.anonaddy.com',
|
||||
astronomyPictureOfTheDay: 'https://go-apod.herokuapp.com/apod',
|
||||
astronomyPictureOfTheDay: 'https://apod.as93.net/apod',
|
||||
blacklistCheck: 'https://api.blacklistchecker.com/check',
|
||||
codeStats: 'https://codestats.net/',
|
||||
covidStats: 'https://disease.sh/v3/covid-19',
|
||||
|
||||
@@ -19,6 +19,7 @@ import pl from '@/assets/locales/pl.json';
|
||||
import pt from '@/assets/locales/pt.json';
|
||||
import gl from '@/assets/locales/gl.json';
|
||||
import ru from '@/assets/locales/ru.json';
|
||||
import ro from '@/assets/locales/ro.json';
|
||||
import sk from '@/assets/locales/sk.json';
|
||||
import sl from '@/assets/locales/sl.json';
|
||||
import sv from '@/assets/locales/sv.json';
|
||||
@@ -151,6 +152,12 @@ export const languages = [
|
||||
locale: ru,
|
||||
flag: '🇷🇺',
|
||||
},
|
||||
{ // Romanian
|
||||
name: 'Romana',
|
||||
code: 'ro',
|
||||
locale: ro,
|
||||
flag: '🇷🇴',
|
||||
},
|
||||
{ // Slovak
|
||||
name: 'Slovenčina',
|
||||
code: 'sk',
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
<section class="license">
|
||||
<h2>License</h2>
|
||||
<code>
|
||||
Copyright © 2021 Alicia Sykes (https://aliciasykes.com)
|
||||
Copyright © {{new Date().getFullYear()}} Alicia Sykes (https://aliciasykes.com)
|
||||
</code>
|
||||
<br><br>
|
||||
<code>
|
||||
|
||||
@@ -19,14 +19,7 @@
|
||||
</router-link>
|
||||
</div>
|
||||
<!-- Main content, section for each group of items -->
|
||||
<div v-if="checkTheresData(sections) || isEditMode"
|
||||
:class="`item-group-container `
|
||||
+ `orientation-${layout} `
|
||||
+ `item-size-${itemSizeBound} `
|
||||
+ (isEditMode ? 'edit-mode ' : '')
|
||||
+ (singleSectionView ? 'single-section-view ' : '')
|
||||
+ (this.colCount ? `col-count-${this.colCount} ` : '')"
|
||||
>
|
||||
<div v-if="checkTheresData(sections) || isEditMode" :class="computedClass">
|
||||
<template v-for="(section, index) in filteredSections">
|
||||
<Section
|
||||
:key="index"
|
||||
@@ -34,7 +27,7 @@
|
||||
:title="section.name"
|
||||
:icon="section.icon || undefined"
|
||||
:displayData="getDisplayData(section)"
|
||||
:groupId="`${pageId}-section-${index}`"
|
||||
:groupId="makeSectionId(section)"
|
||||
:items="section.filteredItems"
|
||||
:widgets="section.widgets"
|
||||
:searchTerm="searchValue"
|
||||
@@ -56,6 +49,8 @@
|
||||
<EditModeSaveMenu v-if="isEditMode" />
|
||||
<!-- Modal for viewing and exporting configuration file -->
|
||||
<ExportConfigMenu />
|
||||
<!-- Shows pertinent info -->
|
||||
<NotificationThing v-if="$store.state.isUsingLocalConfig"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -66,8 +61,9 @@ import Section from '@/components/LinkItems/Section.vue';
|
||||
import EditModeSaveMenu from '@/components/InteractiveEditor/EditModeSaveMenu.vue';
|
||||
import ExportConfigMenu from '@/components/InteractiveEditor/ExportConfigMenu.vue';
|
||||
import AddNewSection from '@/components/InteractiveEditor/AddNewSectionLauncher.vue';
|
||||
import NotificationThing from '@/components/Settings/LocalConfigWarning.vue';
|
||||
import StoreKeys from '@/utils/StoreMutations';
|
||||
import { localStorageKeys, modalNames } from '@/utils/defaults';
|
||||
import { modalNames } from '@/utils/defaults';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import BackIcon from '@/assets/interface-icons/back-arrow.svg';
|
||||
|
||||
@@ -79,6 +75,7 @@ export default {
|
||||
EditModeSaveMenu,
|
||||
ExportConfigMenu,
|
||||
AddNewSection,
|
||||
NotificationThing,
|
||||
Section,
|
||||
BackIcon,
|
||||
},
|
||||
@@ -116,15 +113,13 @@ export default {
|
||||
iconSize() {
|
||||
return this.$store.getters.iconSize;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
layoutOrientation(layout) {
|
||||
localStorage.setItem(localStorageKeys.LAYOUT_ORIENTATION, layout);
|
||||
this.layout = layout;
|
||||
},
|
||||
iconSize(size) {
|
||||
localStorage.setItem(localStorageKeys.ICON_SIZE, size);
|
||||
this.itemSizeBound = size;
|
||||
computedClass() {
|
||||
let classes = 'item-group-container '
|
||||
+ ` orientation-${this.$store.getters.layout} item-size-${this.itemSizeBound}`;
|
||||
if (this.isEditMode) classes += ' edit-mode';
|
||||
if (this.singleSectionView) classes += ' single-section-view';
|
||||
if (this.colCount) classes += ` col-count-${this.colCount}`;
|
||||
return classes;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
:class="`item-group-container ${!tabbedView ? 'showing-all' : ''}`">
|
||||
<!-- Section heading buttons -->
|
||||
<MinimalHeading
|
||||
v-for="(section, index) in getSections(sections)"
|
||||
v-for="(section, index) in sections"
|
||||
:key="`heading-${index}`"
|
||||
:index="index"
|
||||
:title="section.name"
|
||||
@@ -29,12 +29,12 @@
|
||||
/>
|
||||
<!-- Section item groups -->
|
||||
<MinimalSection
|
||||
v-for="(section, index) in getSections(sections)"
|
||||
v-for="(section, index) in sections"
|
||||
:key="`body-${index}`"
|
||||
:index="index"
|
||||
:title="section.name"
|
||||
:icon="section.icon || undefined"
|
||||
:groupId="`section-${index}`"
|
||||
:groupId="makeSectionId(section)"
|
||||
:items="filterTiles(section.items)"
|
||||
:widgets="section.widgets"
|
||||
:selected="selectedSection === index"
|
||||
@@ -57,7 +57,6 @@ import HomeMixin from '@/mixins/HomeMixin';
|
||||
import MinimalSection from '@/components/MinimalView/MinimalSection.vue';
|
||||
import MinimalHeading from '@/components/MinimalView/MinimalHeading.vue';
|
||||
import MinimalSearch from '@/components/MinimalView/MinimalSearch.vue';
|
||||
import { localStorageKeys } from '@/utils/defaults';
|
||||
import ConfigLauncher from '@/components/Settings/ConfigLauncher';
|
||||
|
||||
export default {
|
||||
@@ -83,17 +82,6 @@ export default {
|
||||
sectionSelected(index) {
|
||||
this.selectedSection = index;
|
||||
},
|
||||
/* Returns sections from local storage if available, otherwise uses the conf.yml */
|
||||
getSections(sections) {
|
||||
// If the user has stored sections in local storage, return those
|
||||
const localSections = localStorage[localStorageKeys.CONF_SECTIONS];
|
||||
if (localSections) {
|
||||
const json = JSON.parse(localSections);
|
||||
if (json.length >= 1) return json;
|
||||
}
|
||||
// Otherwise, return the usuall data from conf.yml
|
||||
return sections;
|
||||
},
|
||||
/* Clears input field, once a searched item is opened */
|
||||
finishedSearching() {
|
||||
if (this.$refs.filterComp) this.$refs.filterComp.clearMinFilterInput();
|
||||
|
||||
@@ -19,7 +19,6 @@ import WebContent from '@/components/Workspace/WebContent';
|
||||
import WidgetView from '@/components/Workspace/WidgetView';
|
||||
import MultiTaskingWebComtent from '@/components/Workspace/MultiTaskingWebComtent';
|
||||
import Defaults from '@/utils/defaults';
|
||||
import { GetTheme, ApplyLocalTheme, ApplyCustomVariables } from '@/utils/ThemeHelper';
|
||||
|
||||
export default {
|
||||
name: 'Workspace',
|
||||
@@ -27,9 +26,6 @@ export default {
|
||||
data: () => ({
|
||||
url: '',
|
||||
widgets: null,
|
||||
GetTheme,
|
||||
ApplyLocalTheme,
|
||||
ApplyCustomVariables,
|
||||
}),
|
||||
computed: {
|
||||
sections() {
|
||||
@@ -89,6 +85,9 @@ export default {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.work-space {
|
||||
min-height: calc(100vh - var(--footer-height));
|
||||
min-height: fit-content;
|
||||
}
|
||||
:global(footer) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user