Merge branch 'master' of github.com:Lissy93/dashy into FEATURE/more-widgets
This commit is contained in:
1
src/assets/interface-icons/open-clipboard.svg
Normal file
1
src/assets/interface-icons/open-clipboard.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="paste" class="svg-inline--fa fa-paste fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M433.941 193.941l-51.882-51.882A48 48 0 0 0 348.118 128H320V80c0-26.51-21.49-48-48-48h-61.414C201.582 13.098 182.294 0 160 0s-41.582 13.098-50.586 32H48C21.49 32 0 53.49 0 80v288c0 26.51 21.49 48 48 48h80v48c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48V227.882a48 48 0 0 0-14.059-33.941zm-84.066-16.184l48.368 48.368a6 6 0 0 1 1.757 4.243V240h-64v-64h9.632a6 6 0 0 1 4.243 1.757zM160 38c9.941 0 18 8.059 18 18s-8.059 18-18 18-18-8.059-18-18 8.059-18 18-18zm-32 138v192H54a6 6 0 0 1-6-6V86a6 6 0 0 1 6-6h55.414c9.004 18.902 28.292 32 50.586 32s41.582-13.098 50.586-32H266a6 6 0 0 1 6 6v42h-96c-26.51 0-48 21.49-48 48zm266 288H182a6 6 0 0 1-6-6V182a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v170a6 6 0 0 1-6 6z"></path></svg>
|
||||
|
After Width: | Height: | Size: 949 B |
@@ -185,10 +185,12 @@
|
||||
"newtab": "New Tab",
|
||||
"modal": "Pop-Up Modal",
|
||||
"workspace": "Workspace View",
|
||||
"clipboard": "Copy to Clipboard",
|
||||
"options-section-title": "Options",
|
||||
"edit-item": "Edit",
|
||||
"move-item": "Copy or Move",
|
||||
"remove-item": "Remove"
|
||||
"remove-item": "Remove",
|
||||
"copied-toast": "URL has been copied to clipboard"
|
||||
},
|
||||
"section": {
|
||||
"open-section": "Open Section",
|
||||
|
||||
@@ -1,248 +1,284 @@
|
||||
{
|
||||
"home": {
|
||||
"no-results": "Inga sökresultat",
|
||||
"no-data": "Ingen data konfigurerad"
|
||||
"home":{
|
||||
"no-results":"Inga sökresultat",
|
||||
"no-data":"Ingen data konfigurerad",
|
||||
"no-items-section":"Inga objekt att visa än"
|
||||
},
|
||||
"search":{
|
||||
"search-label":"Sök",
|
||||
"search-placeholder":"Börja skriva för att filtrera",
|
||||
"clear-search-tooltip":"Rensa sök",
|
||||
"enter-to-search-web":"Tryck på Retur för att söka på webben"
|
||||
},
|
||||
"login":{
|
||||
"title":"Dashy",
|
||||
"username-label":"Användarnamn",
|
||||
"password-label":"Lösenord",
|
||||
"login-button":"Logga in",
|
||||
"remember-me-label":"Kom ihåg mig",
|
||||
"remember-me-never":"Aldrig",
|
||||
"remember-me-hour":"4 Timmar",
|
||||
"remember-me-day":"1 Dag",
|
||||
"remember-me-week":"1 Vecka",
|
||||
"remember-me-long-time":"Länge",
|
||||
"error-missing-username":"Användarnamn saknas",
|
||||
"error-missing-password":"Lösenord saknas",
|
||||
"error-incorrect-username":"Användaren hittas inte",
|
||||
"error-incorrect-password":"Fel lösenord",
|
||||
"success-message":"Loggar in...",
|
||||
"logout-message":"Utloggad",
|
||||
"already-logged-in-title":"Redan inloggad",
|
||||
"already-logged-in-text":"Du är inloggad som",
|
||||
"proceed-to-dashboard":"Fortsätt till Dashboard",
|
||||
"log-out-button":"Logga ut",
|
||||
"proceed-guest-button":"Fortsätt som Gäst"
|
||||
},
|
||||
"config":{
|
||||
"main-tab":"Huvudmeny",
|
||||
"view-config-tab":"Visa konfiguration",
|
||||
"edit-config-tab":"Redigera konfiguration",
|
||||
"custom-css-tab":"Egna stilmallar",
|
||||
"heading":"Konfigurationsalternativ",
|
||||
"download-config-button":"Visa / Exportera konfiguration",
|
||||
"edit-config-button":"Redigera konfiguration",
|
||||
"edit-css-button":"Redigera Custom CSS",
|
||||
"cloud-sync-button":"Aktivera molnsynk",
|
||||
"edit-cloud-sync-button":"Redigera molnsynk",
|
||||
"rebuild-app-button":"Återuppbygga appen",
|
||||
"change-language-button":"Ändra appspråk",
|
||||
"reset-settings-button":"Återställ lokala inställningar",
|
||||
"app-info-button":"Appinfo",
|
||||
"backup-note":"Det rekommenderas att du gör en säkerhetskopia av din konfiguration innan du gör ändringar.",
|
||||
"reset-config-msg-l1":"Detta tar bort alla användarinställningar från lokal lagring, men påverkar inte din 'conf.yml'-fil",
|
||||
"reset-config-msg-l2":"Du bör först göra en säkerhetskopia av alla ändringar du har gjort lokalt, om du vill använda dem i framtiden.",
|
||||
"reset-config-msg-l3":"Är du säker på att du vill fortsätta?",
|
||||
"data-cleared-msg":"Datarensning har lyckats",
|
||||
"actions-label":"Åtgärder",
|
||||
"copy-config-label":"Kopiera konfiguration",
|
||||
"data-copied-msg":"Konfiguration har kopierats till urklipp",
|
||||
"reset-config-label":"Återställ konfiguration",
|
||||
"css-save-btn":"Spara ändringar",
|
||||
"css-note-label":"Not",
|
||||
"css-note-l1":"Du måste uppdatera sidan för att dina ändringar ska gälla.",
|
||||
"css-note-l2":"Styles overrides lagras bara lokalt, så det rekommenderas att du gör en kopia av din CSS.",
|
||||
"css-note-l3":"För att ta bort alla egna stilmallar, radera innehållet och tryck på Spara ändringar"
|
||||
},
|
||||
"alternate-views":{
|
||||
"alternate-view-heading":"Ändra vy",
|
||||
"default":"Standard",
|
||||
"workspace":"Workspace",
|
||||
"minimal":"Minimal"
|
||||
},
|
||||
"settings":{
|
||||
"theme-label":"Tema",
|
||||
"layout-label":"Layout",
|
||||
"layout-auto":"Auto",
|
||||
"layout-horizontal":"Vågrät",
|
||||
"layout-vertical":"Lodrät",
|
||||
"item-size-label":"Storlek",
|
||||
"item-size-small":"Liten",
|
||||
"item-size-medium":"Mellan",
|
||||
"item-size-large":"Stor",
|
||||
"config-launcher-label":"Konfig",
|
||||
"config-launcher-tooltip":"Uppdatera konfiguration",
|
||||
"sign-out-tooltip":"Logga ut",
|
||||
"sign-in-tooltip":"Logga in",
|
||||
"sign-in-welcome":"Hej {username}!"
|
||||
},
|
||||
"updates":{
|
||||
"app-version-note":"Dashy-version",
|
||||
"up-to-date":"Uppdaterat",
|
||||
"out-of-date":"Uppdatering finns",
|
||||
"unsupported-version-l1":"Du använder en icke-stödd version av Dashy",
|
||||
"unsupported-version-l2":"För den bästa upplevelsen och de senaste säkerhetskorrigeringarna, uppdatera till"
|
||||
},
|
||||
"language-switcher":{
|
||||
"title":"Ändra appspråk",
|
||||
"dropdown-label":"Välj språk",
|
||||
"save-button":"Spara",
|
||||
"success-msg":"Språket har ändrats till"
|
||||
},
|
||||
"theme-maker":{
|
||||
"title":"Temakonfigurator",
|
||||
"export-button":"Exportera egendefinierade variabler",
|
||||
"reset-button":"Återställ stilmallar för",
|
||||
"show-all-button":"Vissa alla variabler",
|
||||
"change-fonts-button":"Ändra typsnitt",
|
||||
"save-button":"Spara",
|
||||
"cancel-button":"Avbryt",
|
||||
"saved-toast":"{theme} har uppdaterats",
|
||||
"copied-toast":"Temadatan för {theme} har kopierats till urklipp",
|
||||
"reset-toast":"Egna färger för {theme} har tagits bort"
|
||||
},
|
||||
"config-editor":{
|
||||
"save-location-label":"Sparningsplats",
|
||||
"location-local-label":"Tillämpa lokalt",
|
||||
"location-disk-label":"Skriv ändringar till konfigurationsfil",
|
||||
"save-button":"Spara ändringar",
|
||||
"preview-button":"Förhandsgranska ändringar",
|
||||
"valid-label":"Konfigurationen är giltig",
|
||||
"status-success-msg":"Åtgärden slutförts",
|
||||
"status-fail-msg":"Åtgärden misslyckats",
|
||||
"success-msg-disk":"Konfigurationsfil har skrivits till disk utan problem",
|
||||
"success-msg-local":"Lokala ändringar har sparats utan problem",
|
||||
"success-note-l1":"Återskapa",
|
||||
"success-note-l2":"Detta kan ta upp till en minut.",
|
||||
"success-note-l3":"Du måste uppdatera sidan för att ändringar ska gälla",
|
||||
"error-msg-save-mode":"Välj Lagringsläge: Lokalt eller Fil",
|
||||
"error-msg-cannot-save":"Ett fel uppstod när konfigurationen skulle sparas",
|
||||
"error-msg-bad-json":"Fel i JSON, möjligen felformaterat",
|
||||
"warning-msg-validation":"Valideringsvarning",
|
||||
"not-admin-note":"Du kan inte skriva ändringar till disk, eftersom du inte är inloggad som admin"
|
||||
},
|
||||
"app-rebuild":{
|
||||
"title":"Återskapa appen",
|
||||
"rebuild-note-l1":"Appen måste återskapas för att ändringar som skrivits till filen conf.yml ska gälla.",
|
||||
"rebuild-note-l2":"Detta bör ske automatiskt, men om det inte har gjort det kan du aktivera det manuellt här.",
|
||||
"rebuild-note-l3":"Detta krävs inte för ändringar som lagras lokalt.",
|
||||
"rebuild-button":"Återskapa",
|
||||
"rebuilding-status-1":"Återskapar...",
|
||||
"rebuilding-status-2":"Detta kan ta några minuter",
|
||||
"error-permission":"Du har inte behörighet att utföra denna åtgärd",
|
||||
"success-msg":"Återskapning lyckats",
|
||||
"fail-msg":"Återskapning misslyckats",
|
||||
"reload-note":"En omladdning av sidan krävs nu för att ändringarna ska gälla",
|
||||
"reload-button":"Ladda om sidan"
|
||||
},
|
||||
"cloud-sync":{
|
||||
"title":"Molnsäkerhetskopiering och återställning",
|
||||
"intro-l1":"Molnsäkerhetskopiering och återställning är en valfri funktion som gör att du kan ladda upp din konfiguration till internet och sedan återställa den på någon annan enhet eller instans av Dashy.",
|
||||
"intro-l2":"All data är fullständigt end-to-end krypterad med AES, med ditt lösenord som nyckel.",
|
||||
"intro-l3":"För mer information, vänligen se",
|
||||
"backup-title-setup":"Gör en säkerhetskopia",
|
||||
"backup-title-update":"Uppdatera säkerhetskopia",
|
||||
"password-label-setup":"Välj lösenord",
|
||||
"password-label-update":"Ange ditt lösenord",
|
||||
"backup-button-setup":"Säkerhetskopiering",
|
||||
"backup-button-update":"Uppdatera säkerhetskopia",
|
||||
"backup-id-label":"Ditt säkerhetskopierings-ID",
|
||||
"backup-id-note":"Detta används för att återställa från säkerhetskopior senare. Så förvara det tillsammans med ditt lösenord någonstans säkert.",
|
||||
"restore-title":"Återställ en säkerhetskopia",
|
||||
"restore-id-label":"Återställ ID",
|
||||
"restore-password-label":"Lösenord",
|
||||
"restore-button":"Återställ",
|
||||
"backup-missing-password":"Lösenord saknas",
|
||||
"backup-error-unknown":"Begäran kan inte behandlas",
|
||||
"backup-error-password":"Fel lösenord. Vänligen ange ditt aktuella lösenord.",
|
||||
"backup-success-msg":"Slutfört utan problem",
|
||||
"restore-success-msg":"Konfigurationen har återställts utan problem"
|
||||
},
|
||||
"menu":{
|
||||
"open-section-title":"Öppna i",
|
||||
"sametab":"Denna flik",
|
||||
"newtab":"Ny flik",
|
||||
"modal":"Pop-Up Modal",
|
||||
"workspace":"Workspace-vy",
|
||||
"options-section-title":"Alternativ",
|
||||
"edit-item":"Redigera",
|
||||
"move-item":"Kopiera eller flytta",
|
||||
"remove-item":"Ta bort"
|
||||
},
|
||||
"context-menus":{
|
||||
"item":{
|
||||
"open-section-title":"Öppna i",
|
||||
"sametab":"Denna flik",
|
||||
"newtab":"Ny flik",
|
||||
"modal":"Pop-Up Modal",
|
||||
"workspace":"Workspace View",
|
||||
"options-section-title":"Alternativ",
|
||||
"edit-item":"Redigera",
|
||||
"move-item":"Kopiera eller flytta",
|
||||
"remove-item":"Ta bort"
|
||||
},
|
||||
"search": {
|
||||
"search-label": "Sök",
|
||||
"search-placeholder": "Börja skriva för att filtrera",
|
||||
"clear-search-tooltip": "Rensa sök",
|
||||
"enter-to-search-web": "Tryck på Retur för att söka på webben"
|
||||
},
|
||||
"login": {
|
||||
"title": "Dashy",
|
||||
"username-label": "Användarnamn",
|
||||
"password-label": "Lösenord",
|
||||
"login-button": "Logga in",
|
||||
"remember-me-label": "Kom ihåg mig",
|
||||
"remember-me-never": "Aldrig",
|
||||
"remember-me-hour": "4 Timmar",
|
||||
"remember-me-day": "1 Dag",
|
||||
"remember-me-week": "1 Vecka",
|
||||
"remember-me-long-time": "Länge",
|
||||
"error-missing-username": "Användarnamn saknas",
|
||||
"error-missing-password": "Lösenord saknas",
|
||||
"error-incorrect-username": "Användaren hittas inte",
|
||||
"error-incorrect-password": "Fel lösenord",
|
||||
"success-message": "Loggar in...",
|
||||
"logout-message": "Utloggad",
|
||||
"already-logged-in-title": "Redan inloggad",
|
||||
"already-logged-in-text": "Du är inloggad som",
|
||||
"proceed-to-dashboard": "Fortsätt till Dashboard",
|
||||
"log-out-button": "Logga ut",
|
||||
"proceed-guest-button": "Fortsätt som Gäst"
|
||||
},
|
||||
"config": {
|
||||
"main-tab": "Huvudmeny",
|
||||
"view-config-tab": "Visa konfiguration",
|
||||
"edit-config-tab": "Redigera konfiguration",
|
||||
"custom-css-tab": "Egendefinierade stilmallar",
|
||||
"heading": "Konfigurationsalternativ",
|
||||
"download-config-button": "Visa / Exportera konfiguration",
|
||||
"edit-config-button": "Redigera konfiguration",
|
||||
"edit-css-button": "Redigera egendefinierad CSS",
|
||||
"cloud-sync-button": "Aktivera molnsynkronisering",
|
||||
"edit-cloud-sync-button": "Redigera molnsynkronisering",
|
||||
"rebuild-app-button": "Återuppbygga appen",
|
||||
"change-language-button": "Ändra appspråk",
|
||||
"reset-settings-button": "Återställ lokala inställningar",
|
||||
"app-info-button": "Appinfo",
|
||||
"backup-note": "Det rekommenderas att du gör en säkerhetskopia av din konfiguration innan du gör ändringar.",
|
||||
"reset-config-msg-l1": "Detta tar bort alla användarinställningar från lokal lagring, men påverkar inte din 'conf.yml'-fil",
|
||||
"reset-config-msg-l2": "Du bör först göra en säkerhetskopia av alla ändringar du har gjort lokalt, om du vill använda dem i framtiden.",
|
||||
"reset-config-msg-l3": "Är du säker på att du vill fortsätta?",
|
||||
"data-cleared-msg": "Datarensning har lyckats",
|
||||
"actions-label": "Åtgärder",
|
||||
"copy-config-label": "Kopiera konfiguration",
|
||||
"data-copied-msg": "Konfiguration har kopierats till urklipp",
|
||||
"reset-config-label": "Återställ konfiguration",
|
||||
"css-save-btn": "Spara ändringar",
|
||||
"css-note-label": "Not",
|
||||
"css-note-l1": "Du måste uppdatera sidan för att dina ändringar ska gälla.",
|
||||
"css-note-l2": "Styles overrides lagras bara lokalt, så det rekommenderas att du gör en kopia av din CSS.",
|
||||
"css-note-l3": "För att ta bort alla egendefinierade stilmallar, radera innehållet och tryck på Spara ändringar"
|
||||
},
|
||||
"alternate-views": {
|
||||
"alternate-view-heading": "Ändra vy",
|
||||
"default": "Standard",
|
||||
"workspace": "Workspace",
|
||||
"minimal": "Minimal"
|
||||
},
|
||||
"settings": {
|
||||
"theme-label": "Tema",
|
||||
"layout-label": "Layout",
|
||||
"layout-auto": "Auto",
|
||||
"layout-horizontal": "Vågrät",
|
||||
"layout-vertical": "Lodrät",
|
||||
"item-size-label": "Storlek",
|
||||
"item-size-small": "Liten",
|
||||
"item-size-medium": "Mellan",
|
||||
"item-size-large": "Stor",
|
||||
"config-launcher-label": "Konfig",
|
||||
"config-launcher-tooltip": "Uppdatera konfiguration",
|
||||
"sign-out-tooltip": "Logga ut",
|
||||
"sign-in-tooltip": "Logga in",
|
||||
"sign-in-welcome": "Hej {username}!"
|
||||
},
|
||||
"updates": {
|
||||
"app-version-note": "Dashy-version",
|
||||
"up-to-date": "Uppdaterat",
|
||||
"out-of-date": "Uppdatering finns",
|
||||
"unsupported-version-l1": "Du använder en icke-stödd version av Dashy",
|
||||
"unsupported-version-l2": "För den bästa upplevelsen och de senaste säkerhetskorrigeringarna, uppdatera till"
|
||||
},
|
||||
"language-switcher": {
|
||||
"title": "Ändra appspråk",
|
||||
"dropdown-label": "Välj språk",
|
||||
"save-button": "Spara",
|
||||
"success-msg": "Språket har ändrats till"
|
||||
},
|
||||
"theme-maker": {
|
||||
"title": "Temakonfigurator",
|
||||
"export-button": "Exportera egendefinierade variabler",
|
||||
"reset-button": "Återställ stilmallar för",
|
||||
"show-all-button": "Visa alla variabler",
|
||||
"change-fonts-button": "Ändra typsnitt",
|
||||
"save-button": "Spara",
|
||||
"cancel-button": "Avbryt",
|
||||
"saved-toast": "Uppdatering av {theme} har lyckats",
|
||||
"copied-toast": "Temadatan för {theme} har kopierats till urklipp",
|
||||
"reset-toast": "Egendefinierade färger för {theme} har tagits bort"
|
||||
},
|
||||
"config-editor": {
|
||||
"save-location-label": "Sparningsplats",
|
||||
"location-local-label": "Tillämpa lokalt",
|
||||
"location-disk-label": "Skriv ändringar till konfigurationsfil",
|
||||
"save-button": "Spara ändringar",
|
||||
"preview-button": "Förhandsgranska ändringar",
|
||||
"valid-label": "Konfigurationen är giltig",
|
||||
"status-success-msg": "Åtgärden slutförts",
|
||||
"status-fail-msg": "Åtgärden misslyckats",
|
||||
"success-msg-disk": "Konfigurationsfil har skrivits till disk utan problem",
|
||||
"success-msg-local": "Lokala ändringar har sparats utan problem",
|
||||
"success-note-l1": "Återskapa",
|
||||
"success-note-l2": "Detta kan ta upp till en minut.",
|
||||
"success-note-l3": "Du måste uppdatera sidan för att ändringar ska gälla",
|
||||
"error-msg-save-mode": "Välj Lagringsläge: Lokalt eller Fil",
|
||||
"error-msg-cannot-save": "Ett fel uppstod när konfigurationen skulle sparas",
|
||||
"error-msg-bad-json": "Fel i JSON, möjligen felformaterat",
|
||||
"warning-msg-validation": "Valideringsvarning",
|
||||
"not-admin-note": "Du kan inte skriva ändringar till disk, eftersom du inte är inloggad som admin"
|
||||
},
|
||||
"app-rebuild": {
|
||||
"title": "Återskapa appen",
|
||||
"rebuild-note-l1": "Appen måste återskapas för att ändringar som skrivits till filen conf.yml ska gälla.",
|
||||
"rebuild-note-l2": "Detta bör ske automatiskt, men om det inte har gjort det kan du aktivera det manuellt här.",
|
||||
"rebuild-note-l3": "Detta krävs inte för ändringar som lagras lokalt.",
|
||||
"rebuild-button": "Återskapa",
|
||||
"rebuilding-status-1": "Återskapar...",
|
||||
"rebuilding-status-2": "Detta kan ta några minuter",
|
||||
"error-permission": "Du har inte behörighet att utföra denna åtgärd",
|
||||
"success-msg": "Återskapning lyckats",
|
||||
"fail-msg": "Återskapning misslyckats",
|
||||
"reload-note": "En omladdning av sidan krävs nu för att ändringarna ska gälla",
|
||||
"reload-button": "Ladda om sidan"
|
||||
},
|
||||
"cloud-sync": {
|
||||
"title": "Molnsäkerhetskopiering och återställning",
|
||||
"intro-l1": "Molnsäkerhetskopiering och återställning är en valfri funktion som gör att du kan ladda upp din konfiguration till internet och sedan återställa den på någon annan enhet eller instans av Dashy.",
|
||||
"intro-l2": "All data är fullständigt end-to-end krypterad med AES, med ditt lösenord som nyckel.",
|
||||
"intro-l3": "För mer information, vänligen se",
|
||||
"backup-title-setup": "Gör en säkerhetskopia",
|
||||
"backup-title-update": "Uppdatera säkerhetskopia",
|
||||
"password-label-setup": "Välj lösenord",
|
||||
"password-label-update": "Ange ditt lösenord",
|
||||
"backup-button-setup": "Säkerhetskopiering",
|
||||
"backup-button-update": "Uppdatera säkerhetskopia",
|
||||
"backup-id-label": "Ditt säkerhetskopierings-ID",
|
||||
"backup-id-note": "Detta används för att återställa från säkerhetskopior senare. Så förvara det tillsammans med ditt lösenord någonstans säkert.",
|
||||
"restore-title": "Återställ en säkerhetskopia",
|
||||
"restore-id-label": "Återställ ID",
|
||||
"restore-password-label": "Lösenord",
|
||||
"restore-button": "Återställ",
|
||||
"backup-missing-password": "Lösenord saknas",
|
||||
"backup-error-unknown": "Begäran kan inte behandlas",
|
||||
"backup-error-password": "Fel lösenord. Vänligen ange ditt aktuella lösenord.",
|
||||
"backup-success-msg": "Slutfört utan problem",
|
||||
"restore-success-msg": "Konfigurationen har återställts utan problem"
|
||||
},
|
||||
"menu": {
|
||||
"open-section-title": "Öppna i",
|
||||
"sametab": "Denna flik",
|
||||
"newtab": "Ny flik",
|
||||
"modal": "Pop-Up Modal",
|
||||
"workspace": "Workspace-vy",
|
||||
"options-section-title": "Alternativ",
|
||||
"edit-item": "Redigera",
|
||||
"move-item": "Kopiera eller flytta",
|
||||
"remove-item": "Ta bort"
|
||||
},
|
||||
"context-menus": {
|
||||
"item": {
|
||||
"open-section-title": "Öppna i",
|
||||
"sametab": "Denna flik",
|
||||
"newtab": "Ny flik",
|
||||
"modal": "Pop-Up Modal",
|
||||
"workspace": "Workspace View",
|
||||
"options-section-title": "Alternativ",
|
||||
"edit-item": "Redigera",
|
||||
"move-item": "Kopiera eller flytta",
|
||||
"remove-item": "Ta bort"
|
||||
},
|
||||
"section": {
|
||||
"open-section": "Öppna sektion",
|
||||
"edit-section": "Redigera",
|
||||
"move-section": "Flytta till",
|
||||
"remove-section": "Ta bort"
|
||||
}
|
||||
},
|
||||
"interactive-editor": {
|
||||
"menu": {
|
||||
"start-editing-tooltip": "Öppna den interaktiva redigeraren",
|
||||
"edit-site-data-subheading": "Redigera webbplatsinformation",
|
||||
"edit-page-info-btn": "Redigera sidinformation",
|
||||
"edit-page-info-tooltip": "Appnamn, beskrivning, navigeringslänkar, sidfotstext, etc",
|
||||
"edit-app-config-btn": "Redigera appkonfiguration",
|
||||
"edit-app-config-tooltip": "Övriga appkonfigurationsalternativ",
|
||||
"config-save-methods-subheading": "Alternativ för konfigurationssparande",
|
||||
"save-locally-btn": "Spara lokalt",
|
||||
"save-locally-tooltip": "Spara konfigurationen lokalt, till webbläsarens lagring. Detta påverkar inte din konfigurationsfil, men ändringarna sparas bara på denna enhet",
|
||||
"save-disk-btn": "Spara till disk",
|
||||
"save-disk-tooltip": "Spara konfiguration to conf.yml-filen på disk. Detta kommer att säkerhetskopiera och sedan skriva över din befintliga konfiguration",
|
||||
"export-config-btn": "Exportera konfiguration",
|
||||
"export-config-tooltip": "Visa och exportera den nya konfigurationen, antingen till fil eller urklipp",
|
||||
"cloud-backup-btn": "Säkerhetskopiera till molnet",
|
||||
"cloud-backup-tooltip": "Spara krypterad säkerhetskopia av konfigurationen i molnet",
|
||||
"edit-raw-config-btn": "Redigera raw-konfiguration",
|
||||
"edit-raw-config-tooltip": "Visa och redigera raw-konfiguration via JSON-redigeraren",
|
||||
"cancel-changes-btn": "Avbryt redigering",
|
||||
"cancel-changes-tooltip": "Radera nuvarande ändringar och lämna Redigeringsläge. Detta kommer in påverka din sparade konfiguration.",
|
||||
"edit-mode-name": "Redigeringsläge",
|
||||
"edit-mode-subtitle": "Du är i Redigeringsläge",
|
||||
"edit-mode-description": "Detta innebär att du kan göra ändringar i din konfiguration och förhandsgranska resultaten, men tills du sparar kommer inga av dina ändringar att bevaras.",
|
||||
"save-stage-btn": "Spara",
|
||||
"cancel-stage-btn": "Avbryt"
|
||||
},
|
||||
"edit-section": {
|
||||
"edit-section-title": "Redigera sektion",
|
||||
"add-section-title": "Lägg till ny sektion",
|
||||
"edit-tooltip": "Tryck för att redigera, eller högerklicka för fler alternativ",
|
||||
"remove-confirm": "Är du säker på att du vill ta bort denna sektion? Denna åtgärd kan ångras senare."
|
||||
},
|
||||
"edit-app-config": {
|
||||
"warning-msg-title": "Fortsätt med försiktighet",
|
||||
"warning-msg-l1": "Följande alternativ är för avancerade appkonfigurationer.",
|
||||
"warning-msg-l2": "Om du är osäker på något av fälten, vänligen kolla",
|
||||
"warning-msg-docs": "dokumentationen",
|
||||
"warning-msg-l3": "för att undvika oavsiktliga konsekvenser."
|
||||
},
|
||||
"export": {
|
||||
"export-title": "Exportera konfiguration",
|
||||
"copy-clipboard-btn": "Kopiera till urklipp",
|
||||
"copy-clipboard-tooltip": "Kopiera alla appkonfigurationer till systemets urklipp i YAML-format",
|
||||
"download-file-btn": "Ladda ned som fil",
|
||||
"download-file-tooltip": "Ladda ner alla appkonfigurationer till din enhet som en YAML-fil",
|
||||
"view-title": "Visa konfiguration"
|
||||
}
|
||||
"section":{
|
||||
"open-section":"Öppna sektion",
|
||||
"edit-section":"Redigera",
|
||||
"move-section":"Flytta till",
|
||||
"remove-section":"Ta bort"
|
||||
}
|
||||
}
|
||||
},
|
||||
"interactive-editor":{
|
||||
"menu":{
|
||||
"start-editing-tooltip":"Öppna den interaktiva redigeraren",
|
||||
"edit-site-data-subheading":"Redigera webbplatsinformation",
|
||||
"edit-page-info-btn":"Redigera sidinformation",
|
||||
"edit-page-info-tooltip":"Appnamn, beskrivning, navigeringslänkar, sidfotstext, etc",
|
||||
"edit-app-config-btn":"Redigera appkonfiguration",
|
||||
"edit-app-config-tooltip":"Övriga appkonfigurationsalternativ",
|
||||
"config-save-methods-subheading":"Alternativ för konfigurationssparande",
|
||||
"save-locally-btn":"Spara lokalt",
|
||||
"save-locally-tooltip":"Spara konfigurationen lokalt, till webbläsarens lagring. Detta påverkar inte din konfigurationsfil, men ändringarna sparas bara på denna enhet",
|
||||
"save-disk-btn":"Spara till disk",
|
||||
"save-disk-tooltip":"Spara konfiguration to conf.yml-filen på disk. Detta kommer att säkerhetskopiera och sedan skriva över din befintliga konfiguration",
|
||||
"export-config-btn":"Exportera konfiguration",
|
||||
"export-config-tooltip":"Visa och exportera den nya konfigurationen, antingen till fil eller urklipp",
|
||||
"cloud-backup-btn":"Säkerhetskopiera till molnet",
|
||||
"cloud-backup-tooltip":"Spara krypterad säkerhetskopia av konfigurationen i molnet",
|
||||
"edit-raw-config-btn":"Redigera raw-konfiguration",
|
||||
"edit-raw-config-tooltip":"Visa och redigera raw-konfiguration via JSON-redigeraren",
|
||||
"cancel-changes-btn":"Avbryt redigering",
|
||||
"cancel-changes-tooltip":"Radera nuvarande ändringar och lämna Redigeringsläge. Detta kommer in påverka din sparade konfiguration.",
|
||||
"edit-mode-name":"Redigeringsläge",
|
||||
"edit-mode-subtitle":"Du är i Redigeringsläge",
|
||||
"edit-mode-description":"Detta innebär att du kan göra ändringar i din konfiguration och förhandsgranska resultaten, men tills du sparar kommer inga av dina ändringar att bevaras.",
|
||||
"save-stage-btn":"Spara",
|
||||
"cancel-stage-btn":"Avbryt"
|
||||
},
|
||||
"edit-item":{
|
||||
"missing-title-err":"Objektet måste ha en titel"
|
||||
},
|
||||
"edit-section":{
|
||||
"edit-section-title":"Redigera sektion",
|
||||
"add-section-title":"Lägg till ny sektion",
|
||||
"edit-tooltip":"Tryck för att redigera, eller högerklicka för fler alternativ",
|
||||
"remove-confirm":"Är du säker på att du vill ta bort denna sektion? Denna åtgärd kan ångras senare."
|
||||
},
|
||||
"edit-app-config":{
|
||||
"warning-msg-title":"Fortsätt med försiktighet",
|
||||
"warning-msg-l1":"Följande alternativ är för avancerade appkonfigurationer.",
|
||||
"warning-msg-l2":"Om du är osäker på något av fälten, vänligen kolla",
|
||||
"warning-msg-docs":"dokumentationen",
|
||||
"warning-msg-l3":"för att undvika oavsiktliga konsekvenser."
|
||||
},
|
||||
"export":{
|
||||
"export-title":"Exportera konfiguration",
|
||||
"copy-clipboard-btn":"Kopiera till urklipp",
|
||||
"copy-clipboard-tooltip":"Kopiera alla appkonfigurationer till systemets urklipp i YAML-format",
|
||||
"download-file-btn":"Ladda ned som fil",
|
||||
"download-file-tooltip":"Ladda ner alla appkonfigurationer till din enhet som en YAML-fil",
|
||||
"view-title":"Visa konfiguration"
|
||||
}
|
||||
},
|
||||
"widgets":{
|
||||
"general":{
|
||||
"loading":"Laddar...",
|
||||
"show-more":"Visa mer info",
|
||||
"show-less":"Visa mindre",
|
||||
"open-link":"Läs mer"
|
||||
},
|
||||
"pi-hole":{
|
||||
"status-heading":"Status"
|
||||
},
|
||||
"stat-ping":{
|
||||
"up":"Online",
|
||||
"down":"Offline"
|
||||
},
|
||||
"net-data":{
|
||||
"cpu-chart-title":"CPU History",
|
||||
"mem-chart-title":"Memory Usage",
|
||||
"mem-breakdown-title":"Memory Breakdown",
|
||||
"load-chart-title":"System Load"
|
||||
},
|
||||
"system-info":{
|
||||
"uptime":"Uptime"
|
||||
},
|
||||
"flight-data":{
|
||||
"arrivals":"Ankomster",
|
||||
"departures":"Avgångar"
|
||||
},
|
||||
"tfl-status":{
|
||||
"good-service-all":"Good Service på alla linjer",
|
||||
"good-service-rest":"Good Service på alla övriga linjer"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +141,7 @@ export default {
|
||||
hyperLinkHref() {
|
||||
const nothing = '#';
|
||||
if (this.isEditMode) return nothing;
|
||||
const noAnchorNeeded = ['modal', 'workspace'];
|
||||
const noAnchorNeeded = ['modal', 'workspace', 'clipboard'];
|
||||
return noAnchorNeeded.includes(this.accumulatedTarget) ? nothing : this.url;
|
||||
},
|
||||
},
|
||||
@@ -174,6 +174,9 @@ export default {
|
||||
this.$emit('triggerModal', this.url);
|
||||
} else if (this.accumulatedTarget === 'workspace') {
|
||||
router.push({ name: 'workspace', query: { url: this.url } });
|
||||
} else if (this.accumulatedTarget === 'clipboard') {
|
||||
navigator.clipboard.writeText(this.url);
|
||||
this.$toasted.show(this.$t('context-menus.item.copied-toast'));
|
||||
} else {
|
||||
this.$emit('itemClicked');
|
||||
}
|
||||
@@ -226,6 +229,7 @@ export default {
|
||||
case 'top': return '"\\f102"';
|
||||
case 'modal': return '"\\f2d0"';
|
||||
case 'workspace': return '"\\f0b1"';
|
||||
case 'clipboard': return '"\\f0ea"';
|
||||
default: return '"\\f054"';
|
||||
}
|
||||
},
|
||||
@@ -279,6 +283,10 @@ export default {
|
||||
case 'workspace':
|
||||
router.push({ name: 'workspace', query: { url } });
|
||||
break;
|
||||
case 'clipboard':
|
||||
navigator.clipboard.writeText(url);
|
||||
this.$toasted.show(this.$t('context-menus.item.copied-toast'));
|
||||
break;
|
||||
default: window.open(url, '_blank');
|
||||
}
|
||||
},
|
||||
@@ -546,4 +554,7 @@ a.item.is-edit-mode {
|
||||
.disabled-link {
|
||||
pointer-events: none;
|
||||
}
|
||||
.tooltip.item-description-tooltip {
|
||||
z-index: 7;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
<WorkspaceOpenIcon />
|
||||
<span>{{ $t('context-menus.item.workspace') }}</span>
|
||||
</li>
|
||||
<li @click="launch('clipboard')">
|
||||
<ClipboardOpenIcon />
|
||||
<span>{{ $t('context-menus.item.clipboard') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Edit Options -->
|
||||
<ul class="menu-section">
|
||||
@@ -55,6 +59,7 @@ import SameTabOpenIcon from '@/assets/interface-icons/open-current-tab.svg';
|
||||
import NewTabOpenIcon from '@/assets/interface-icons/open-new-tab.svg';
|
||||
import IframeOpenIcon from '@/assets/interface-icons/open-iframe.svg';
|
||||
import WorkspaceOpenIcon from '@/assets/interface-icons/open-workspace.svg';
|
||||
import ClipboardOpenIcon from '@/assets/interface-icons/open-clipboard.svg';
|
||||
|
||||
export default {
|
||||
name: 'ContextMenu',
|
||||
@@ -66,6 +71,7 @@ export default {
|
||||
NewTabOpenIcon,
|
||||
IframeOpenIcon,
|
||||
WorkspaceOpenIcon,
|
||||
ClipboardOpenIcon,
|
||||
},
|
||||
props: {
|
||||
posX: Number, // The X coordinate for positioning
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<WorkspaceOpenIcon v-else-if="openingMethod === 'workspace'" />
|
||||
<ParentOpenIcon v-else-if="openingMethod === 'parent'" />
|
||||
<TopOpenIcon v-else-if="openingMethod === 'top'" />
|
||||
<ClipboardOpenIcon v-else-if="openingMethod === 'clipboard'" />
|
||||
<UnknownIcon v-else />
|
||||
</div>
|
||||
<div v-if="hotkey" :class="`hotkey-denominator ${makeClass(position, isSmall, isTransparent)}`">
|
||||
@@ -25,6 +26,7 @@ import IframeOpenIcon from '@/assets/interface-icons/open-iframe.svg';
|
||||
import WorkspaceOpenIcon from '@/assets/interface-icons/open-workspace.svg';
|
||||
import ParentOpenIcon from '@/assets/interface-icons/open-parent.svg';
|
||||
import TopOpenIcon from '@/assets/interface-icons/open-top.svg';
|
||||
import ClipboardOpenIcon from '@/assets/interface-icons/open-clipboard.svg';
|
||||
import UnknownIcon from '@/assets/interface-icons/unknown-icon.svg';
|
||||
|
||||
export default {
|
||||
@@ -52,6 +54,7 @@ export default {
|
||||
WorkspaceOpenIcon,
|
||||
ParentOpenIcon,
|
||||
TopOpenIcon,
|
||||
ClipboardOpenIcon,
|
||||
UnknownIcon,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
@openContextMenu="openContextMenu"
|
||||
>
|
||||
<!-- If no items, show message -->
|
||||
<div v-if="sectionType === 'empty'" class="no-items">
|
||||
<div v-if="isEmpty" class="no-items">
|
||||
{{ $t('home.no-items-section') }}
|
||||
</div>
|
||||
<!-- Item Container -->
|
||||
<div v-else-if="sectionType === 'item'"
|
||||
<div v-if="hasItems"
|
||||
:class="`there-are-items ${isGridLayout? 'item-group-grid': ''} inner-size-${itemSize}`"
|
||||
:style="gridStyle" :id="`section-${groupId}`"
|
||||
> <!-- Show for each item -->
|
||||
@@ -58,7 +58,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="sectionType === 'widget'"
|
||||
v-if="hasWidgets"
|
||||
:class="`widget-list ${isWide? 'wide' : ''}`">
|
||||
<WidgetBase
|
||||
v-for="(widget, widgetIndx) in widgets"
|
||||
@@ -154,11 +154,15 @@ export default {
|
||||
sortOrder() {
|
||||
return this.displayData.sortBy || defaultSortOrder;
|
||||
},
|
||||
/* A section can contain either items or widgets */
|
||||
sectionType() {
|
||||
if (this.widgets && this.widgets.length > 0) return 'widget';
|
||||
if (this.items && this.items.length > 0) return 'item';
|
||||
return 'empty';
|
||||
hasItems() {
|
||||
if (this.isEditMode) return true;
|
||||
return this.items && this.items.length > 0;
|
||||
},
|
||||
hasWidgets() {
|
||||
return this.widgets && this.widgets.length > 0;
|
||||
},
|
||||
isEmpty() {
|
||||
return !this.hasItems && !this.hasWidgets;
|
||||
},
|
||||
/* If the sortBy attribute is specified, then return sorted data */
|
||||
sortedItems() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- If auth configured, show status text -->
|
||||
<span class="user-type-note">{{ makeText() }}</span>
|
||||
<span class="user-type-note">{{ makeUserGreeting() }}</span>
|
||||
<div class="display-options">
|
||||
<!-- If user logged in, show logout button -->
|
||||
<IconLogout
|
||||
@@ -17,6 +17,13 @@
|
||||
v-tooltip="tooltip($t('settings.sign-in-tooltip'))"
|
||||
class="layout-icon" tabindex="-2"
|
||||
/>
|
||||
<!-- If user logged in via keycloak, show keycloak logout button -->
|
||||
<IconLogout
|
||||
v-if="userType == userStateEnum.keycloakEnabled"
|
||||
@click="keycloakLogout()"
|
||||
v-tooltip="tooltip($t('settings.sign-out-tooltip'))"
|
||||
class="layout-icon" tabindex="-2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -24,6 +31,7 @@
|
||||
<script>
|
||||
import router from '@/router';
|
||||
import { logout as registerLogout } from '@/utils/Auth';
|
||||
import { getKeycloakAuth } from '@/utils/KeycloakAuth';
|
||||
import { localStorageKeys, userStateEnum } from '@/utils/defaults';
|
||||
import IconLogout from '@/assets/interface-icons/user-logout.svg';
|
||||
|
||||
@@ -48,14 +56,22 @@ export default {
|
||||
router.push({ path: '/login' });
|
||||
}, 500);
|
||||
},
|
||||
keycloakLogout() {
|
||||
const keycloak = getKeycloakAuth();
|
||||
this.$toasted.show(this.$t('login.logout-message'));
|
||||
setTimeout(() => {
|
||||
keycloak.logout();
|
||||
}, 500);
|
||||
},
|
||||
goToLogin() {
|
||||
router.push({ path: '/login' });
|
||||
},
|
||||
tooltip(content) {
|
||||
return { content, trigger: 'hover focus', delay: 250 };
|
||||
},
|
||||
makeText() {
|
||||
if (this.userType === userStateEnum.loggedIn) {
|
||||
makeUserGreeting() {
|
||||
if (this.userType === userStateEnum.loggedIn
|
||||
|| this.userType === userStateEnum.keycloakEnabled) {
|
||||
const username = localStorage[localStorageKeys.USERNAME];
|
||||
return username ? this.$t('settings.sign-in-welcome', { username }) : '';
|
||||
}
|
||||
@@ -73,7 +89,6 @@ export default {
|
||||
|
||||
span.user-type-note {
|
||||
color: var(--settings-text-color);
|
||||
text-transform: capitalize;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<LayoutSelector :displayLayout="displayLayout" />
|
||||
<ItemSizeSelector :iconSize="iconSize" />
|
||||
<ConfigLauncher />
|
||||
<AuthButtons v-if="userState != 'noone'" :userType="userState" />
|
||||
<AuthButtons v-if="userState !== 0" :userType="userState" />
|
||||
</div>
|
||||
<div :class="`show-hide-container ${settingsVisible? 'hide-btn' : 'show-btn'}`">
|
||||
<button @click="toggleSettingsVisibility()"
|
||||
@@ -80,7 +80,7 @@ export default {
|
||||
/**
|
||||
* Determines which button should display, based on the user type
|
||||
* 0 = Auth not configured, don't show anything
|
||||
* 1 = Auth condifured, and user logged in, show logout button
|
||||
* 1 = Auth configured, and user logged in, show logout button
|
||||
* 2 = Auth configured, guest access enabled, and not logged in, show login
|
||||
* Note that if auth is enabled, but not guest access, and user not logged in,
|
||||
* then they will never be able to view the homepage, so no button needed
|
||||
|
||||
@@ -47,12 +47,12 @@ export default {
|
||||
},
|
||||
startDate() {
|
||||
const now = new Date();
|
||||
return `${now.getDate()}-${now.getMonth()}-${now.getFullYear()}`;
|
||||
return `${now.getDate()}-${now.getMonth() + 1}-${now.getFullYear()}`;
|
||||
},
|
||||
endDate() {
|
||||
const now = new Date();
|
||||
const then = new Date((now.setMonth(now.getMonth() + this.monthsToShow)));
|
||||
return `${then.getDate()}-${then.getMonth()}-${then.getFullYear()}`;
|
||||
return `${then.getDate()}-${then.getMonth() + 1}-${then.getFullYear()}`;
|
||||
},
|
||||
endpoint() {
|
||||
return `${widgetApiEndpoints.holidays}`
|
||||
|
||||
20
src/main.js
20
src/main.js
@@ -2,7 +2,6 @@
|
||||
// Import core framework and essential utils
|
||||
import Vue from 'vue';
|
||||
import VueI18n from 'vue-i18n'; // i18n for localization
|
||||
import Keycloak from 'keycloak-js';
|
||||
|
||||
// Import component Vue plugins, used throughout the app
|
||||
import VTooltip from 'v-tooltip'; // A Vue directive for Popper.js, tooltip component
|
||||
@@ -21,7 +20,7 @@ import clickOutside from '@/utils/ClickOutside'; // Directive for closing p
|
||||
import { messages } from '@/utils/languages'; // Language texts
|
||||
import ErrorReporting from '@/utils/ErrorReporting'; // Error reporting initializer (off)
|
||||
import { toastedOptions, tooltipOptions, language as defaultLanguage } from '@/utils/defaults';
|
||||
import { isKeycloakEnabled, getKeycloakConfig } from '@/utils/Auth'; // Keycloak auth config
|
||||
import { initKeycloakAuth, isKeycloakEnabled } from '@/utils/KeycloakAuth';
|
||||
|
||||
// Initialize global Vue components
|
||||
Vue.use(VueI18n);
|
||||
@@ -63,18 +62,7 @@ const mount = () => new Vue({
|
||||
if (!isKeycloakEnabled()) {
|
||||
mount();
|
||||
} else { // Keycloak is enabled, redirect to KC login page
|
||||
const { serverUrl, realm, clientId } = getKeycloakConfig();
|
||||
const initOptions = {
|
||||
url: `${serverUrl}/auth`, realm, clientId, onLoad: 'login-required',
|
||||
};
|
||||
const keycloak = Keycloak(initOptions);
|
||||
keycloak.init({ onLoad: initOptions.onLoad }).then((auth) => {
|
||||
if (!auth) {
|
||||
// Not authenticated, reload to Keycloak login page
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Yay - user successfully authenticated with Keycloak, render the app!
|
||||
mount();
|
||||
}
|
||||
});
|
||||
initKeycloakAuth()
|
||||
.then(() => mount())
|
||||
.catch(() => window.location.reload());
|
||||
}
|
||||
|
||||
@@ -70,8 +70,10 @@ const store = new Vuex.Store({
|
||||
getItemById: (state, getters) => (id) => {
|
||||
let item;
|
||||
getters.sections.forEach(sec => {
|
||||
const foundItem = sec.items.find((itm) => itm.id === id);
|
||||
if (foundItem) item = foundItem;
|
||||
if (sec.items) {
|
||||
const foundItem = sec.items.find((itm) => itm.id === id);
|
||||
if (foundItem) item = foundItem;
|
||||
}
|
||||
});
|
||||
return item;
|
||||
},
|
||||
|
||||
@@ -2,6 +2,7 @@ import sha256 from 'crypto-js/sha256';
|
||||
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { cookieKeys, localStorageKeys, userStateEnum } from '@/utils/defaults';
|
||||
import { isKeycloakEnabled } from '@/utils/KeycloakAuth';
|
||||
|
||||
/* Uses config accumulator to get and return app config */
|
||||
const getAppConfig = () => {
|
||||
@@ -19,26 +20,6 @@ const printWarning = () => {
|
||||
ErrorHandler('From V 1.6.5 onwards, the structure of the users object has changed.');
|
||||
};
|
||||
|
||||
/* Returns true if keycloak is enabled */
|
||||
export const isKeycloakEnabled = () => {
|
||||
const appConfig = getAppConfig();
|
||||
if (!appConfig.auth) return false;
|
||||
return appConfig.auth.enableKeycloak || false;
|
||||
};
|
||||
|
||||
/* Returns the users keycloak config */
|
||||
export const getKeycloakConfig = () => {
|
||||
const appConfig = getAppConfig();
|
||||
if (!isKeycloakEnabled()) return false;
|
||||
const { keycloak } = appConfig.auth;
|
||||
const { serverUrl, realm, clientId } = keycloak;
|
||||
if (!serverUrl || !realm || !clientId) {
|
||||
ErrorHandler('Keycloak config missing- please ensure you specify: serverUrl, realm, clientId');
|
||||
return false;
|
||||
}
|
||||
return keycloak;
|
||||
};
|
||||
|
||||
/* Returns array of users from appConfig.auth, if available, else an empty array */
|
||||
const getUsers = () => {
|
||||
const appConfig = getAppConfig();
|
||||
@@ -65,7 +46,6 @@ const generateUserToken = (user) => {
|
||||
|
||||
/**
|
||||
* Checks if the user is currently authenticated
|
||||
* @param {Array[Object]} users An array of user objects pulled from the config
|
||||
* @returns {Boolean} Will return true if the user is logged in, else false
|
||||
*/
|
||||
export const isLoggedIn = () => {
|
||||
@@ -95,7 +75,7 @@ export const isAuthEnabled = () => {
|
||||
/* Returns true if guest access is enabled */
|
||||
export const isGuestAccessEnabled = () => {
|
||||
const appConfig = getAppConfig();
|
||||
if (appConfig.auth && typeof appConfig.auth === 'object') {
|
||||
if (appConfig.auth && typeof appConfig.auth === 'object' && !isKeycloakEnabled()) {
|
||||
return appConfig.auth.enableGuestAccess || false;
|
||||
}
|
||||
return false;
|
||||
@@ -108,6 +88,7 @@ export const isGuestAccessEnabled = () => {
|
||||
* @param {String} username The username entered by the user
|
||||
* @param {String} pass The password entered by the user
|
||||
* @param {String[]} users An array of valid user objects
|
||||
* @param {Object} messages A static message template object
|
||||
* @returns {Object} An object containing a boolean result and a message
|
||||
*/
|
||||
export const checkCredentials = (username, pass, users, messages) => {
|
||||
@@ -146,7 +127,7 @@ export const login = (username, pass, timeout) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Removed the browsers cookie, causing user to be logged out
|
||||
* Removed the browsers' cookie, causing user to be logged out
|
||||
*/
|
||||
export const logout = () => {
|
||||
document.cookie = 'authenticationToken=null';
|
||||
@@ -164,7 +145,7 @@ export const getCurrentUser = () => {
|
||||
if (!username) return false; // No username
|
||||
let foundUserObject = false; // Value to return
|
||||
getUsers().forEach((user) => {
|
||||
// If current logged in user found, then return that user
|
||||
// If current logged-in user found, then return that user
|
||||
if (user.user === username) foundUserObject = user;
|
||||
});
|
||||
return foundUserObject;
|
||||
@@ -182,11 +163,10 @@ export const isLoggedInAsGuest = () => {
|
||||
|
||||
/**
|
||||
* Checks if the current user has admin privileges.
|
||||
* If no users are setup, then function will always return true
|
||||
* If no users are set up, then function will always return true
|
||||
* But if auth is configured, then will verify user is correctly
|
||||
* logged in and then check weather they are of type admin, and
|
||||
* return false if any conditions fail
|
||||
* @param {String[]} - Array of users
|
||||
* @returns {Boolean} - True if admin privileges
|
||||
*/
|
||||
export const isUserAdmin = () => {
|
||||
@@ -212,7 +192,13 @@ export const isUserAdmin = () => {
|
||||
* then they will never be able to view the homepage, so no button needed
|
||||
*/
|
||||
export const getUserState = () => {
|
||||
const { notConfigured, loggedIn, guestAccess } = userStateEnum; // Numeric enum options
|
||||
const {
|
||||
notConfigured,
|
||||
loggedIn,
|
||||
guestAccess,
|
||||
keycloakEnabled,
|
||||
} = userStateEnum; // Numeric enum options
|
||||
if (isKeycloakEnabled()) return keycloakEnabled; // Keycloak auth configured
|
||||
if (!isAuthEnabled()) return notConfigured; // No auth enabled
|
||||
if (isLoggedIn()) return loggedIn; // User is logged in
|
||||
if (isGuestAccessEnabled()) return guestAccess; // Guest is viewing
|
||||
|
||||
@@ -6,24 +6,32 @@
|
||||
|
||||
// Import helper functions from auth, to get current user, and check if guest
|
||||
import { getCurrentUser, isLoggedInAsGuest } from '@/utils/Auth';
|
||||
import { localStorageKeys } from '@/utils/defaults';
|
||||
|
||||
/* Helper function, checks if a given username appears in a user array */
|
||||
const determineVisibility = (visibilityList, cUsername) => {
|
||||
/* Helper function, checks if a given testValue is found in the visibility list */
|
||||
const determineVisibility = (visibilityList, testValue) => {
|
||||
let isFound = false;
|
||||
visibilityList.forEach((userInList) => {
|
||||
if (userInList.toLowerCase() === cUsername) isFound = true;
|
||||
visibilityList.forEach((visibilityItem) => {
|
||||
if (visibilityItem.toLowerCase() === testValue.toLowerCase()) isFound = true;
|
||||
});
|
||||
return isFound;
|
||||
};
|
||||
|
||||
/* Helper function, determines if two arrays have any intersecting elements
|
||||
(one or more items that are the same) */
|
||||
const determineIntersection = (source = [], target = []) => {
|
||||
const intersections = source.filter(item => target.indexOf(item) !== -1);
|
||||
return intersections.length > 0;
|
||||
};
|
||||
|
||||
/* Returns false if this section should not be rendered for the current user/ guest */
|
||||
const isSectionVisibleToUser = (displayData, currentUser, isGuest) => {
|
||||
// Checks if user explicitly has access to a certain section
|
||||
const checkVisiblity = () => {
|
||||
const checkVisibility = () => {
|
||||
if (!currentUser) return true;
|
||||
const hideFor = displayData.hideForUsers || [];
|
||||
const hideForUsers = displayData.hideForUsers || [];
|
||||
const cUsername = currentUser.user.toLowerCase();
|
||||
return !determineVisibility(hideFor, cUsername);
|
||||
return !determineVisibility(hideForUsers, cUsername);
|
||||
};
|
||||
// Checks if user is explicitly prevented from viewing a certain section
|
||||
const checkHiddenability = () => {
|
||||
@@ -33,12 +41,36 @@ const isSectionVisibleToUser = (displayData, currentUser, isGuest) => {
|
||||
if (showForUsers.length < 1) return true;
|
||||
return determineVisibility(showForUsers, cUsername);
|
||||
};
|
||||
const checkKeycloakVisibility = () => {
|
||||
if (!displayData.hideForKeycloakUsers) return true;
|
||||
|
||||
const { groups, roles } = JSON.parse(localStorage.getItem(localStorageKeys.KEYCLOAK_INFO) || '{}');
|
||||
const hideForGroups = displayData.hideForKeycloakUsers.groups || [];
|
||||
const hideForRoles = displayData.hideForKeycloakUsers.roles || [];
|
||||
|
||||
return !(determineIntersection(hideForRoles, roles)
|
||||
|| determineIntersection(hideForGroups, groups));
|
||||
};
|
||||
const checkKeycloakHiddenability = () => {
|
||||
if (!displayData.showForKeycloakUsers) return true;
|
||||
|
||||
const { groups, roles } = JSON.parse(localStorage.getItem(localStorageKeys.KEYCLOAK_INFO) || '{}');
|
||||
const showForGroups = displayData.showForKeycloakUsers.groups || [];
|
||||
const showForRoles = displayData.showForKeycloakUsers.roles || [];
|
||||
|
||||
return determineIntersection(showForRoles, roles)
|
||||
|| determineIntersection(showForGroups, groups);
|
||||
};
|
||||
// Checks if the current user is a guest, and if section allows for guests
|
||||
const checkIfHideForGuest = () => {
|
||||
const hideForGuest = displayData.hideForGuests;
|
||||
return !(hideForGuest && isGuest);
|
||||
};
|
||||
return checkVisiblity() && checkHiddenability() && checkIfHideForGuest();
|
||||
return checkVisibility()
|
||||
&& checkHiddenability()
|
||||
&& checkIfHideForGuest()
|
||||
&& checkKeycloakVisibility()
|
||||
&& checkKeycloakHiddenability();
|
||||
};
|
||||
|
||||
/* Putting it all together, the function to export */
|
||||
|
||||
@@ -84,7 +84,8 @@
|
||||
"parent",
|
||||
"top",
|
||||
"modal",
|
||||
"workspace"
|
||||
"workspace",
|
||||
"clipboard"
|
||||
],
|
||||
"default": "newtab",
|
||||
"description": "The default opening method for items. Only used if no item.target is specified"
|
||||
@@ -613,6 +614,58 @@
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If set to true, section will be visible for logged in users, but not for guests"
|
||||
},
|
||||
"showForKeycloakUsers": {
|
||||
"title": "Show for select Keycloak groups or roles",
|
||||
"type": "object",
|
||||
"description": "Configure the Keycloak groups or roles that will have access to this section",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"groups": {
|
||||
"title": "Show for Groups",
|
||||
"type": "array",
|
||||
"description": "Section will be hidden from all users except those with one or more of these groups",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "Name of the group that will be able to view this section"
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"title": "Show for Roles",
|
||||
"type": "array",
|
||||
"description": "Section will be hidden from all users except those with one or more of these roles",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "Name of the role that will be able to view this section"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"hideForKeycloakUsers": {
|
||||
"title": "Hide for select Keycloak groups or roles",
|
||||
"type": "object",
|
||||
"description": "Configure the Keycloak groups or roles that will not have access to this section",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"groups": {
|
||||
"title": "Hide for Groups",
|
||||
"type": "array",
|
||||
"description": "Section will be hidden from users with any of these groups",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "name of the group that will not be able to view this section"
|
||||
}
|
||||
},
|
||||
"roles": {
|
||||
"title": "Hide for Roles",
|
||||
"type": "array",
|
||||
"description": "Section will be hidden from users with any of roles",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"description": "name of the role that will not be able to view this section"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -658,7 +711,8 @@
|
||||
"parent",
|
||||
"top",
|
||||
"modal",
|
||||
"workspace"
|
||||
"workspace",
|
||||
"clipboard"
|
||||
],
|
||||
"default": "newtab",
|
||||
"description": "Where / how the item is opened when it's clicked"
|
||||
@@ -742,4 +796,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
92
src/utils/KeycloakAuth.js
Normal file
92
src/utils/KeycloakAuth.js
Normal file
@@ -0,0 +1,92 @@
|
||||
import Keycloak from 'keycloak-js';
|
||||
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
||||
import { localStorageKeys } from '@/utils/defaults';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
|
||||
const getAppConfig = () => {
|
||||
const Accumulator = new ConfigAccumulator();
|
||||
const config = Accumulator.config();
|
||||
return config.appConfig || {};
|
||||
};
|
||||
|
||||
class KeycloakAuth {
|
||||
constructor() {
|
||||
const { auth } = getAppConfig();
|
||||
const { serverUrl, realm, clientId } = auth.keycloak;
|
||||
const initOptions = {
|
||||
url: `${serverUrl}/auth`, realm, clientId, onLoad: 'login-required',
|
||||
};
|
||||
|
||||
this.keycloakClient = Keycloak(initOptions);
|
||||
}
|
||||
|
||||
login() {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.keycloakClient.init({ onLoad: 'login-required' })
|
||||
.then((auth) => {
|
||||
if (auth) {
|
||||
this.storeKeycloakInfo();
|
||||
return resolve();
|
||||
} else {
|
||||
return reject(new Error('Not authenticated'));
|
||||
}
|
||||
})
|
||||
.catch((reason) => reject(reason));
|
||||
});
|
||||
}
|
||||
|
||||
logout() {
|
||||
localStorage.removeItem(localStorageKeys.USERNAME);
|
||||
localStorage.removeItem(localStorageKeys.KEYCLOAK_INFO);
|
||||
this.keycloakClient.logout();
|
||||
}
|
||||
|
||||
storeKeycloakInfo() {
|
||||
if (this.keycloakClient.tokenParsed && typeof this.keycloakClient.tokenParsed === 'object') {
|
||||
const {
|
||||
groups,
|
||||
realm_access: realmAccess,
|
||||
resource_access: resourceAccess,
|
||||
azp: clientId,
|
||||
preferred_username: preferredUsername,
|
||||
} = this.keycloakClient.tokenParsed;
|
||||
|
||||
const realmRoles = realmAccess.roles || [];
|
||||
|
||||
let clientRoles = [];
|
||||
if (Object.hasOwn(resourceAccess, clientId)) {
|
||||
clientRoles = resourceAccess[clientId].roles || [];
|
||||
}
|
||||
|
||||
const roles = [...realmRoles, ...clientRoles];
|
||||
|
||||
const info = {
|
||||
groups,
|
||||
roles,
|
||||
};
|
||||
|
||||
localStorage.setItem(localStorageKeys.KEYCLOAK_INFO, JSON.stringify(info));
|
||||
localStorage.setItem(localStorageKeys.USERNAME, preferredUsername);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const isKeycloakEnabled = () => {
|
||||
const { auth } = getAppConfig();
|
||||
if (!auth) return false;
|
||||
return auth.enableKeycloak || false;
|
||||
};
|
||||
|
||||
let keycloak;
|
||||
|
||||
export const initKeycloakAuth = () => {
|
||||
keycloak = new KeycloakAuth();
|
||||
return keycloak.login();
|
||||
};
|
||||
|
||||
export const getKeycloakAuth = () => {
|
||||
if (!keycloak) {
|
||||
ErrorHandler("Keycloak not initialized, can't get instance of class");
|
||||
}
|
||||
return keycloak;
|
||||
};
|
||||
@@ -8,7 +8,8 @@ export const shouldBeVisible = (routeName) => !hideFurnitureOn.includes(routeNam
|
||||
/* Based on section title, item name and index, return a string value for ID */
|
||||
const makeItemId = (sectionStr, itemStr, index) => {
|
||||
const charSum = sectionStr.split('').map((a) => a.charCodeAt(0)).reduce((x, y) => x + y);
|
||||
const itemTitleStr = itemStr.replace(/\s+/g, '-').replace(/[^a-zA-Z ]/g, '').toLowerCase();
|
||||
const newItemStr = itemStr || `unknown_${Math.random()}`;
|
||||
const itemTitleStr = newItemStr.replace(/\s+/g, '-').replace(/[^a-zA-Z ]/g, '').toLowerCase();
|
||||
return `${index}_${charSum}_${itemTitleStr}`;
|
||||
};
|
||||
|
||||
|
||||
@@ -3,10 +3,7 @@ module.exports = {
|
||||
pageInfo: {
|
||||
title: 'Dashy',
|
||||
description: '',
|
||||
navLinks: [
|
||||
{ title: 'Home', path: '/' },
|
||||
{ title: 'Source', path: 'https://github.com/Lissy93/dashy' },
|
||||
],
|
||||
navLinks: [],
|
||||
footerText: '',
|
||||
},
|
||||
/* Default appConfig to be used, if user does not specify their own */
|
||||
@@ -124,6 +121,7 @@ module.exports = {
|
||||
USERNAME: 'username',
|
||||
MOST_USED: 'mostUsed',
|
||||
LAST_USED: 'lastUsed',
|
||||
KEYCLOAK_INFO: 'keycloakInfo',
|
||||
},
|
||||
/* Key names for cookie identifiers */
|
||||
cookieKeys: {
|
||||
@@ -284,6 +282,7 @@ module.exports = {
|
||||
loggedIn: 1,
|
||||
guestAccess: 2,
|
||||
notLoggedIn: 3,
|
||||
keycloakEnabled: 4,
|
||||
},
|
||||
/* Progressive Web App settings, used by Vue Config */
|
||||
pwa: {
|
||||
|
||||
Reference in New Issue
Block a user