Merge branch 'master' of github.com:Lissy93/dashy into FEATURE/minimal-view

This commit is contained in:
Alicia Sykes
2021-07-25 15:02:17 +01:00
40 changed files with 1768 additions and 352 deletions

View File

@@ -17,6 +17,7 @@ import {
localStorageKeys,
splashScreenTime,
visibleComponents as defaultVisibleComponents,
language as defaultLanguage,
} from '@/utils/defaults';
const Accumulator = new ConfigAccumulator();
@@ -70,9 +71,33 @@ export default {
this.isLoading = false;
}
},
/* Checks local storage, then appConfig, and if a custom language is specified, its applied */
applyLanguage() {
let language = defaultLanguage; // Language to apply
const availibleLocales = this.$i18n.availableLocales; // All available locales
// If user has specified a language, locally or in config, then check and apply it
const usersLang = localStorage[localStorageKeys.LANGUAGE] || this.appConfig.language;
if (usersLang && availibleLocales.includes(usersLang)) {
language = usersLang;
} else {
// Otherwise, attempt to apply language automatically, based on their system language
const usersBorwserLang1 = window.navigator.language || ''; // e.g. en-GB or or ''
const usersBorwserLang2 = usersBorwserLang1.split('-')[0]; // e.g. en or undefined
if (availibleLocales.includes(usersBorwserLang1)) {
language = usersBorwserLang1;
} else if (availibleLocales.includes(usersBorwserLang2)) {
language = usersBorwserLang2;
}
}
// Apply Language
this.$i18n.locale = language;
document.getElementsByTagName('html')[0].setAttribute('lang', language);
},
},
/* When component mounted, hide splash and initiate the injection of custom styles */
mounted() {
this.applyLanguage();
this.hideSplash();
if (this.appConfig.customCss) {
const cleanedCss = this.appConfig.customCss.replace(/<\/?[^>]+(>|$)/g, '');

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="language" class="svg-inline--fa fa-language fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M160.3 203.8h-.5s-4.3 20.9-7.8 33l-11 37.3h37.9l-10.7-37.3c-3.6-12.1-7.9-33-7.9-33zM616 96H24c-13.3 0-24 10.7-24 24v272c0 13.3 10.7 24 24 24h592c13.3 0 24-10.7 24-24V120c0-13.3-10.7-24-24-24zM233.2 352h-22.6a12 12 0 0 1-11.5-8.6l-9.3-31.7h-59.9l-9.1 31.6c-1.5 5.1-6.2 8.7-11.5 8.7H86.8c-8.2 0-14-8.1-11.4-15.9l57.1-168c1.7-4.9 6.2-8.1 11.4-8.1h32.2c5.1 0 9.7 3.3 11.4 8.1l57.1 168c2.6 7.8-3.2 15.9-11.4 15.9zM600 376H320V136h280zM372 228h110.8c-6.3 12.8-15.1 25.9-25.9 38.5-6.6-7.8-12.8-15.8-18.3-24-3.5-5.3-10.6-6.9-16.1-3.6l-13.7 8.2c-5.9 3.5-7.6 11.3-3.8 17 6.5 9.7 14.4 20.1 23.5 30.6-9 7.7-18.6 14.8-28.7 21.2-5.4 3.4-7.1 10.5-3.9 16l7.9 13.9c3.4 5.9 11 7.9 16.8 4.2 12.5-7.9 24.6-17 36-26.8 10.7 9.6 22.3 18.6 34.6 26.6 5.8 3.7 13.6 1.9 17-4.1l8-13.9c3.1-5.5 1.5-12.5-3.8-16-9.2-6-18.4-13.1-27.2-20.9 1.5-1.7 2.9-3.3 4.3-5 17.1-20.6 29.6-41.7 36.8-62H540c6.6 0 12-5.4 12-12v-16c0-6.6-5.4-12-12-12h-64v-16c0-6.6-5.4-12-12-12h-16c-6.6 0-12 5.4-12 12v16h-64c-6.6 0-12 5.4-12 12v16c0 6.7 5.4 12.1 12 12.1z"></path></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

136
src/assets/locales/en.json Normal file
View File

@@ -0,0 +1,136 @@
{
"home": {
"no-results": "No Search Results",
"no-data": "No Data Configured"
},
"search": {
"search-label": "Search",
"search-placeholder": "Start typing to filter",
"clear-search-tooltip": "Clear Search"
},
"login": {
"title": "Dashy",
"username-label": "Username",
"password-label": "Password",
"login-button": "Login",
"remember-me-label": "Remember me for",
"remember-me-never": "Never",
"remember-me-hour": "4 Hours",
"remember-me-day": "1 Day",
"remember-me-week": "1 Week"
},
"config": {
"main-tab": "Config",
"view-config-tab": "View Config",
"edit-config-tab": "Edit Config",
"custom-css-tab": "Custom Styles",
"heading": "Configuration Options",
"download-config-button": "Download Config",
"edit-config-button": "Edit Config",
"edit-css-button": "Edit Custom CSS",
"cloud-sync-button": "Enable Cloud Sync",
"edit-cloud-sync-button": "Edit Cloud Sync",
"rebuild-app-button": "Rebuild Application",
"change-language-button": "Change App Language",
"reset-settings-button": "Reset Local Settings",
"app-info-button": "App Info",
"app-version-note": "Dashy version",
"backup-note": "It is recommend to make a backup of your configuration before making changes.",
"reset-config-msg-l1": "This will remove all user settings from local storage, but won't effect your 'conf.yml' file.",
"reset-config-msg-l2": "You should first backup any changes you've made locally, if you want to use them in the future.",
"reset-config-msg-l3": "Are you sure you want to proceed?",
"data-cleared-msg": "Data cleared successfully",
"actions-label": "Actions",
"copy-config-label": "Copy Config",
"data-copied-msg": "Config has been copied to clipboard",
"reset-config-label": "Reset Config",
"css-save-btn": "Save Changes",
"css-note-label": "Note",
"css-note-l1": "You will need to refresh the page for your changes to take effect.",
"css-note-l2": "Styles overrides are only stored locally, so it is recommended to make a copy of your CSS.",
"css-note-l3": "To remove all custom styles, delete the contents and hit Save Changes"
},
"settings": {
"theme-label": "Theme",
"layout-label": "Layout",
"layout-auto": "Auto",
"layout-horizontal": "Horizontal",
"layout-vertical": "Vertical",
"item-size-label": "Item Size",
"item-size-small": "Small",
"item-size-medium": "Medium",
"item-size-large": "Large",
"config-launcher-label": "Config"
},
"language-switcher": {
"title": "Change Application Language",
"dropdown-label": "Select a Language",
"save-button": "Save",
"success-msg": "Language Updated to"
},
"theme-maker": {
"title": "Theme Configurator",
"export-button": "Export Custom Variables",
"reset-button": "Reset Styles for",
"show-all-button": "Show All Variables",
"save-button": "Save",
"cancel-button": "Cancel",
"saved-toast": "{theme} Updated Successfully",
"copied-toast": "Theme data for {theme} copied to clipboard",
"reset-toast": "Custom Colors for {theme} Removed"
},
"config-editor": {
"save-location-label": "Save Location",
"location-local-label": "Apply Locally",
"location-disk-label": "Write Changes to Config File",
"save-button": "Save Changes",
"valid-label": "Config is Valid",
"status-success-msg": "Task Complete",
"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.",
"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",
"warning-msg-validation": "Validation Warning"
},
"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-button": "Start Build",
"rebuilding-status-1": "Building...",
"rebuilding-status-2": "This may take a few minutes",
"error-permission": "You do no have permission to trigger this action",
"success-msg": "Build completed succesfully",
"fail-msg": "Build operation failed",
"reload-note": "A page reload is now required for changes to take effect",
"reload-button": "Reload Page"
},
"cloud-sync": {
"title": "Cloud Backup & Restore",
"intro-l1": "Cloud backup and restore is an optional feature, that enables you to upload your config to the internet, and then restore it on any other device or instance of Dashy.",
"intro-l2": "All data is fully end-to-end encrypted with AES, using your password as the key.",
"intro-l3": "For more info, please see the",
"backup-title-setup": "Make a Backup",
"backup-title-update": "Update Backup",
"password-label-setup": "Choose a Password",
"password-label-update": "Enter your Password",
"backup-button-setup": "Backup",
"backup-button-update": "Update Backup",
"backup-id-label": "Your Backup ID",
"backup-id-note": "This is used to restore from backups later. So keep it, along with your password somewhere safe.",
"restore-title": "Restore a Backup",
"restore-id-label": "Restore ID",
"restore-password-label": "Password",
"restore-button": "Restore",
"backup-error-unknown": "Unable to process request",
"backup-error-password": "Incorrect password. Please enter your current password.",
"backup-success-msg": "Completed Successfully",
"restore-success-msg": "Config Restored Successfully"
}
}

View File

@@ -1,55 +1,55 @@
<template>
<div class="cloud-backup-restore-wrapper">
<div class="section intro">
<h2>Cloud Backup & Restore</h2>
<h2>{{ $t('cloud-sync.title') }}</h2>
<p class="intro">
Cloud backup and restore is an optional feature, that enables you to upload your
config to the internet, and then restore it on any other device or instance of Dashy.
{{ $t('cloud-sync.intro-l1') }}
<br><br>
All data is fully end-to-end encrypted with AES, using your password as the key.
{{ $t('cloud-sync.intro-l2') }}
<br>
For more info, please see the
{{ $t('cloud-sync.intro-l3') }}
<a href="https://github.com/Lissy93/dashy/blob/master/docs/backup-restore.md">docs</a>
</p>
</div>
<div class="section backup-section">
<h3 v-if="backupId">Update Backup</h3>
<h3 v-else>Make a Backup</h3>
<h3 v-if="backupId">{{ $t('cloud-sync.backup-title-setup') }}</h3>
<h3 v-else>{{ $t('cloud-sync.backup-title-setup') }}</h3>
<Input
v-model="backupPassword"
name="backup-password"
:label="backupId ? 'Enter your Password' : 'Choose a Password'"
:label="backupId
? $t('cloud-sync.password-label-update') : $t('cloud-sync.password-label-setup')"
layout="vertical"
type="password"
/>
<Button :click="checkPass">
<template v-slot:text>{{backupId ? 'Update Backup' : 'Backup'}}</template>
<template v-slot:text>
{{backupId
? $t('cloud-sync.backup-button-update') : $t('cloud-sync.backup-button-setup')}}
</template>
<template v-slot:icon><IconBackup /></template>
</Button>
<div class="results-view" v-if="backupId">
<span class="backup-id-label">Your Backup ID: </span>
<span class="backup-id-label">{{ $t('cloud-sync.backup-id-label') }}: </span>
<pre class="backup-id-value">{{ backupId }}</pre>
<span class="backup-id-note">
This is used to restore from backups later.
So keep it, along with your password somewhere safe.
</span>
<span class="backup-id-note">{{ $t('cloud-sync.backup-id-note') }}</span>
</div>
</div>
<div class="section restore-section">
<h3>Restore a Backup</h3>
<h3>{{ $t('cloud-sync.restore-title') }}</h3>
<Input
v-model="restoreCode"
name="restore-code"
label="Restore ID"
:label="$t('cloud-sync.restore-id-label')"
/>
<Input
v-model="restorePassword"
name="restore-password"
label="Password"
:label="$t('cloud-sync.restore-password-label')"
type="password"
/>
<Button :click="restoreBackup">
<template v-slot:text>Restore</template>
<template v-slot:text>{{ $t('cloud-sync.restore-button') }}</template>
<template v-slot:icon><IconRestore /></template>
</Button>
</div>
@@ -101,7 +101,7 @@ export default {
} else if (savedHash === this.makeHash(this.backupPassword)) {
this.makeUpdate();
} else {
this.showErrorMsg('Incorrect password. Please enter your current password.');
this.showErrorMsg(this.$t('cloud-sync.backup-error-password'));
}
},
makeBackup() {
@@ -113,7 +113,7 @@ export default {
this.updateUiAfterBackup(response.data.backupId, false);
}
}).catch(() => {
this.showErrorMsg('Unable to process request');
this.showErrorMsg(this.$t('cloud-sync.backup-error-unknown'));
});
},
makeUpdate() {
@@ -125,7 +125,7 @@ export default {
this.updateUiAfterBackup(response.data.backupId, true);
}
}).catch(() => {
this.showErrorMsg('Unable to process request');
this.showErrorMsg(this.$t('cloud-sync.backup-error-unknown'));
});
},
restoreFromBackup(config, backupId) {
@@ -136,12 +136,14 @@ export default {
localStorage.setItem(localStorageKeys.THEME, config.appConfig.theme);
}
this.setBackupIdLocally(backupId, this.restorePassword);
this.showSuccessMsg('Config Restored Succesfully');
this.showSuccessMsg(this.$t('cloud-sync.restore-success-msg'));
setTimeout(() => { location.reload(); }, 1500); // eslint-disable-line no-restricted-globals
},
updateUiAfterBackup(backupId, isUpdate = false) {
this.setBackupIdLocally(backupId, this.backupPassword);
this.showSuccessMsg(`${isUpdate ? 'Update' : 'Backup'} Completed Succesfully`);
this.showSuccessMsg(
`${isUpdate ? 'Update' : 'Backup'} ${this.$t('cloud-sync.backup-success-msg')}`,
);
this.backupPassword = '';
},
showErrorMsg(errorMsg) {

View File

@@ -1,67 +1,74 @@
<template>
<Tabs :navAuto="true" name="Add Item" ref="tabView">
<TabItem name="Config" class="main-tab">
<TabItem :name="$t('config.main-tab')" class="main-tab">
<div class="main-options-container">
<h2>Configuration Options</h2>
<a class="hyperlink-wrapper" @click="downloadConfigFile('conf.yml', yaml)">
<button class="config-button center">
<DownloadIcon class="button-icon"/>
Download Config
<DownloadIcon class="button-icon"/>
{{ $t('config.download-config-button') }}
</button>
</a>
<button class="config-button center" @click="() => navigateToTab(2)">
<EditIcon class="button-icon"/>
Edit Config
{{ $t('config.edit-config-button') }}
</button>
<button class="config-button center" @click="() => navigateToTab(3)">
<CustomCssIcon class="button-icon"/>
Edit Custom CSS
{{ $t('config.edit-css-button') }}
</button>
<button class="config-button center" @click="openCloudSync()">
<CloudIcon class="button-icon"/>
{{backupId ? 'Edit Cloud Sync' : 'Enable Cloud Sync'}}
{{backupId ? $t('config.edit-cloud-sync-button') : $t('config.cloud-sync-button') }}
</button>
<button class="config-button center" @click="openLanguageSwitchModal()">
<LanguageIcon class="button-icon"/>
{{ $t('config.change-language-button') }}
</button>
<button class="config-button center" @click="openRebuildAppModal()">
<RebuildIcon class="button-icon"/>
Rebuild Application
{{ $t('config.rebuild-app-button') }}
</button>
<button class="config-button center" @click="resetLocalSettings()">
<DeleteIcon class="button-icon"/>
Reset Local Settings
{{ $t('config.reset-settings-button') }}
</button>
<button class="config-button center" @click="openAboutModal()">
<IconAbout class="button-icon" />
App Info
{{ $t('config.app-info-button') }}
</button>
<p class="small-screen-note" style="display: none;">
You are using a very small screen, and some screens in this menu may not be optimal
</p>
<p class="app-version">Dashy version {{ appVersion }}</p>
<p class="app-version">{{ $t('config.app-version-note') }} {{ appVersion }}</p>
<p class="language">{{ getLanguage() }}</p>
<div class="config-note">
<span>
It is recommend to make a backup of your conf.yml file before making changes.
</span>
<span>{{ $t('config.backup-note') }}</span>
</div>
</div>
<!-- Rebuild App Modal -->
<RebuildApp />
</TabItem>
<TabItem name="View Config" class="code-container">
<TabItem :name="$t('config.view-config-tab')" class="code-container">
<pre id="conf-yaml">{{yaml}}</pre>
<div class="yaml-action-buttons">
<h2>Actions</h2>
<h2>{{ $t('config.actions-label') }}</h2>
<a class="yaml-button download" @click="downloadConfigFile('conf.yml', yaml)">
Download Config
{{ $t('config.download-config-button') }}
</a>
<a class="yaml-button copy" @click="copyConfigToClipboard()">
{{ $t('config.copy-config-label') }}
</a>
<a class="yaml-button reset" @click="resetLocalSettings()">
{{ $t('config.reset-config-label') }}
</a>
<a class="yaml-button copy" @click="copyConfigToClipboard()">Copy Config</a>
<a class="yaml-button reset" @click="resetLocalSettings()">Reset Config</a>
</div>
</TabItem>
<TabItem name="Edit Config">
<TabItem :name="$t('config.edit-config-tab')">
<JsonEditor :config="config" />
</TabItem>
<TabItem name="Custom Styles">
<CustomCssEditor :config="config" initialCss="hello" />
<TabItem :name="$t('config.custom-css-tab')">
<CustomCssEditor :config="config" />
</TabItem>
</Tabs>
</template>
@@ -74,6 +81,7 @@ import 'highlight.js/styles/mono-blue.css';
import JsonToYaml from '@/utils/JsonToYaml';
import { localStorageKeys, modalNames } from '@/utils/defaults';
import { getUsersLanguage } from '@/utils/ConfigHelpers';
import JsonEditor from '@/components/Configuration/JsonEditor';
import CustomCssEditor from '@/components/Configuration/CustomCss';
import RebuildApp from '@/components/Configuration/RebuildApp';
@@ -84,6 +92,7 @@ import EditIcon from '@/assets/interface-icons/config-edit-json.svg';
import CustomCssIcon from '@/assets/interface-icons/config-custom-css.svg';
import CloudIcon from '@/assets/interface-icons/cloud-backup-restore.svg';
import RebuildIcon from '@/assets/interface-icons/application-rebuild.svg';
import LanguageIcon from '@/assets/interface-icons/config-language.svg';
import IconAbout from '@/assets/interface-icons/application-about.svg';
export default {
@@ -115,6 +124,7 @@ export default {
EditIcon,
CloudIcon,
CustomCssIcon,
LanguageIcon,
RebuildIcon,
IconAbout,
},
@@ -133,20 +143,21 @@ export default {
openCloudSync() {
this.$modal.show(modalNames.CLOUD_BACKUP);
},
openLanguageSwitchModal() {
this.$modal.show(modalNames.LANG_SWITCHER);
},
copyConfigToClipboard() {
navigator.clipboard.writeText(this.jsonParser(this.config));
// event.target.textContent = 'Copied to clipboard';
this.$toasted.show(this.$t('config.data-copied-msg'));
},
/* Checks that the user is sure, then resets site-wide local storage, and reloads page */
resetLocalSettings() {
const msg = 'This will remove all user settings from local storage, '
+ 'but won\'t effect your \'conf.yml\' file. '
+ 'It is recommend to make a backup of your modified YAML settings first.\n\n'
+ 'Are you sure you want to proceed?';
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('Data cleared succesfully');
this.$toasted.show(this.$t('config.data-cleared-msg'));
setTimeout(() => {
location.reload(true); // eslint-disable-line no-restricted-globals
}, 1900);
@@ -162,11 +173,19 @@ export default {
element.click();
document.body.removeChild(element);
},
/* Highlights the YAML config in View config tab */
initiateStntaxHighlighter() {
hljs.registerLanguage('yaml', yaml);
const highlighted = hljs.highlight(this.jsonParser(this.config), { language: 'yaml' }).value;
document.getElementById('conf-yaml').innerHTML = highlighted;
},
getLanguage() {
const lang = getUsersLanguage();
return lang ? `${lang.flag} ${lang.name}` : '';
},
},
mounted() {
hljs.registerLanguage('yaml', yaml);
const highlighted = hljs.highlight(this.jsonParser(this.config), { language: 'yaml' }).value;
document.getElementById('conf-yaml').innerHTML = highlighted;
this.initiateStntaxHighlighter();
},
};
</script>
@@ -213,10 +232,11 @@ a.config-button, button.config-button {
}
}
p.app-version {
p.app-version, p.language {
margin: 0.5rem auto;
font-size: 1rem;
color: var(--transparent-white-50);
cursor: default;
}
div.code-container {

View File

@@ -1,11 +1,10 @@
<template>
<div class="css-editor-outer">
<prism-editor class="my-editor" v-model="customCss" :highlight="highlighter" line-numbers />
<button class="save-button" @click="save()">Save Changes</button>
<button class="save-button" @click="save()">{{ $t('config.css-save-btn') }}</button>
<p class="quick-note">
<b>Note</b>: You will need to refresh the page for your changes to take effect.
Styles overides are only stored locally, so it is reccomended to make a copy of your CSS.
To remove all custom styles, delete the contents and hit Save Changes
<b>{{ $t('config.css-note-label') }}:</b>
{{ $t('config.css-note-l1') }} {{ $t('config.css-note-l2') }} {{ $t('config.css-note-l3') }}
</p>
<CustomThemeMaker :themeToEdit="currentTheme" class="color-config" />
</div>

View File

@@ -8,19 +8,25 @@
/>
<!-- Options raido, and save button -->
<div class="save-options">
<span class="save-option-title">Save Location:</span>
<span class="save-option-title">{{ $t('config-editor.save-location-label') }}:</span>
<div class="option">
<input type="radio" id="local" value="local"
v-model="saveMode" class="radio-option" :disabled="!allowWriteToDisk" />
<label for="local" class="save-option-label">Apply Locally</label>
<label for="local" class="save-option-label">
{{ $t('config-editor.location-local-label') }}
</label>
</div>
<div class="option">
<input type="radio" id="file" value="file" v-model="saveMode" class="radio-option"
:disabled="!allowWriteToDisk" />
<label for="file" class="save-option-label">Write Changes to Config File</label>
<label for="file" class="save-option-label">
{{ $t('config-editor.location-disk-label') }}
</label>
</div>
</div>
<button :class="`save-button ${!isValid ? 'err' : ''}`" @click="save()">Save Changes</button>
<button :class="`save-button ${!isValid ? 'err' : ''}`" @click="save()">
{{ $t('config-editor.save-button') }}
</button>
<!-- List validation warnings -->
<p class="errors">
<ul>
@@ -28,24 +34,23 @@
{{error.msg}}
</li>
<li v-if="errorMessages.length < 1" class="type-valid">
Config is Valid
{{ $t('config-editor.valid-label') }}
</li>
</ul>
</p>
<!-- Information notes -->
<p v-if="saveSuccess !== undefined"
:class="`response-output status-${saveSuccess ? 'success' : 'fail'}`">
{{saveSuccess ? 'Task Complete' : 'Task Failed'}}
{{saveSuccess
? $t('config-editor.status-success-msg') : $t('config-editor.status-fail-msg') }}
</p>
<p class="response-output">{{ responseText }}</p>
<p v-if="saveSuccess" class="response-output">
The app should rebuild automatically.
This may take up to a minute.
You will need to refresh the page for changes to take effect.
</p>
<p class="note">
It is recommend to backup your existing confiruration before making any changes.
{{ $t('config-editor.success-note-l1') }}
{{ $t('config-editor.success-note-l2') }}
{{ $t('config-editor.success-note-l3') }}
</p>
<p class="note">{{ $t('config.backup-note') }}</p>
</div>
</template>
@@ -103,7 +108,7 @@ export default {
} else if (this.saveMode === 'file') {
this.writeConfigToDisk();
} else {
this.$toasted.show('Please select a Save Mode: Local or File');
this.$toasted.show(this.$t('config-editor.error-msg-save-mode'));
}
},
writeConfigToDisk() {
@@ -121,9 +126,9 @@ export default {
this.responseText = response.data.message;
if (this.saveSuccess) {
this.carefullyClearLocalStorage();
this.showToast('Config file written to disk succesfully', true);
this.showToast(this.$t('config-editor.success-msg-disk'), true);
} else {
this.showToast('An error occurred saving config', false);
this.showToast(this.$t('config-editor.error-msg-cannot-save'), false);
}
})
.catch((error) => {
@@ -146,7 +151,7 @@ export default {
if (data.appConfig.theme) {
localStorage.setItem(localStorageKeys.THEME, data.appConfig.theme);
}
this.showToast('Changes saved succesfully', true);
this.showToast(this.$t('config-editor.success-msg-local'), true);
},
carefullyClearLocalStorage() {
localStorage.removeItem(localStorageKeys.PAGE_INFO);
@@ -160,7 +165,8 @@ export default {
case 'validation':
errorMessages.push({
type: 'validation',
msg: `Validatation Warning: ${error.error.keyword} ${error.error.message}`,
msg: `${this.$t('config-editor.warning-msg-validation')}: `
+ `${error.error.keyword} ${error.error.message}`,
});
break;
case 'error':
@@ -172,7 +178,7 @@ export default {
default:
errorMessages.push({
type: 'editor',
msg: 'Error in JSON',
msg: this.$t('config-editor.error-msg-bad-json'),
});
break;
}

View File

@@ -2,35 +2,41 @@
<modal :name="modalName" :resizable="true" width="50%" height="60%" classes="dashy-modal">
<div class="rebuild-app-container">
<!-- Title, intro and start button -->
<h3 class="rebuild-app-title">Rebuild Application</h3>
<h3 class="rebuild-app-title">{{ $t('app-rebuild.title') }}</h3>
<p>
A rebuild is required for changes written to the conf.yml file to take effect.
This should happen automatically, but if it hasn't, you can manually trigger it here.<br>
This is not required for modifications stored locally.
{{ $t('app-rebuild.rebuild-note-l1') }}
{{ $t('app-rebuild.rebuild-note-l2') }}<br>
{{ $t('app-rebuild.rebuild-note-l3') }}
</p>
<Button :click="startBuild" :disabled="loading || !allowRebuild" :disallow="!allowRebuild">
<template v-slot:text>{{ loading ? 'Building...' : 'Start Build' }}</template>
<template v-slot:text>
{{ loading ? $t('app-rebuild.rebuilding-status-1') : $t('app-rebuild.rebuild-button') }}
</template>
<template v-slot:icon><RebuildIcon /></template>
</Button>
<div v-if="!allowRebuild">
<p class="disallow-rebuild-msg">You do no have permission to trigger this action</p>
<p class="disallow-rebuild-msg">{{ $t('app-rebuild.error-permission') }}</p>
</div>
<!-- Loading animation and text (shown while build is happening) -->
<div v-if="loading" class="loader-info">
<LoadingAnimation class="loader" />
<p class="loading-message">This may take a few minutes...</p>
<p class="loading-message">{{ $t('app-rebuild.rebuilding-status-2') }}...</p>
</div>
<!-- Build response, and next actions (shown after build is done) -->
<div class="rebuild-response" v-if="success !== undefined">
<p v-if="success" class="response-status success">✅ Build completed succesfully</p>
<p v-else class="response-status failure">❌ Build operation failed</p>
<p v-if="success" class="response-status success">
{{ $t('app-rebuild.success-msg') }}
</p>
<p v-else class="response-status failure">
{{ $t('app-rebuild.fail-msg') }}
</p>
<pre class="output"><code>{{ output || error }}</code></pre>
<p class="rebuild-message">{{ message }}</p>
<p v-if="success" class="rebuild-message">
A page reload is now required for changes to take effect
{{ $t('app-rebuild.reload-note') }}
</p>
<Button :click="refreshPage" v-if="success">
<template v-slot:text>Reload Page</template>
<template v-slot:text>{{ $t('app-rebuild.reload-button') }}</template>
<template v-slot:icon><ReloadIcon /></template>
</Button>
</div>
@@ -65,6 +71,7 @@ export default {
allowRebuild: true,
}),
methods: {
/* Calls to the rebuild endpoint, to kickoff the app build */
startBuild() {
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
const endpoint = `${baseUrl}/config-manager/rebuild`;
@@ -77,6 +84,7 @@ export default {
this.finished({ success: false, error });
});
},
/* Called when rebuild is complete, updates UI with either success or fail message */
finished(responseData) {
this.loading = false;
if (responseData) {
@@ -89,7 +97,8 @@ export default {
this.error = error;
}
this.$toasted.show(
(this.success ? ' Build Completed Succesfully' : ' Build Failed'),
(this.success
? `${this.$t('app-rebuild.success-msg')}` : `${this.$t('app-rebuild.fail-msg')}`),
{ className: `toast-${this.success ? 'success' : 'error'}` },
);
},

View File

@@ -1,10 +1,10 @@
<template>
<div class="config-options">
<!-- Button and label -->
<span>Config</span>
<span>{{ $t('settings.config-launcher-label') }}</span>
<div class="config-buttons">
<IconSpanner @click="showEditor()" tabindex="-2"
v-tooltip="tooltip('Update configuration locally')" />
v-tooltip="tooltip('Update configuration')" />
<IconCloud @click="showCloudModal()" tabindex="-2"
v-tooltip="tooltip('Backup / restore cloud config')" />
</div>
@@ -20,6 +20,13 @@
@closed="$emit('modalChanged', false)" classes="dashy-modal">
<CloudBackupRestore :config="combineConfig()" />
</modal>
<!-- Modal for manually changing locale -->
<modal :name="modalNames.LANG_SWITCHER" classes="dashy-modal"
:resizable="true" width="30%" height="25%">
<LanguageSwitcher />
</modal>
</div>
</template>
@@ -29,6 +36,7 @@ import IconSpanner from '@/assets/interface-icons/config-editor.svg';
import IconCloud from '@/assets/interface-icons/cloud-backup-restore.svg';
import ConfigContainer from '@/components/Configuration/ConfigContainer';
import CloudBackupRestore from '@/components/Configuration/CloudBackupRestore';
import LanguageSwitcher from '@/components/Settings/LanguageSwitcher';
import { topLevelConfKeys, localStorageKeys, modalNames } from '@/utils/defaults';
export default {
@@ -43,6 +51,7 @@ export default {
IconCloud,
ConfigContainer,
CloudBackupRestore,
LanguageSwitcher,
},
props: {
sections: Array,

View File

@@ -1,6 +1,6 @@
<template>
<div :class="`theme-configurator-wrapper ${showingAllVars ? 'showing-all' : ''}`">
<h3 class="configurator-title">Theme Configurator</h3>
<h3 class="configurator-title">{{ $t('theme-maker.title') }}</h3>
<div class="color-row-container">
<div class="color-row" v-for="colorName in Object.keys(customColors)" :key="colorName">
<label :for="`color-input-${colorName}`" class="color-name">
@@ -33,17 +33,21 @@
</div> <!-- End of color list -->
</div>
<p @click="exportToClipboard" class="action-text-btn">
Export Custom Variables
{{ $t('theme-maker.export-button') }}
</p>
<p @click="resetAndSave" class="action-text-btn show-all-vars-btn">
Reset Styles for '{{ themeToEdit }}'
{{ $t('theme-maker.reset-button') }} '{{ themeToEdit }}'
</p>
<p @click="findAllVariableNames" class="action-text-btn">
Show All Variables
{{ $t('theme-maker.show-all-button') }}
</p>
<div class="action-buttons">
<Button :click="saveChanges"><SaveIcon />Save</Button>
<Button :click="resetUnsavedColors"><CancelIcon />Cancel</Button>
<Button :click="saveChanges">
<SaveIcon /> {{ $t('theme-maker.save-button') }}
</Button>
<Button :click="resetUnsavedColors">
<CancelIcon /> {{ $t('theme-maker.cancel-button') }}
</Button>
</div>
</div>
</template>
@@ -89,7 +93,7 @@ export default {
const priorSettings = JSON.parse(localStorage[localStorageKeys.CUSTOM_COLORS] || '{}');
priorSettings[this.themeToEdit] = this.customColors;
localStorage.setItem(localStorageKeys.CUSTOM_COLORS, JSON.stringify(priorSettings));
this.$toasted.show('Theme Updates Succesfully');
this.$toasted.show(this.$t('theme-maker.saved-toast', { theme: this.themeToEdit }));
this.$emit('closeThemeConfigurator');
},
/* Itterates over available variables, removing them from the DOM */
@@ -107,7 +111,7 @@ export default {
delete priorSettings[this.themeToEdit];
localStorage.setItem(localStorageKeys.CUSTOM_COLORS, JSON.stringify(priorSettings));
this.resetUnsavedColors();
this.$toasted.show(`Custom Colors for ${this.themeToEdit} Removed`);
this.$toasted.show(this.$t('theme-maker.reset-toast', { theme: this.themeToEdit }));
},
/* Generates CSS for the currently applied variables, and copys to users clipboard */
exportToClipboard() {
@@ -117,7 +121,7 @@ export default {
clipboardText += (`--${customVar}: ${this.customColors[customVar]};\n`);
});
navigator.clipboard.writeText(clipboardText);
this.$toasted.show(`Theme data for ${themeName} copied to clipboard`);
this.$toasted.show(this.$t('theme-maker.copied-toast', { theme: themeName }));
},
/* Returns a JSON object, with the variable name as key, and color as value */
makeInitialData(variableArray) {

View File

@@ -1,13 +1,25 @@
<template>
<div>
<span class="options-label">Icon Size</span>
<span class="options-label">{{ $t('settings.item-size-label') }}</span>
<div class="display-options">
<IconSmall @click="updateIconSize('small')" v-tooltip="tooltip('Small')"
:class="`layout-icon ${iconSize === 'small' ? 'selected' : ''}`" tabindex="-2" />
<IconMedium @click="updateIconSize('medium')" v-tooltip="tooltip('Medium')"
:class="`layout-icon ${iconSize === 'medium' ? 'selected' : ''}`" tabindex="-2" />
<IconLarge @click="updateIconSize('large')" v-tooltip="tooltip('Large')"
:class="`layout-icon ${iconSize === 'large' ? 'selected' : ''}`" tabindex="-2" />
<IconSmall
@click="updateIconSize('small')"
v-tooltip="tooltip($t('settings.item-size-small'))"
:class="`layout-icon ${iconSize === 'small' ? 'selected' : ''}`"
tabindex="-2"
/>
<IconMedium
@click="updateIconSize('medium')"
v-tooltip="tooltip($t('settings.item-size-medium'))"
:class="`layout-icon ${iconSize === 'medium' ? 'selected' : ''}`"
tabindex="-2"
/>
<IconLarge
@click="updateIconSize('large')"
v-tooltip="tooltip($t('settings.item-size-large'))"
:class="`layout-icon ${iconSize === 'large' ? 'selected' : ''}`"
tabindex="-2"
/>
</div>
</div>
</template>

View File

@@ -0,0 +1,110 @@
<template>
<div class="language-switcher">
<h3 class="title">{{ $t('language-switcher.title') }}</h3>
<p class="intro">{{ $t('language-switcher.dropdown-label') }}:</p>
<v-select
v-model="language"
:selectOnTab="true"
:options="availibleLanguages"
class="language-dropdown"
label="name"
:input="setLangLocally()"
/>
<Button class="save-button" :click="saveLanguage" :disallow="!language">
{{ $t('language-switcher.save-button') }}
<SaveConfigIcon />
</Button>
<p v-if="language">{{ language.flag }} {{ language.name }}</p>
<p v-if="$i18n.availableLocales.length <= 1" class="sad-times">
There are not currently any additional languages supported,
but stay tuned as more are on their way!
</p>
</div>
</template>
<script>
import Button from '@/components/FormElements/Button';
import { languages } from '@/utils/languages';
import SaveConfigIcon from '@/assets/interface-icons/save-config.svg';
import { localStorageKeys, modalNames } from '@/utils/defaults';
export default {
name: 'LanguageSwitcher',
inject: ['config'],
components: {
Button,
SaveConfigIcon,
},
data() {
return {
availibleLanguages: languages,
language: '',
modalName: modalNames.LANG_SWITCHER,
};
},
methods: {
/* Save language to local storage, show success msg and close modal */
saveLanguage() {
const selectedLanguage = this.language;
if (this.checkLocale(selectedLanguage)) {
localStorage.setItem(localStorageKeys.LANGUAGE, selectedLanguage.code);
this.setLangLocally();
const successMsg = `${selectedLanguage.flag} `
+ `${this.$t('language-switcher.success-msg')} ${selectedLanguage.name}`;
this.$toasted.show(successMsg, { className: 'toast-success' });
this.$modal.hide(this.modalName);
} else {
this.$toasted.show('Unable to update language', { className: 'toast-error' });
}
},
/* Check language is supported, before saving */
checkLocale(selectedLanguage) {
if (!selectedLanguage || !selectedLanguage.code) return false;
const i18nLocales = this.$i18n.availableLocales;
return i18nLocales.includes(selectedLanguage.code);
},
/* Apply language locally */
setLangLocally() {
if (this.language && this.language.code) {
this.$i18n.locale = this.language.code;
}
},
},
};
</script>
<style scoped lang="scss">
.language-switcher {
height: 100%;
margin: 0;
padding: 1rem;
background: var(--config-settings-background);
color: var(--config-settings-color);
h3.title {
text-align: center;
}
p.intro {
margin: 0;
}
button.save-button {
margin: 0 auto;
width: 100%;
}
p.sad-times {
color: var(--warning);
text-align: center;
}
.language-dropdown {
margin: 1rem auto;
div.vs__dropdown-toggle {
padding: 0.2rem 0;
}
}
}
</style>

View File

@@ -1,13 +1,25 @@
<template>
<div>
<span class="options-label">Layout</span>
<span class="options-label">{{ $t('settings.layout-label') }}</span>
<div class="display-options">
<IconDeafault @click="updateDisplayLayout('auto')" v-tooltip="tooltip('Auto')"
:class="`layout-icon ${displayLayout === 'auto' ? 'selected' : ''}`" tabindex="-2" />
<IconHorizontal @click="updateDisplayLayout('horizontal')" v-tooltip="tooltip('Horizontal')"
:class="`layout-icon ${displayLayout === 'horizontal' ? 'selected' : ''}`" tabindex="-2" />
<IconVertical @click="updateDisplayLayout('vertical')" v-tooltip="tooltip('Vertical')"
:class="`layout-icon ${displayLayout === 'vertical' ? 'selected' : ''}`" tabindex="-2" />
<IconDeafault
@click="updateDisplayLayout('auto')"
v-tooltip="tooltip($t('settings.layout-auto'))"
:class="`layout-icon ${displayLayout === 'auto' ? 'selected' : ''}`"
tabindex="-2"
/>
<IconHorizontal
@click="updateDisplayLayout('horizontal')"
v-tooltip="tooltip($t('settings.layout-horizontal'))"
:class="`layout-icon ${displayLayout === 'horizontal' ? 'selected' : ''}`"
tabindex="-2"
/>
<IconVertical
@click="updateDisplayLayout('vertical')"
v-tooltip="tooltip($t('settings.layout-vertical'))"
:class="`layout-icon ${displayLayout === 'vertical' ? 'selected' : ''}`"
tabindex="-2"
/>
</div>
</div>
</template>

View File

@@ -1,16 +1,16 @@
<template>
<form>
<label for="filter-tiles">Search</label>
<label for="filter-tiles">{{ $t('search.search-label') }}</label>
<input
id="filter-tiles"
v-model="input"
ref="filter"
placeholder="Start typing to filter..."
:placeholder="$t('search.search-placeholder')"
v-on:input="userIsTypingSomething"
@keydown.esc="clearFilterInput" />
<i v-if="input.length > 0"
class="clear-search"
title="Clear search"
:title="$t('search.clear-search-tooltip')"
@click="clearFilterInput">x</i>
</form>
</template>

View File

@@ -1,7 +1,7 @@
<template>
<div class="theme-selector-section" v-click-outside="closeThemeConfigurator">
<div>
<span class="theme-label">Theme</span>
<span class="theme-label">{{ $t('settings.theme-label') }}</span>
<v-select
:options="themeNames"
v-model="selectedTheme"

View File

@@ -1,18 +1,23 @@
// Import core framework and essential utils
import Vue from 'vue';
import VueI18n from 'vue-i18n'; // i18n for localization
/* Import component Vue plugins, used throughout the app */
// Import component Vue plugins, used throughout the app
import VTooltip from 'v-tooltip'; // A Vue directive for Popper.js, tooltip component
import VModal from 'vue-js-modal'; // Modal component
import VSelect from 'vue-select'; // Select dropdown component
import VTabs from 'vue-material-tabs'; // Tab view component, used on the config page
import Toasted from 'vue-toasted'; // Toast component, used to show confirmation notifications
import { toastedOptions } from '@/utils/defaults';
// Import base Dashy components and utils
import Dashy from '@/App.vue';
import router from '@/router';
import registerServiceWorker from '@/registerServiceWorker';
import clickOutside from '@/utils/ClickOutside';
import { toastedOptions, language as defaultLanguage } from '@/utils/defaults';
import { messages } from '@/utils/languages';
Vue.use(VueI18n);
Vue.use(VTooltip);
Vue.use(VModal);
Vue.use(VTabs);
@@ -22,10 +27,18 @@ Vue.directive('clickOutside', clickOutside);
Vue.config.productionTip = false;
// Setup i18n translations
const i18n = new VueI18n({
locale: defaultLanguage,
fallbackLocale: defaultLanguage,
messages,
});
// Register Service Worker
registerServiceWorker();
new Vue({
router,
render: (awesome) => awesome(Dashy),
}).$mount('#app');
// Render function
const render = (awesome) => awesome(Dashy);
// All done, now just initialize main Vue app!
new Vue({ router, render, i18n }).$mount('#app');

View File

@@ -24,8 +24,8 @@ html {
/* Overriding styles for the modal component */
.vm--modal, .dashy-modal {
box-shadow: 0 40px 70px -2px hsl(0deg 0% 0% / 60%), 1px 1px 6px var(--primary) !important;
min-width: 300px;
min-height: 500px;
min-width: 350px;
min-height: 200px;
}
.vm--overlay {
background: #00000080;
@@ -48,4 +48,32 @@ html {
background: var(--success) !important;
color: var(--white) !important;
font-size: 1.25rem !important;
}
/* v-select, dropdown styles */
.v-select {
.vs__dropdown-toggle {
border-color: var(--primary);
background: var(--background);
cursor: pointer;
span.vs__selected {
color: var(--primary);
}
.vs__actions svg path { fill: var(--primary); }
}
ul.vs__dropdown-menu {
background: var(--background);
border-color: var(--primary);
li {
color: var(--primary);
&:hover {
color: var(--background);
background: var(--primary);
}
&.vs__dropdown-option--highlight {
color: var(--background);
background: var(--primary);
}
}
}
}

View File

@@ -11,6 +11,7 @@ import {
pageInfo as defaultPageInfo,
iconSize as defaultIconSize,
layout as defaultLayout,
language as defaultLanguage,
} from '@/utils/defaults';
import conf from '../../public/conf.yml';
@@ -33,6 +34,8 @@ export default class ConfigAccumulator {
|| appConfigFile.layout || defaultLayout;
usersAppConfig.iconSize = localStorage[localStorageKeys.ICON_SIZE]
|| appConfigFile.iconSize || defaultIconSize;
usersAppConfig.language = localStorage[localStorageKeys.LANGUAGE]
|| appConfigFile.language || defaultLanguage;
return usersAppConfig;
}

View File

@@ -1,5 +1,11 @@
import ConfigAccumulator from '@/utils/ConfigAccumalator';
import { visibleComponents, localStorageKeys, theme as defaultTheme } from '@/utils/defaults';
import { languages } from '@/utils/languages';
import {
visibleComponents,
localStorageKeys,
theme as defaultTheme,
language as defaultLanguage,
} from '@/utils/defaults';
/**
* Initiates the Accumulator class and generates a complete config object
@@ -74,3 +80,15 @@ export const getCustomKeyShortcuts = () => {
});
return results.flat();
};
/**
* Gets the users chosen language. Defaults to English.
* @returns {object} Language, including code, name and flag
*/
export const getUsersLanguage = () => {
const langCode = localStorage[localStorageKeys.LANGUAGE]
|| config.appConfig.language
|| defaultLanguage;
const langObj = languages.find(lang => lang.code === langCode);
return langObj;
};

View File

@@ -54,6 +54,10 @@
"type": "string",
"description": "A URL to an image asset to be displayed as background"
},
"language": {
"type": "string",
"description": "The ISO code of your desired language, must have translations present, check docs for more info"
},
"theme": {
"type": "string",
"default": "callisto",

View File

@@ -11,6 +11,8 @@ module.exports = {
},
/* Default appConfig to be used, if user does not specify their own */
appConfig: {},
/* Default language code */
language: 'en',
/* Default icon size to be applied on initial load */
iconSize: 'medium',
/* Default layout to be applied on initial load */
@@ -58,6 +60,7 @@ module.exports = {
},
/* Key names for local storage identifiers */
localStorageKeys: {
LANGUAGE: 'language',
HIDE_WELCOME_BANNER: 'hideWelcomeHelpers',
LAYOUT_ORIENTATION: 'layoutOrientation',
COLLAPSE_STATE: 'collapseState',
@@ -87,6 +90,7 @@ module.exports = {
REBUILD_APP: 'REBUILD_APP',
THEME_MAKER: 'THEME_MAKER',
ABOUT_APP: 'ABOUT_APP',
LANG_SWITCHER: 'LANG_SWITCHER',
},
/* Key names for the top-level objects in conf.yml */
topLevelConfKeys: {

23
src/utils/languages.js Normal file
View File

@@ -0,0 +1,23 @@
// Locales - Import translation files here!
import en from '@/assets/locales/en.json';
// Language data - Add your country name, locale code and imported file here
export const languages = [
{
name: 'English',
code: 'en',
locale: en,
flag: '🇬🇧',
},
// Including:
// name - Human readable name for your language (e.g German)
// code - ISO language code (e.g. de)
// locale - The file that you imported above
// flag - A nice emoji flag (optional, e.g. 🇩🇪)
];
const i18nMessages = {};
languages.forEach((lang) => {
i18nMessages[lang.code] = lang.locale;
});
export const messages = i18nMessages;

View File

@@ -13,7 +13,7 @@
:appConfig="appConfig"
:pageInfo="pageInfo"
:modalOpen="modalOpen"
class="filter-container"
class="settings-outer"
/>
<!-- Main content, section for each group of items -->
<div v-if="checkTheresData(sections)"
@@ -27,14 +27,14 @@
:groupId="`section-${index}`"
:items="filterTiles(section.items)"
:itemSize="itemSizeBound"
@itemClicked="finishedSearching()"
@change-modal-visibility="updateModalVisibility"
:class="(filterTiles(section.items).length === 0 && searchValue) ? 'no-results' : ''"
@itemClicked="finishedSearching()"
@change-modal-visibility="updateModalVisibility"
:class="(filterTiles(section.items).length === 0 && searchValue) ? 'no-results' : ''"
/>
</div>
<!-- Show message when there's no data to show -->
<div v-if="checkIfResults()" class="no-data">
{{searchValue ? 'No Search Results' : 'No Data Configured'}}
{{searchValue ? $t('home.no-results') : $t('home.no-data')}}
</div>
</div>
</template>
@@ -63,6 +63,7 @@ export default {
modalOpen: false, // When true, keybindings are disabled
}),
computed: {
/* Updates layout (when button clicked), and saves in local storage */
layoutOrientation: {
get() { return this.appConfig.layout || Defaults.layout; },
set: function setLayout(layout) {
@@ -70,6 +71,7 @@ export default {
this.layout = layout;
},
},
/* Updates icon size (when button clicked), and saves in local storage */
iconSize: {
get() { return this.appConfig.iconSize || Defaults.iconSize; },
set: function setIconSize(iconSize) {
@@ -192,6 +194,7 @@ export default {
return itemsFound;
}
},
/* If user has a background image, then generate CSS attributes */
getBackgroundImage() {
if (this.appConfig && this.appConfig.backgroundImg) {
return `background: url('${this.appConfig.backgroundImg}');background-size:cover;`;
@@ -267,6 +270,7 @@ export default {
.no-results { display: none; }
}
/* Custom styles only applied when there is no sections in config */
.no-data {
font-size: 2rem;
color: var(--background);
@@ -277,7 +281,8 @@ export default {
border-radius: var(--curve-factor);
}
section.filter-container {
/* Settings section, includes search, config and user settings */
section.settings-outer {
border-bottom: 1px solid var(--outline-color);
@include phone {
flex-direction: column;

View File

@@ -1,20 +1,31 @@
<template>
<div class="login-page">
<form class="login-form">
<h2 class="login-title">Dashy</h2>
<Input v-model="username" label="Username" class="login-field username" type="text" />
<Input v-model="password" label="Password" class="login-field password" type="password" />
<label>Remember me for</label>
<h2 class="login-title">{{ $t('login.title') }}</h2>
<Input
v-model="username"
type="text"
:label="$t('login.username-label')"
class="login-field username"
/>
<Input
v-model="password"
type="password"
:label="$t('login.password-label')"
class="login-field password"
/>
<label>{{ $t('login.remember-me-label') }}</label>
<v-select
v-model="timeout"
:options="dropDownMenu"
label="label"
:selectOnTab="true"
:options="dropDownMenu"
class="login-time-dropdown"
/>
<Button class="login-button" :click="submitLogin">Login</Button>
<Button class="login-button" :click="submitLogin">
{{ $t('login.login-button') }}
</Button>
<transition name="bounce">
<p :class="`login-error-message ${status}`" v-show="message">{{ message }}</p>
<p :class="`login-error-message ${status}`" v-show="message">{{ message }}</p>
</transition>
</form>
</div>
@@ -38,12 +49,12 @@ export default {
password: '',
message: '',
status: 'waiting', // wating, error, success
timeout: { label: 'Never', time: 0 },
dropDownMenu: [ // Data for timeout dropdown menu, label + value
{ label: 'Never', time: 0 }, // Time is specified in ms
{ label: '4 Hours', time: 14400 * 1000 },
{ label: '1 Day', time: 86400 * 1000 },
{ label: '1 Week', time: 604800 * 1000 },
timeout: { label: this.$t('login.remember-me-never'), time: 0 },
dropDownMenu: [ // Data for timeout dropdown menu, translated label + value in ms
{ label: this.$t('login.remember-me-never'), time: 0 },
{ label: this.$t('login.remember-me-hour'), time: 14400 * 1000 },
{ label: this.$t('login.remember-me-day'), time: 86400 * 1000 },
{ label: this.$t('login.remember-me-week'), time: 604800 * 1000 },
],
};
},
@@ -52,8 +63,9 @@ export default {
Input,
},
methods: {
/* Checks form is filled in, then initiates the login, and redirects to /home */
submitLogin() {
const timeout = this.timeout.time || 0;
const timeout = this.timeout ? this.timeout.time : 0;
const response = checkCredentials(this.username, this.password, this.appConfig.auth || []);
this.message = response.msg; // Show error or success message to the user
this.status = response.correct ? 'success' : 'error';
@@ -64,6 +76,7 @@ export default {
}, 250);
}
},
/* Since we don't have the Theme setter at this point, we must manually set users theme */
setTheme() {
const theme = localStorage[localStorageKeys.THEME] || Defaults.theme;
document.getElementsByTagName('html')[0].setAttribute('data-theme', theme);
@@ -78,43 +91,48 @@ export default {
<style lang="scss">
/* Login page base styles */
.login-page {
display: flex;
align-items: center;
justify-content: center;
min-height: 800px;
min-height: calc(100vh - var(--footer-height));
.login-form {
/* Login form container */
form.login-form {
background: var(--login-form-background);
color: var(--login-form-color);
border: 1px solid var(--login-form-color);
border-radius: var(--curve-factor);
font-size: 1.4rem;
padding: 2rem;
margin: 2rem auto;
display: flex;
flex-direction: column;
/* Login form title */
h2.login-title {
font-size: 3rem;
margin: 0 0 1rem 0;
text-align: center;
cursor: default;
}
/* Set sizings for input fields and login button */
.login-field input, Button.login-button {
width: 18rem;
width: 20rem;
margin: 0.5rem auto;
font-size: 1.4rem;
padding: 0.5rem 1rem;
}
/* Custom colors for username/ password input fields */
.login-field input {
color: var(--login-form-color);
border-color: var(--login-form-color);
background: var(--login-form-background);
&:focus {
}
}
/* Custom colors for Login Button */
Button.login-button {
background: var(--login-form-color);
border-color: var(--login-form-background);
@@ -128,6 +146,7 @@ export default {
box-shadow: 1px 1px 6px var(--login-form-color);
}
}
/* Apply color to status message, depending on status */
p.login-error-message {
font-size: 1rem;
text-align: center;
@@ -138,6 +157,7 @@ export default {
}
}
/* Enter animations for error/ success message */
.bounce-enter-active { animation: bounce-in 0.25s; }
.bounce-leave-active { animation: bounce-in 0.25s reverse; }
@keyframes bounce-in {
@@ -146,6 +166,7 @@ export default {
100% { transform: scale(1); }
}
/* Custom styles for dropdown component */
.v-select.login-time-dropdown {
margin: 0.5rem 0;
.vs__dropdown-toggle {