Merge branch 'master' of github.com:Lissy93/dashy into FEATURE/sub-items
This commit is contained in:
37
src/components/Configuration/AccessError.vue
Normal file
37
src/components/Configuration/AccessError.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="error">
|
||||
<p>
|
||||
Error: Configuration has been disabled on this instance.
|
||||
Please contact your administrator for more information.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
|
||||
export default {
|
||||
name: 'AccessError',
|
||||
mounted() {
|
||||
ErrorHandler('Access Error: Config has been disabled on this instance');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.error {
|
||||
padding: 0.5rem 1rem;
|
||||
min-width: 20rem;
|
||||
width: 50%;
|
||||
margin: 2rem auto;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 1.2rem;
|
||||
color: var(--warning);
|
||||
border-radius: var(--curve-factor);
|
||||
border: 1px dashed var(--warning);
|
||||
background: var(--background);
|
||||
}
|
||||
</style>
|
||||
@@ -30,7 +30,8 @@
|
||||
<a href="https://github.com/Lissy93/dashy/blob/master/.github/SECURITY.md">Security Policy</a>
|
||||
<!-- License -->
|
||||
<h3>License</h3>
|
||||
Licensed under MIT X11. Copyright <a href="https://aliciasykes.com">Alicia Sykes</a> © 2021.<br>
|
||||
Licensed under <a href="https://github.com/Lissy93/dashy/blob/master/LICENSE">MIT X11</a>.
|
||||
Copyright <a href="https://aliciasykes.com">Alicia Sykes</a> © 2021.<br>
|
||||
For licenses for third-party modules, please see <a href="https://github.com/Lissy93/dashy/blob/master/.github/LEGAL.md">Legal</a>.<br>
|
||||
For the full list of contributors and thanks, see <a href="https://github.com/Lissy93/dashy/blob/master/docs/credits.md">Credits</a>.
|
||||
<!-- App Version -->
|
||||
|
||||
@@ -1,49 +1,61 @@
|
||||
<template>
|
||||
<Tabs :navAuto="true" name="Add Item" ref="tabView">
|
||||
<Tabs :navAuto="true" name="Add Item" ref="tabView" v-bind:class="{ hideTabs: !enableConfig }">
|
||||
<!-- Main tab -->
|
||||
<TabItem :name="$t('config.main-tab')" class="main-tab">
|
||||
<div class="main-options-container">
|
||||
<div class="config-buttons">
|
||||
<h2>{{ $t('config.heading') }}</h2>
|
||||
<a class="hyperlink-wrapper" @click="openExportConfigModal()">
|
||||
<button class="config-button center">
|
||||
<DownloadIcon class="button-icon"/>
|
||||
{{ $t('config.download-config-button') }}
|
||||
</button>
|
||||
</a>
|
||||
<button class="config-button center" @click="() => navigateToTab(1)">
|
||||
<EditIcon class="button-icon"/>
|
||||
<!-- Export config button -->
|
||||
<Button class="config-button" :disallow="!enableConfig" :click="openExportConfigModal">
|
||||
{{ $t('config.download-config-button') }}
|
||||
<DownloadIcon class="button-icon"/>
|
||||
</Button>
|
||||
<!-- Edit config button -->
|
||||
<Button class="config-button" :disallow="!enableConfig" :click="openEditConfigTab">
|
||||
{{ $t('config.edit-config-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="openLanguageSwitchModal()">
|
||||
<LanguageIcon class="button-icon"/>
|
||||
<EditIcon class="button-icon"/>
|
||||
</Button>
|
||||
<!-- Language switcher button -->
|
||||
<Button class="config-button" :click="openLanguageSwitchModal">
|
||||
{{ $t('config.change-language-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="() => navigateToTab(3)">
|
||||
<CustomCssIcon class="button-icon"/>
|
||||
<LanguageIcon class="button-icon"/>
|
||||
</Button>
|
||||
<!-- CSS / Styling button -->
|
||||
<Button class="config-button" :disallow="!enableConfig" :click="openEditCssTab">
|
||||
{{ $t('config.edit-css-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="() => navigateToTab(2)">
|
||||
<CloudIcon class="button-icon"/>
|
||||
<CustomCssIcon class="button-icon"/>
|
||||
</Button>
|
||||
<!-- Cloud sync button -->
|
||||
<Button class="config-button" :disallow="!enableConfig" :click="openCloudSyncTab">
|
||||
{{backupId ? $t('config.edit-cloud-sync-button') : $t('config.cloud-sync-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="openRebuildAppModal()">
|
||||
<RebuildIcon class="button-icon"/>
|
||||
<CloudIcon class="button-icon"/>
|
||||
</Button>
|
||||
<!-- Rebuild app button -->
|
||||
<Button class="config-button" :disallow="!enableConfig" :click="openRebuildAppModal">
|
||||
{{ $t('config.rebuild-app-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="resetLocalSettings()">
|
||||
<DeleteIcon class="button-icon"/>
|
||||
<RebuildIcon class="button-icon"/>
|
||||
</Button>
|
||||
<!-- Reset local changes button -->
|
||||
<Button class="config-button" :click="resetLocalSettings">
|
||||
{{ $t('config.reset-settings-button') }}
|
||||
</button>
|
||||
<button class="config-button center" @click="openAboutModal()">
|
||||
<IconAbout class="button-icon" />
|
||||
<DeleteIcon class="button-icon"/>
|
||||
</Button>
|
||||
<!-- About modal button -->
|
||||
<Button class="config-button" :click="openAboutModal">
|
||||
{{ $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>
|
||||
<IconAbout class="button-icon" />
|
||||
</Button>
|
||||
<!-- Display app version and language -->
|
||||
<p class="language">{{ getLanguage() }}</p>
|
||||
<AppVersion />
|
||||
</div>
|
||||
<!-- Display note if Config disabled, or if on mobile -->
|
||||
<p v-if="!enableConfig" class="config-disabled-note">
|
||||
Some configuration features have been disabled by your administrator
|
||||
</p>
|
||||
<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>
|
||||
<div class="config-note">
|
||||
<span>{{ $t('config.backup-note') }}</span>
|
||||
</div>
|
||||
@@ -51,13 +63,13 @@
|
||||
<!-- Rebuild App Modal -->
|
||||
<RebuildApp />
|
||||
</TabItem>
|
||||
<TabItem :name="$t('config.edit-config-tab')">
|
||||
<TabItem :name="$t('config.edit-config-tab')" v-if="enableConfig">
|
||||
<JsonEditor />
|
||||
</TabItem>
|
||||
<TabItem :name="$t('cloud-sync.title')">
|
||||
<TabItem :name="$t('cloud-sync.title')" v-if="enableConfig">
|
||||
<CloudBackupRestore />
|
||||
</TabItem>
|
||||
<TabItem :name="$t('config.custom-css-tab')">
|
||||
<TabItem :name="$t('config.custom-css-tab')" v-if="enableConfig">
|
||||
<CustomCssEditor />
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
@@ -65,15 +77,16 @@
|
||||
|
||||
<script>
|
||||
|
||||
import JsonToYaml from '@/utils/JsonToYaml';
|
||||
import { localStorageKeys, modalNames } from '@/utils/defaults';
|
||||
import { getUsersLanguage } from '@/utils/ConfigHelpers';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import StoreKeys from '@/utils/StoreMutations';
|
||||
import JsonEditor from '@/components/Configuration/JsonEditor';
|
||||
import CustomCssEditor from '@/components/Configuration/CustomCss';
|
||||
import CloudBackupRestore from '@/components/Configuration/CloudBackupRestore';
|
||||
import RebuildApp from '@/components/Configuration/RebuildApp';
|
||||
import AppVersion from '@/components/Configuration/AppVersion';
|
||||
import Button from '@/components/FormElements/Button';
|
||||
|
||||
import DownloadIcon from '@/assets/interface-icons/config-download-file.svg';
|
||||
import DeleteIcon from '@/assets/interface-icons/config-delete-local.svg';
|
||||
@@ -88,7 +101,6 @@ export default {
|
||||
name: 'ConfigContainer',
|
||||
data() {
|
||||
return {
|
||||
jsonParser: JsonToYaml,
|
||||
backupId: localStorage[localStorageKeys.BACKUP_ID] || '',
|
||||
appVersion: process.env.VUE_APP_VERSION,
|
||||
latestVersion: '',
|
||||
@@ -101,11 +113,12 @@ export default {
|
||||
sections: function getSections() {
|
||||
return this.config.sections;
|
||||
},
|
||||
yaml() {
|
||||
return this.jsonParser(this.config);
|
||||
enableConfig() {
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Button,
|
||||
JsonEditor,
|
||||
CustomCssEditor,
|
||||
CloudBackupRestore,
|
||||
@@ -127,7 +140,11 @@ export default {
|
||||
this.$refs.tabView.activeTabItem(itemToSelect);
|
||||
},
|
||||
openRebuildAppModal() {
|
||||
this.$modal.show(modalNames.REBUILD_APP);
|
||||
if (this.enableConfig) {
|
||||
this.$modal.show(modalNames.REBUILD_APP);
|
||||
} else {
|
||||
this.unauthorized();
|
||||
}
|
||||
},
|
||||
openAboutModal() {
|
||||
this.$modal.show(modalNames.ABOUT_APP);
|
||||
@@ -136,7 +153,20 @@ export default {
|
||||
this.$modal.show(modalNames.LANG_SWITCHER);
|
||||
},
|
||||
openExportConfigModal() {
|
||||
this.$modal.show(modalNames.EXPORT_CONFIG_MENU);
|
||||
if (this.enableConfig) {
|
||||
this.$modal.show(modalNames.EXPORT_CONFIG_MENU);
|
||||
} else {
|
||||
this.unauthorized();
|
||||
}
|
||||
},
|
||||
openEditConfigTab() {
|
||||
this.navigateToTab(1);
|
||||
},
|
||||
openCloudSyncTab() {
|
||||
this.navigateToTab(2);
|
||||
},
|
||||
openEditCssTab() {
|
||||
this.navigateToTab(3);
|
||||
},
|
||||
/* Checks that the user is sure, then resets site-wide local storage, and reloads page */
|
||||
resetLocalSettings() {
|
||||
@@ -160,6 +190,9 @@ export default {
|
||||
if (navToTab && isValidTabIndex(navToTab)) this.navigateToTab(navToTab);
|
||||
this.$store.commit(StoreKeys.CONF_MENU_INDEX, undefined);
|
||||
},
|
||||
unauthorized() {
|
||||
ErrorHandler('Unauthorized Operation - Config Disabled');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.navigateToStartingTab();
|
||||
@@ -180,17 +213,13 @@ pre {
|
||||
a.config-button, button.config-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 0.25rem auto;
|
||||
justify-content: flex-end;
|
||||
font-size: 1.2rem;
|
||||
background: var(--config-settings-background);
|
||||
color: var(--config-settings-color);
|
||||
border: 1px solid var(--config-settings-color);
|
||||
border-radius: var(--curve-factor);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
margin: 0.5rem auto;
|
||||
min-width: 18rem;
|
||||
min-width: 15rem;
|
||||
width: 100%;
|
||||
svg.button-icon {
|
||||
path {
|
||||
@@ -199,9 +228,8 @@ a.config-button, button.config-button {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
padding: 0.2rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
&:hover {
|
||||
&:hover:not(.disallowed) {
|
||||
background: var(--config-settings-color);
|
||||
color: var(--config-settings-background);
|
||||
svg path {
|
||||
@@ -226,12 +254,6 @@ p.app-version, p.language {
|
||||
|
||||
div.code-container {
|
||||
background: var(--config-code-background);
|
||||
#conf-yaml span {
|
||||
font-family: var(--font-monospace), monospace !important;
|
||||
&.hljs-attr {
|
||||
font-weight: bold !important;
|
||||
}
|
||||
}
|
||||
.yaml-action-buttons {
|
||||
position: absolute;
|
||||
top: 1.5rem;
|
||||
@@ -320,6 +342,13 @@ div.code-container {
|
||||
display: none;
|
||||
@include tablet-up { display: block; }
|
||||
}
|
||||
p.config-disabled-note {
|
||||
margin: 0.5rem auto;
|
||||
padding: 0 0.5rem;
|
||||
font-weight: bold;
|
||||
color: var(--warning);
|
||||
opacity: var(--dimming-factor);
|
||||
}
|
||||
p.small-screen-note {
|
||||
@include phone {
|
||||
display: block !important;
|
||||
@@ -335,6 +364,10 @@ p.small-screen-note {
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
.hideTabs .tab__pagination {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.tabs__content {
|
||||
height: -webkit-fill-available;
|
||||
height: -moz-available;
|
||||
@@ -364,6 +397,9 @@ p.small-screen-note {
|
||||
font-weight: bold !important;
|
||||
color: var(--config-settings-color) !important;
|
||||
}
|
||||
&:hover span {
|
||||
color: var(--config-settings-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tab__nav__items .tab__nav__item.active {
|
||||
@@ -374,11 +410,4 @@ p.small-screen-note {
|
||||
}
|
||||
}
|
||||
|
||||
#conf-yaml {
|
||||
background: var(--white);
|
||||
.hljs-attr {
|
||||
color: #9c03f5;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="json-editor-outer">
|
||||
<div class="json-editor-outer" v-if="allowViewConfig">
|
||||
<!-- Main JSON editor -->
|
||||
<v-jsoneditor v-model="jsonData" :options="options" />
|
||||
<!-- Options raido, and save button -->
|
||||
@@ -8,11 +8,11 @@
|
||||
:label="$t('config-editor.save-location-label')"
|
||||
:options="saveOptions"
|
||||
:initialOption="initialSaveMode"
|
||||
:disabled="!allowWriteToDisk"
|
||||
:disabled="!allowWriteToDisk || !allowSaveLocally"
|
||||
/>
|
||||
<!-- Save Buttons -->
|
||||
<div :class="`btn-container ${!isValid ? 'err' : ''}`">
|
||||
<Button :click="save">
|
||||
<Button :click="save" :disallow="!allowWriteToDisk && !allowSaveLocally">
|
||||
{{ $t('config-editor.save-button') }}
|
||||
</Button>
|
||||
<Button :click="startPreview">
|
||||
@@ -46,6 +46,7 @@
|
||||
</p>
|
||||
<p class="note">{{ $t('config.backup-note') }}</p>
|
||||
</div>
|
||||
<AccessError v-else />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -58,9 +59,9 @@ import ErrorHandler, { InfoHandler, InfoKeys } from '@/utils/ErrorHandler';
|
||||
import configSchema from '@/utils/ConfigSchema.json';
|
||||
import StoreKeys from '@/utils/StoreMutations';
|
||||
import { localStorageKeys, serviceEndpoints, modalNames } from '@/utils/defaults';
|
||||
import { isUserAdmin } from '@/utils/Auth';
|
||||
import Button from '@/components/FormElements/Button';
|
||||
import Radio from '@/components/FormElements/Radio';
|
||||
import AccessError from '@/components/Configuration/AccessError';
|
||||
|
||||
export default {
|
||||
name: 'JsonEditor',
|
||||
@@ -68,6 +69,7 @@ export default {
|
||||
VJsoneditor,
|
||||
Button,
|
||||
Radio,
|
||||
AccessError,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -97,12 +99,23 @@ export default {
|
||||
isValid() {
|
||||
return this.errorMessages.length < 1;
|
||||
},
|
||||
permissions() {
|
||||
// Returns: { allowWriteToDisk, allowSaveLocally, allowViewConfig }
|
||||
return this.$store.getters.permissions;
|
||||
},
|
||||
allowWriteToDisk() {
|
||||
const { appConfig } = this.config;
|
||||
return appConfig.allowConfigEdit !== false && isUserAdmin();
|
||||
return this.permissions.allowWriteToDisk;
|
||||
},
|
||||
allowSaveLocally() {
|
||||
return this.permissions.allowSaveLocally;
|
||||
},
|
||||
allowViewConfig() {
|
||||
return this.permissions.allowViewConfig;
|
||||
},
|
||||
initialSaveMode() {
|
||||
return this.allowWriteToDisk ? 'file' : 'local';
|
||||
if (this.allowWriteToDisk) return 'file';
|
||||
if (this.allowSaveLocally) return 'local';
|
||||
return '';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
@@ -166,6 +179,10 @@ export default {
|
||||
},
|
||||
/* Saves config to local browser storage */
|
||||
saveConfigLocally() {
|
||||
if (!this.allowSaveLocally) {
|
||||
ErrorHandler('Unable to save changes locally, this feature has been disabled');
|
||||
return;
|
||||
}
|
||||
const data = this.jsonData;
|
||||
if (data.sections) {
|
||||
localStorage.setItem(localStorageKeys.CONF_SECTIONS, JSON.stringify(data.sections));
|
||||
@@ -180,7 +197,7 @@ export default {
|
||||
if (data.appConfig.theme) {
|
||||
localStorage.setItem(localStorageKeys.THEME, data.appConfig.theme);
|
||||
}
|
||||
InfoHandler('Config has succesfully been saved in browser storage', InfoKeys.RAW_EDITOR);
|
||||
InfoHandler('Config has successfully been saved in browser storage', InfoKeys.RAW_EDITOR);
|
||||
this.showToast(this.$t('config-editor.success-msg-local'), true);
|
||||
},
|
||||
/* Clears config from browser storage, only removing relevant items */
|
||||
@@ -277,7 +294,7 @@ p.response-output {
|
||||
}
|
||||
|
||||
p.no-permission-note {
|
||||
color: var(--config-settings-color);
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
|
||||
@@ -51,7 +51,9 @@ import Button from '@/components/FormElements/Button';
|
||||
import RebuildIcon from '@/assets/interface-icons/application-rebuild.svg';
|
||||
import ReloadIcon from '@/assets/interface-icons/application-reload.svg';
|
||||
import LoadingAnimation from '@/assets/interface-icons/loader.svg';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
import { modalNames, serviceEndpoints } from '@/utils/defaults';
|
||||
import { isUserAdmin } from '@/utils/Auth';
|
||||
|
||||
export default {
|
||||
name: 'RebuildApp',
|
||||
@@ -79,6 +81,10 @@ export default {
|
||||
methods: {
|
||||
/* Calls to the rebuild endpoint, to kickoff the app build */
|
||||
startBuild() {
|
||||
if (!this.allowRebuild) { // Double check user is allowed
|
||||
ErrorHandler('Unable to trigger rebuild, insufficient permission');
|
||||
return;
|
||||
}
|
||||
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
|
||||
const endpoint = `${baseUrl}${serviceEndpoints.rebuild}`;
|
||||
this.loading = true;
|
||||
@@ -116,7 +122,10 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.appConfig.allowConfigEdit === false) {
|
||||
// Disable rebuild functionality if user not allowed
|
||||
if (this.appConfig.allowConfigEdit === false
|
||||
|| this.appConfig.preventWriteToDisk
|
||||
|| !isUserAdmin()) {
|
||||
this.allowRebuild = false;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
@click="click ? click() : () => null"
|
||||
:class="disallow ? 'disallowed': ''"
|
||||
:type="type || 'button'"
|
||||
:disabled="disabled"
|
||||
:disabled="disabled || disallow"
|
||||
v-tooltip="hoverText"
|
||||
:title="tooltip"
|
||||
>
|
||||
@@ -68,7 +68,7 @@ button {
|
||||
background: var(--background);
|
||||
border: 1px solid var(--primary);
|
||||
border-radius: var(--curve-factor);
|
||||
&:hover:not(:disabled) {
|
||||
&:hover:not(:disabled):not(.disallowed) {
|
||||
color: var(--background);
|
||||
background: var(--primary);
|
||||
border-color: var(--background);
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
:name="name"
|
||||
:id="name"
|
||||
:placeholder="placeholder"
|
||||
@keyup.enter="onEnter ? onEnter() : () => {}"
|
||||
class="input-field"
|
||||
/>
|
||||
<p
|
||||
@@ -35,6 +36,7 @@ export default {
|
||||
name: String, // Required unique ID value, for accessibility
|
||||
placeholder: String, // Optional placeholder value
|
||||
description: String, // Optional info paragraph
|
||||
onEnter: Function,
|
||||
type: {
|
||||
default: 'text', // Input type, e.g. text, password, number
|
||||
type: String,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
classes="dashy-modal edit-app-config"
|
||||
@closed="modalClosed"
|
||||
>
|
||||
<div class="edit-app-config-inner">
|
||||
<div class="edit-app-config-inner" v-if="allowViewConfig">
|
||||
<h3>{{ $t('interactive-editor.menu.edit-app-config-btn') }}</h3>
|
||||
<!-- Show caution message -->
|
||||
<div class="app-config-intro">
|
||||
@@ -35,6 +35,7 @@
|
||||
<!-- Save Button, lower -->
|
||||
<SaveCancelButtons :saveClick="saveToState" :cancelClick="cancelEditing" />
|
||||
</div>
|
||||
<AccessError v-else />
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
@@ -43,6 +44,7 @@ import FormSchema from '@formschema/native';
|
||||
import DashySchema from '@/utils/ConfigSchema';
|
||||
import StoreKeys from '@/utils/StoreMutations';
|
||||
import { modalNames } from '@/utils/defaults';
|
||||
import AccessError from '@/components/Configuration/AccessError';
|
||||
import SaveCancelButtons from '@/components/InteractiveEditor/SaveCancelButtons';
|
||||
|
||||
export default {
|
||||
@@ -58,6 +60,7 @@ export default {
|
||||
components: {
|
||||
FormSchema,
|
||||
SaveCancelButtons,
|
||||
AccessError,
|
||||
},
|
||||
mounted() {
|
||||
this.formData = this.appConfig;
|
||||
@@ -66,6 +69,9 @@ export default {
|
||||
appConfig() {
|
||||
return this.$store.getters.appConfig;
|
||||
},
|
||||
allowViewConfig() {
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/* When form submitteed, update VueX store with new appConfig, and close modal */
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
classes="dashy-modal edit-item"
|
||||
@closed="modalClosed"
|
||||
>
|
||||
<div class="edit-item-inner">
|
||||
<div class="edit-item-inner" v-if="allowViewConfig">
|
||||
<!-- Title and Item ID -->
|
||||
<h3 class="title">Edit Item</h3>
|
||||
<p class="sub-title">Editing {{item.title}} (ID: {{itemId}})</p>
|
||||
@@ -67,6 +67,7 @@
|
||||
<!-- Save to state button -->
|
||||
<SaveCancelButtons :saveClick="saveItem" :cancelClick="modalClosed" />
|
||||
</div>
|
||||
<AccessError v-else />
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
@@ -74,6 +75,7 @@
|
||||
import AddIcon from '@/assets/interface-icons/interactive-editor-add.svg';
|
||||
import BinIcon from '@/assets/interface-icons/interactive-editor-remove.svg';
|
||||
import SaveCancelButtons from '@/components/InteractiveEditor/SaveCancelButtons';
|
||||
import AccessError from '@/components/Configuration/AccessError';
|
||||
import Input from '@/components/FormElements/Input';
|
||||
import Radio from '@/components/FormElements/Radio';
|
||||
import Select from '@/components/FormElements/Select';
|
||||
@@ -101,13 +103,18 @@ export default {
|
||||
isNew: Boolean,
|
||||
parentSectionTitle: String, // If adding new item, which section to add it under
|
||||
},
|
||||
computed: {},
|
||||
computed: {
|
||||
allowViewConfig() {
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
Input,
|
||||
Radio,
|
||||
Select,
|
||||
AddIcon,
|
||||
BinIcon,
|
||||
AccessError,
|
||||
SaveCancelButtons,
|
||||
},
|
||||
mounted() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<!-- Intro Info -->
|
||||
<div class="edit-mode-bottom-banner">
|
||||
<div class="edit-banner-section intro-container">
|
||||
<div class="edit-banner-section intro-container" v-if="showEditMsg">
|
||||
<p class="section-sub-title edit-mode-intro l-1">
|
||||
{{ $t('interactive-editor.menu.edit-mode-subtitle') }}
|
||||
</p>
|
||||
@@ -9,6 +9,9 @@
|
||||
{{ $t('interactive-editor.menu.edit-mode-description') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="edit-banner-section intro-container" v-else>
|
||||
<AccessError class="no-permission" />
|
||||
</div>
|
||||
<div class="edit-banner-section empty-space"></div>
|
||||
<!-- Save Buttons -->
|
||||
<div class="edit-banner-section save-buttons-container">
|
||||
@@ -17,6 +20,7 @@
|
||||
</p>
|
||||
<Button
|
||||
:click="saveLocally"
|
||||
:disallow="!permissions.allowSaveLocally"
|
||||
v-tooltip="tooltip($t('interactive-editor.menu.save-locally-tooltip'))"
|
||||
>
|
||||
{{ $t('interactive-editor.menu.save-locally-btn') }}
|
||||
@@ -24,7 +28,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
:click="writeToDisk"
|
||||
:disabled="!allowWriteToDisk"
|
||||
:disallow="!permissions.allowWriteToDisk"
|
||||
v-tooltip="tooltip($t('interactive-editor.menu.save-disk-tooltip'))"
|
||||
>
|
||||
{{ $t('interactive-editor.menu.save-disk-btn') }}
|
||||
@@ -32,6 +36,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
:click="openExportConfigMenu"
|
||||
:disallow="!permissions.allowViewConfig"
|
||||
v-tooltip="tooltip($t('interactive-editor.menu.export-config-tooltip'))"
|
||||
>
|
||||
{{ $t('interactive-editor.menu.export-config-btn') }}
|
||||
@@ -52,6 +57,7 @@
|
||||
</p>
|
||||
<Button
|
||||
:click="openEditPageInfo"
|
||||
:disallow="!permissions.allowViewConfig"
|
||||
v-tooltip="tooltip($t('interactive-editor.menu.edit-page-info-tooltip'))"
|
||||
>
|
||||
{{ $t('interactive-editor.menu.edit-page-info-btn') }}
|
||||
@@ -59,6 +65,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
:click="openEditAppConfig"
|
||||
:disallow="!permissions.allowViewConfig"
|
||||
v-tooltip="tooltip($t('interactive-editor.menu.edit-app-config-tooltip'))"
|
||||
>
|
||||
{{ $t('interactive-editor.menu.edit-app-config-btn') }}
|
||||
@@ -82,7 +89,7 @@ import EditPageInfo from '@/components/InteractiveEditor/EditPageInfo';
|
||||
import EditAppConfig from '@/components/InteractiveEditor/EditAppConfig';
|
||||
import { modalNames, localStorageKeys, serviceEndpoints } from '@/utils/defaults';
|
||||
import ErrorHandler, { InfoHandler } from '@/utils/ErrorHandler';
|
||||
import { isUserAdmin } from '@/utils/Auth';
|
||||
import AccessError from '@/components/Configuration/AccessError';
|
||||
|
||||
import SaveLocallyIcon from '@/assets/interface-icons/interactive-editor-save-locally.svg';
|
||||
import SaveToDiskIcon from '@/assets/interface-icons/interactive-editor-save-disk.svg';
|
||||
@@ -103,14 +110,18 @@ export default {
|
||||
AppConfigIcon,
|
||||
PageInfoIcon,
|
||||
EditAppConfig,
|
||||
AccessError,
|
||||
},
|
||||
computed: {
|
||||
config() {
|
||||
return this.$store.state.config;
|
||||
},
|
||||
allowWriteToDisk() {
|
||||
const { appConfig } = this.config;
|
||||
return appConfig.allowConfigEdit !== false && isUserAdmin();
|
||||
permissions() {
|
||||
// Returns: { allowWriteToDisk, allowSaveLocally, allowViewConfig }
|
||||
return this.$store.getters.permissions;
|
||||
},
|
||||
showEditMsg() {
|
||||
return this.permissions.allowWriteToDisk || this.permissions.allowSaveLocally;
|
||||
},
|
||||
},
|
||||
data() {
|
||||
@@ -149,6 +160,10 @@ export default {
|
||||
localStorage.removeItem(localStorageKeys.CONF_SECTIONS);
|
||||
},
|
||||
saveLocally() {
|
||||
if (!this.permissions.allowSaveLocally) {
|
||||
ErrorHandler('Unable to save changes locally, this feature has been disabled');
|
||||
return;
|
||||
}
|
||||
const data = this.config;
|
||||
localStorage.setItem(localStorageKeys.CONF_SECTIONS, JSON.stringify(data.sections));
|
||||
localStorage.setItem(localStorageKeys.PAGE_INFO, JSON.stringify(data.pageInfo));
|
||||
@@ -161,6 +176,10 @@ export default {
|
||||
this.$store.commit(StoreKeys.SET_EDIT_MODE, false);
|
||||
},
|
||||
writeToDisk() {
|
||||
if (this.config.appConfig.preventWriteToDisk) {
|
||||
ErrorHandler('Unable to write changed to disk, as this functionality is disabled');
|
||||
return;
|
||||
}
|
||||
// 1. Convert JSON into YAML
|
||||
const yamlOptions = {};
|
||||
const yaml = jsYaml.dump(this.config, yamlOptions);
|
||||
@@ -228,11 +247,16 @@ div.edit-mode-bottom-banner {
|
||||
}
|
||||
/* Intro-text container */
|
||||
&.intro-container {
|
||||
p.edit-mode-intro {
|
||||
p.edit-mode-intro {
|
||||
margin: 0;
|
||||
color: var(--interactive-editor-color);
|
||||
cursor: default;
|
||||
}
|
||||
.no-permission {
|
||||
margin: 0;
|
||||
width: auto;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
/* Button containers */
|
||||
&.edit-site-config-buttons,
|
||||
@@ -270,7 +294,7 @@ div.edit-mode-bottom-banner {
|
||||
color: var(--interactive-editor-color);
|
||||
border-color: var(--interactive-editor-color);
|
||||
background: var(--interactive-editor-background);
|
||||
&:hover {
|
||||
&:hover:not(.disallowed) {
|
||||
color: var(--interactive-editor-background);
|
||||
border-color: var(--interactive-editor-color);
|
||||
background: var(--interactive-editor-color);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
:resizable="true" width="50%" height="80%"
|
||||
classes="dashy-modal edit-page-info"
|
||||
>
|
||||
<div class="edit-page-info-inner">
|
||||
<div class="edit-page-info-inner" v-if="allowViewConfig">
|
||||
<h3>{{ $t('interactive-editor.menu.edit-page-info-btn') }}</h3>
|
||||
<FormSchema
|
||||
:schema="schema"
|
||||
@@ -19,6 +19,7 @@
|
||||
</Button>
|
||||
</FormSchema>
|
||||
</div>
|
||||
<AccessError v-else />
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
@@ -29,6 +30,7 @@ import StoreKeys from '@/utils/StoreMutations';
|
||||
import { modalNames } from '@/utils/defaults';
|
||||
import Button from '@/components/FormElements/Button';
|
||||
import SaveIcon from '@/assets/interface-icons/save-config.svg';
|
||||
import AccessError from '@/components/Configuration/AccessError';
|
||||
|
||||
export default {
|
||||
name: 'EditPageInfo',
|
||||
@@ -43,6 +45,7 @@ export default {
|
||||
FormSchema,
|
||||
Button,
|
||||
SaveIcon,
|
||||
AccessError,
|
||||
},
|
||||
mounted() {
|
||||
this.formData = this.pageInfo;
|
||||
@@ -51,6 +54,9 @@ export default {
|
||||
pageInfo() {
|
||||
return this.$store.getters.pageInfo;
|
||||
},
|
||||
allowViewConfig() {
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/* When form submitteed, update VueX store with new pageInfo, and close modal */
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
:resizable="true" width="50%" height="80%"
|
||||
classes="dashy-modal edit-section"
|
||||
>
|
||||
<div class="edit-section-inner">
|
||||
<div class="edit-section-inner" v-if="allowViewConfig">
|
||||
<h3>
|
||||
{{ $t(`interactive-editor.edit-section.${isAddNew ? 'add' : 'edit'}-section-title`) }}
|
||||
</h3>
|
||||
@@ -19,6 +19,7 @@
|
||||
:cancelClick="modalClosed"
|
||||
/>
|
||||
</div>
|
||||
<AccessError v-else />
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
@@ -28,6 +29,7 @@ import StoreKeys from '@/utils/StoreMutations';
|
||||
import DashySchema from '@/utils/ConfigSchema';
|
||||
import { modalNames } from '@/utils/defaults';
|
||||
import SaveCancelButtons from '@/components/InteractiveEditor/SaveCancelButtons';
|
||||
import AccessError from '@/components/Configuration/AccessError';
|
||||
|
||||
export default {
|
||||
name: 'EditSection',
|
||||
@@ -38,6 +40,7 @@ export default {
|
||||
components: {
|
||||
SaveCancelButtons,
|
||||
FormSchema,
|
||||
AccessError,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -71,6 +74,9 @@ export default {
|
||||
},
|
||||
};
|
||||
},
|
||||
allowViewConfig() {
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.sectionData = this.$store.getters.getSectionByIndex(this.sectionIndex);
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
:resizable="true"
|
||||
width="50%"
|
||||
height="80%"
|
||||
classes="dashy-modal edit-item"
|
||||
classes="dashy-modal export-modal"
|
||||
@closed="modalClosed"
|
||||
>
|
||||
<div class="export-config-inner">
|
||||
<div class="export-config-inner" v-if="allowViewConfig">
|
||||
<!-- Download and Copy to CLipboard Buttons -->
|
||||
<h3>{{ $t('interactive-editor.export.export-title') }}</h3>
|
||||
<div class="download-button-container">
|
||||
@@ -26,6 +26,7 @@
|
||||
<h3>{{ $t('interactive-editor.export.view-title') }}</h3>
|
||||
<tree-view :data="config" class="config-tree-view" />
|
||||
</div>
|
||||
<AccessError v-else />
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
@@ -34,6 +35,7 @@ import JsYaml from 'js-yaml';
|
||||
import Button from '@/components/FormElements/Button';
|
||||
import StoreKeys from '@/utils/StoreMutations';
|
||||
import { modalNames } from '@/utils/defaults';
|
||||
import AccessError from '@/components/Configuration/AccessError';
|
||||
import DownloadConfigIcon from '@/assets/interface-icons/config-download-file.svg';
|
||||
import CopyConfigIcon from '@/assets/interface-icons/interactive-editor-copy-clipboard.svg';
|
||||
import { InfoHandler, InfoKeys } from '@/utils/ErrorHandler';
|
||||
@@ -42,6 +44,7 @@ export default {
|
||||
name: 'ExportConfigMenu',
|
||||
components: {
|
||||
Button,
|
||||
AccessError,
|
||||
CopyConfigIcon,
|
||||
DownloadConfigIcon,
|
||||
},
|
||||
@@ -55,6 +58,9 @@ export default {
|
||||
config() {
|
||||
return this.$store.state.config;
|
||||
},
|
||||
allowViewConfig() {
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
convertJsonToYaml() {
|
||||
@@ -123,5 +129,8 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
.export-modal {
|
||||
background: var(--interactive-editor-background);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<modal
|
||||
:name="modalName" @closed="close"
|
||||
:resizable="true" width="40%" height="40%" classes="dashy-modal">
|
||||
<div class="move-menu-inner">
|
||||
<div class="move-menu-inner" v-if="allowViewConfig">
|
||||
<!-- Title and item ID -->
|
||||
<h3 class="move-title">Move or Copy Item</h3>
|
||||
<p class="item-id">Editing {{ itemId }}</p>
|
||||
@@ -30,6 +30,7 @@
|
||||
<!-- Save and cancel buttons -->
|
||||
<SaveCancelButtons :saveClick="save" :cancelClick="close" />
|
||||
</div>
|
||||
<AccessError v-else />
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
@@ -37,6 +38,7 @@
|
||||
import Select from '@/components/FormElements/Select';
|
||||
import Radio from '@/components/FormElements/Radio';
|
||||
import SaveCancelButtons from '@/components/InteractiveEditor/SaveCancelButtons';
|
||||
import AccessError from '@/components/Configuration/AccessError';
|
||||
import StoreKeys from '@/utils/StoreMutations';
|
||||
import { modalNames } from '@/utils/defaults';
|
||||
|
||||
@@ -45,6 +47,7 @@ export default {
|
||||
components: {
|
||||
Select,
|
||||
Radio,
|
||||
AccessError,
|
||||
SaveCancelButtons,
|
||||
},
|
||||
props: {
|
||||
@@ -83,6 +86,9 @@ export default {
|
||||
});
|
||||
return sectionName;
|
||||
},
|
||||
allowViewConfig() {
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.selectedSection = this.currentSection;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Edit Options -->
|
||||
<ul class="menu-section">
|
||||
<ul class="menu-section" v-bind:class="{ disabled: !isEditAllowed }">
|
||||
<li class="section-title">
|
||||
{{ $t('context-menus.item.options-section-title') }}
|
||||
</li>
|
||||
@@ -85,6 +85,9 @@ export default {
|
||||
isEditMode() {
|
||||
return this.$store.state.editMode;
|
||||
},
|
||||
isEditAllowed() {
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/* Called on item click, emits an event up to Item */
|
||||
@@ -93,13 +96,19 @@ export default {
|
||||
this.$emit('launchItem', target);
|
||||
},
|
||||
openSettings() {
|
||||
this.$emit('openItemSettings');
|
||||
if (this.isEditAllowed) {
|
||||
this.$emit('openItemSettings');
|
||||
}
|
||||
},
|
||||
openMoveMenu() {
|
||||
this.$emit('openMoveItemMenu');
|
||||
if (this.isEditAllowed) {
|
||||
this.$emit('openMoveItemMenu');
|
||||
}
|
||||
},
|
||||
openDeleteItem() {
|
||||
this.$emit('openDeleteItem');
|
||||
if (this.isEditAllowed) {
|
||||
this.$emit('openDeleteItem');
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -149,6 +158,13 @@ div.context-menu {
|
||||
path { fill: currentColor; }
|
||||
}
|
||||
}
|
||||
&.disabled li:not(.section-title) {
|
||||
cursor: not-allowed;
|
||||
opacity: var(--dimming-factor);
|
||||
&:hover {
|
||||
background: var(--context-menu-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -259,6 +259,10 @@ export default {
|
||||
},
|
||||
/* Navigate to the section's single-section view page */
|
||||
navigateToSection() {
|
||||
if (!this.title) {
|
||||
ErrorHandler('Cannot open section without a valid name');
|
||||
return;
|
||||
}
|
||||
const parse = (section) => section.replace(' ', '-').toLowerCase().trim();
|
||||
const sectionIdentifier = parse(this.title);
|
||||
router.push({ path: `/home/${sectionIdentifier}` });
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
v-tooltip="tooltip($t('settings.config-launcher-tooltip'))" />
|
||||
<IconInteractiveEditor @click="startInteractiveEditor()" tabindex="-2"
|
||||
v-tooltip="tooltip(enterEditModeTooltip)"
|
||||
:class="isEditMode ? 'disabled' : ''" />
|
||||
:class="(isEditMode || !isEditAllowed) ? 'disabled' : ''" />
|
||||
<IconViewMode @click="openChangeViewMenu()" tabindex="-2"
|
||||
v-tooltip="tooltip($t('alternate-views.alternate-view-heading'))" />
|
||||
</div>
|
||||
@@ -91,8 +91,12 @@ export default {
|
||||
isEditMode() {
|
||||
return this.$store.state.editMode;
|
||||
},
|
||||
isEditAllowed() {
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
/* Tooltip text for Edit Mode button, to change depending on it in edit mode */
|
||||
enterEditModeTooltip() {
|
||||
if (!this.isEditAllowed) return 'Config editor not available';
|
||||
return this.$t(
|
||||
`interactive-editor.menu.${this.isEditMode
|
||||
? 'edit-mode-subtitle' : 'start-editing-tooltip'}`,
|
||||
@@ -126,7 +130,7 @@ export default {
|
||||
this.viewSwitcherOpen = false;
|
||||
},
|
||||
startInteractiveEditor() {
|
||||
if (!this.isEditMode) {
|
||||
if (!this.isEditMode && this.isEditAllowed) {
|
||||
this.$store.commit(Keys.SET_EDIT_MODE, true);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -39,6 +39,7 @@ export default {
|
||||
filters: {},
|
||||
methods: {
|
||||
processData(alertData) {
|
||||
const round = (num) => ((num && typeof num === 'number') ? Math.round(num) : num);
|
||||
if (!alertData || alertData.length === 0) {
|
||||
this.noResults = true;
|
||||
} else {
|
||||
@@ -51,8 +52,9 @@ export default {
|
||||
lasted: alert[1] ? getTimeDifference(alert[0] * 1000, alert[1] * 1000) : 'Ongoing',
|
||||
severity: alert[2],
|
||||
category: alert[3],
|
||||
value: alert[5],
|
||||
minMax: `Min: ${alert[4]}%<br>Avg: ${alert[5]}%<br>Max: ${alert[6]}%`,
|
||||
value: round(alert[5]),
|
||||
minMax: `Min: ${round(alert[4])}%<br>Avg: `
|
||||
+ `${round(alert[5])}%<br>Max: ${round(alert[6])}%`,
|
||||
});
|
||||
});
|
||||
this.alerts = alerts;
|
||||
|
||||
83
src/components/Widgets/GlCpuTemp.vue
Normal file
83
src/components/Widgets/GlCpuTemp.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="glances-temp-wrapper" v-if="tempData">
|
||||
<div class="temp-row" v-for="sensor in tempData" :key="sensor.label">
|
||||
<p class="label">{{ sensor.label | formatLbl }}</p>
|
||||
<p :class="`temp range-${sensor.color}`">{{ sensor.value | formatVal }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import GlancesMixin from '@/mixins/GlancesMixin';
|
||||
import { capitalize, fahrenheitToCelsius } from '@/utils/MiscHelpers';
|
||||
|
||||
export default {
|
||||
mixins: [WidgetMixin, GlancesMixin],
|
||||
data() {
|
||||
return {
|
||||
tempData: null,
|
||||
noResults: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
endpoint() {
|
||||
return this.makeGlancesUrl('sensors');
|
||||
},
|
||||
},
|
||||
filters: {
|
||||
formatLbl(lbl) {
|
||||
return capitalize(lbl);
|
||||
},
|
||||
formatVal(val) {
|
||||
return `${Math.round(val)}°C`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getTempColor(temp) {
|
||||
if (temp <= 50) return 'green';
|
||||
if (temp > 50 && temp < 75) return 'yellow';
|
||||
if (temp >= 75) return 'red';
|
||||
return 'grey';
|
||||
},
|
||||
processData(sensorData) {
|
||||
const results = [];
|
||||
sensorData.forEach((sensor) => {
|
||||
const tempC = sensor.unit === 'F' ? fahrenheitToCelsius(sensor.value) : sensor.value;
|
||||
results.push({
|
||||
label: sensor.label,
|
||||
value: tempC,
|
||||
color: this.getTempColor(tempC),
|
||||
});
|
||||
});
|
||||
this.tempData = results;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.glances-temp-wrapper {
|
||||
.temp-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
p.label {
|
||||
margin: 0.5rem 0;
|
||||
color: var(--widget-text-color);
|
||||
}
|
||||
p.temp {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
&.range-green { color: var(--success); }
|
||||
&.range-yellow { color: var(--warning); }
|
||||
&.range-red { color: var(--danger); }
|
||||
&.range-grey { color: var(--medium-grey); }
|
||||
}
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px dashed var(--widget-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
28
src/components/Widgets/ImageWidget.vue
Normal file
28
src/components/Widgets/ImageWidget.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="image-widget">
|
||||
<img :src="imagePath" class="embedded-image" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
|
||||
export default {
|
||||
mixins: [WidgetMixin],
|
||||
computed: {
|
||||
imagePath() {
|
||||
if (!this.options.imagePath) this.error('You must specify an imagePath');
|
||||
return this.options.imagePath;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.image-widget {
|
||||
img.embedded-image {
|
||||
max-width: 100%;
|
||||
margin: 0.2rem auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -209,6 +209,13 @@
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<GlCpuTemp
|
||||
v-else-if="widgetType === 'gl-cpu-temp'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<HealthChecks
|
||||
v-else-if="widgetType === 'health-checks'"
|
||||
:options="widgetOptions"
|
||||
@@ -223,6 +230,13 @@
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<ImageWidget
|
||||
v-else-if="widgetType === 'image'"
|
||||
:options="widgetOptions"
|
||||
@loading="setLoaderState"
|
||||
@error="handleError"
|
||||
:ref="widgetRef"
|
||||
/>
|
||||
<Jokes
|
||||
v-else-if="widgetType === 'joke'"
|
||||
:options="widgetOptions"
|
||||
@@ -413,8 +427,10 @@ export default {
|
||||
GlNetworkInterfaces: () => import('@/components/Widgets/GlNetworkInterfaces.vue'),
|
||||
GlNetworkTraffic: () => import('@/components/Widgets/GlNetworkTraffic.vue'),
|
||||
GlSystemLoad: () => import('@/components/Widgets/GlSystemLoad.vue'),
|
||||
GlCpuTemp: () => import('@/components/Widgets/GlCpuTemp.vue'),
|
||||
HealthChecks: () => import('@/components/Widgets/HealthChecks.vue'),
|
||||
IframeWidget: () => import('@/components/Widgets/IframeWidget.vue'),
|
||||
ImageWidget: () => import('@/components/Widgets/ImageWidget.vue'),
|
||||
Jokes: () => import('@/components/Widgets/Jokes.vue'),
|
||||
NdCpuHistory: () => import('@/components/Widgets/NdCpuHistory.vue'),
|
||||
NdLoadHistory: () => import('@/components/Widgets/NdLoadHistory.vue'),
|
||||
@@ -446,6 +462,9 @@ export default {
|
||||
errorMsg: null,
|
||||
}),
|
||||
computed: {
|
||||
appConfig() {
|
||||
return this.$store.getters.appConfig;
|
||||
},
|
||||
/* Returns the widget type, shows error if not specified */
|
||||
widgetType() {
|
||||
if (!this.widget.type) {
|
||||
@@ -457,7 +476,7 @@ export default {
|
||||
/* Returns users specified widget options, or empty object */
|
||||
widgetOptions() {
|
||||
const options = this.widget.options || {};
|
||||
const useProxy = !!this.widget.useProxy;
|
||||
const useProxy = this.appConfig.widgetsAlwaysUseProxy || !!this.widget.useProxy;
|
||||
const updateInterval = this.widget.updateInterval !== undefined
|
||||
? this.widget.updateInterval : null;
|
||||
return { useProxy, updateInterval, ...options };
|
||||
|
||||
Reference in New Issue
Block a user