feat: notifications (#738)
Co-authored-by: th33xitus <th33xitus@googlemail.com>
This commit is contained in:
parent
e2caa99f0b
commit
d830493acc
@ -41,7 +41,7 @@
|
||||
<the-connecting-dialog v-else></the-connecting-dialog>
|
||||
<the-update-dialog></the-update-dialog>
|
||||
<the-editor></the-editor>
|
||||
<the-timelapse-rendering-snackbar>-</the-timelapse-rendering-snackbar>
|
||||
<the-timelapse-rendering-snackbar></the-timelapse-rendering-snackbar>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
|
@ -1,96 +0,0 @@
|
||||
<style scoped></style>
|
||||
|
||||
<template>
|
||||
<v-menu
|
||||
v-if="throttledStateFlags.length"
|
||||
v-model="showMenu"
|
||||
bottom
|
||||
:offset-y="true"
|
||||
:close-on-content-click="false">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn :color="currentFlags.length ? 'error' : 'warning'" icon tile class="mr-3" v-bind="attrs" v-on="on">
|
||||
<v-icon>{{ mdiRaspberryPi }}</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<v-list min-width="300" max-width="600">
|
||||
<template v-if="currentFlags.length">
|
||||
<v-subheader class="" style="height: auto">
|
||||
{{ $t('App.ThrottledStates.HeadlineCurrentFlags') }}
|
||||
</v-subheader>
|
||||
<v-list-item v-for="flag in currentFlags" :key="flag" two-line>
|
||||
<v-list-item-content class="py-0">
|
||||
<v-list-item-title>{{ $t(`App.ThrottledStates.Title${convertName(flag)}`) }}</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-wrap">
|
||||
{{ $t(`App.ThrottledStates.Description${convertName(flag)}`) }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template v-if="previouslyFlags.length">
|
||||
<v-divider v-if="currentFlags.length" class="my-2"></v-divider>
|
||||
<v-subheader class="" style="height: auto">
|
||||
{{ $t('App.ThrottledStates.HeadlinePreviouslyFlags') }}
|
||||
</v-subheader>
|
||||
<v-list-item v-for="flag in previouslyFlags" :key="flag" two-line>
|
||||
<v-list-item-content class="py-0">
|
||||
<v-list-item-title>{{ $t(`App.ThrottledStates.Title${convertName(flag)}`) }}</v-list-item-title>
|
||||
<v-list-item-subtitle class="text-wrap">
|
||||
{{ $t(`App.ThrottledStates.Description${convertName(flag)}`) }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Mixins } from 'vue-property-decorator'
|
||||
import BaseMixin from './mixins/base'
|
||||
|
||||
import { mdiRaspberryPi } from '@mdi/js'
|
||||
|
||||
@Component
|
||||
export default class TheThrottledStates extends Mixins(BaseMixin) {
|
||||
mdiRaspberryPi = mdiRaspberryPi
|
||||
|
||||
private showMenu = false
|
||||
|
||||
get throttledStateFlags() {
|
||||
return this.$store.state.server.throttled_state.flags.filter((flag: string) => {
|
||||
return flag !== '?'
|
||||
})
|
||||
|
||||
/*return [
|
||||
'Under-Voltage Detected',
|
||||
'Frequency Capped',
|
||||
'Currently Throttled',
|
||||
'Temperature Limit Active',
|
||||
'Previously Under-Volted',
|
||||
'Previously Frequency Capped',
|
||||
'Previously Throttled',
|
||||
'Previously Temperature Limited'
|
||||
]*/
|
||||
}
|
||||
|
||||
get currentFlags() {
|
||||
return this.throttledStateFlags.filter((flag: string) => {
|
||||
return !flag.startsWith('Previously ')
|
||||
})
|
||||
}
|
||||
|
||||
get previouslyFlags() {
|
||||
return this.throttledStateFlags.filter((flag: string) => {
|
||||
return flag.startsWith('Previously ')
|
||||
})
|
||||
}
|
||||
|
||||
convertName(flag: string): string {
|
||||
flag = flag.replace(/ /g, '').replace(/-/g, '')
|
||||
flag = flag.charAt(0).toUpperCase() + flag.slice(1)
|
||||
|
||||
return flag
|
||||
}
|
||||
}
|
||||
</script>
|
@ -47,7 +47,6 @@
|
||||
<v-toolbar-title class="text-no-wrap ml-0 pl-2 mr-2">{{ printerName }}</v-toolbar-title>
|
||||
<printer-selector v-if="countPrinters"></printer-selector>
|
||||
<v-spacer></v-spacer>
|
||||
<the-throttled-states></the-throttled-states>
|
||||
<input
|
||||
ref="fileUploadAndStart"
|
||||
type="file"
|
||||
@ -95,6 +94,7 @@
|
||||
<v-icon class="mr-md-2">{{ mdiAlertCircleOutline }}</v-icon>
|
||||
<span class="d-none d-md-inline">{{ $t('App.TopBar.EmergencyStop') }}</span>
|
||||
</v-btn>
|
||||
<the-notification-menu></the-notification-menu>
|
||||
<the-settings-menu></the-settings-menu>
|
||||
<the-top-corner-menu></the-top-corner-menu>
|
||||
</v-app-bar>
|
||||
@ -142,10 +142,10 @@ import axios from 'axios'
|
||||
import { formatFilesize } from '@/plugins/helpers'
|
||||
import TheTopCornerMenu from '@/components/TheTopCornerMenu.vue'
|
||||
import TheSettingsMenu from '@/components/TheSettingsMenu.vue'
|
||||
import TheThrottledStates from '@/components/TheThrottledStates.vue'
|
||||
import Panel from '@/components/ui/Panel.vue'
|
||||
import PrinterSelector from '@/components/ui/PrinterSelector.vue'
|
||||
import MainsailLogo from '@/components/ui/MainsailLogo.vue'
|
||||
import TheNotificationMenu from '@/components/notifications/TheNotificationMenu.vue'
|
||||
import { topbarHeight } from '@/store/variables'
|
||||
import { mdiAlertCircleOutline, mdiContentSave, mdiFileUpload, mdiClose, mdiCloseThick } from '@mdi/js'
|
||||
|
||||
@ -165,11 +165,11 @@ type uploadSnackbar = {
|
||||
@Component({
|
||||
components: {
|
||||
Panel,
|
||||
TheThrottledStates,
|
||||
TheSettingsMenu,
|
||||
TheTopCornerMenu,
|
||||
PrinterSelector,
|
||||
MainsailLogo,
|
||||
TheNotificationMenu,
|
||||
},
|
||||
})
|
||||
export default class TheTopbar extends Mixins(BaseMixin) {
|
||||
|
@ -40,7 +40,8 @@ export default class BaseMixin extends Vue {
|
||||
}
|
||||
|
||||
get printer_state(): string {
|
||||
const printer_state = this.$store.state.printer.print_stats?.state ?? ''
|
||||
const printer_state =
|
||||
this.$store.state.printer.print_stats?.state ?? this.$store.state.printer.idle_timeout?.state ?? ''
|
||||
const timelapse_pause = this.$store.state.printer['gcode_macro TIMELAPSE_TAKE_FRAME']?.is_paused ?? false
|
||||
return printer_state === 'paused' && timelapse_pause ? 'printing' : printer_state
|
||||
}
|
||||
|
156
src/components/notifications/NotificationMenuEntry.vue
Normal file
156
src/components/notifications/NotificationMenuEntry.vue
Normal file
@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<v-alert text :color="alertColor" border="left">
|
||||
<v-row align="start">
|
||||
<v-col class="grow">
|
||||
<div class="notification-menu-entry__headline mb-1 text-subtitle-1">
|
||||
<template v-if="'url' in entry">
|
||||
<a :class="`text-decoration-none ${alertColor}--text`" :href="entry.url" target="_blank">
|
||||
<v-icon small :class="`${alertColor}--text pb-1`">
|
||||
{{ mdiLinkVariant }}
|
||||
</v-icon>
|
||||
{{ entry.title }}
|
||||
</a>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span :class="`${alertColor}--text`">{{ entry.title }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<p class="text-body-2 mb-0 text--disabled font-weight-light" v-html="formatedText"></p>
|
||||
</v-col>
|
||||
<v-col
|
||||
v-if="entry.priority !== 'critical'"
|
||||
class="shrink pl-0 pb-0 pt-1 pr-2 d-flex flex-column align-self-stretch justify-space-between">
|
||||
<v-btn v-if="entryType === 'announcement'" icon plain :color="alertColor" class="mb-2" @click="close">
|
||||
<v-icon>{{ mdiClose }}</v-icon>
|
||||
</v-btn>
|
||||
<v-btn v-else icon plain :color="alertColor" class="mb-2" @click="dismiss('reboot', null)">
|
||||
<v-icon>{{ mdiClose }}</v-icon>
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon plain retain-focus-on-click :color="alertColor" class="pb-1" @click="expand = !expand">
|
||||
<v-icon>{{ mdiBellOffOutline }}</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row v-if="entry.priority !== 'critical'">
|
||||
<v-expand-transition>
|
||||
<div v-show="expand" class="pt-1" style="width: 100%">
|
||||
<v-divider class="pb-1 ml-2"></v-divider>
|
||||
<div class="text-right py-1" style="font-size: 0.875rem">
|
||||
<span class="text--disabled text-caption font-weight-light">
|
||||
{{ $t('App.Notifications.Remind') }}
|
||||
</span>
|
||||
<template v-if="entryType === 'announcement'">
|
||||
<v-btn
|
||||
:color="alertColor"
|
||||
x-small
|
||||
plain
|
||||
text
|
||||
outlined
|
||||
class="mx-1"
|
||||
@click="dismiss('time', 60 * 60)">
|
||||
1H
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:color="alertColor"
|
||||
x-small
|
||||
plain
|
||||
text
|
||||
outlined
|
||||
class="mx-1"
|
||||
@click="dismiss('time', 60 * 60 * 24)">
|
||||
1D
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:color="alertColor"
|
||||
x-small
|
||||
plain
|
||||
text
|
||||
outlined
|
||||
class="mx-1"
|
||||
@click="dismiss('time', 60 * 60 * 24 * 7)">
|
||||
7D
|
||||
</v-btn>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-btn
|
||||
:color="alertColor"
|
||||
x-small
|
||||
plain
|
||||
text
|
||||
outlined
|
||||
class="mx-1"
|
||||
@click="dismiss('reboot', null)">
|
||||
{{ $t('App.Notifications.NextReboot') }}
|
||||
</v-btn>
|
||||
<v-btn :color="alertColor" x-small plain text outlined class="mx-1" @click="close">
|
||||
{{ $t('App.Notifications.Never') }}
|
||||
</v-btn>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
</v-row>
|
||||
</v-alert>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import { Component, Mixins, Prop, Watch } from 'vue-property-decorator'
|
||||
import { mdiClose, mdiLinkVariant, mdiBellOffOutline } from '@mdi/js'
|
||||
import { GuiNotificationStateEntry } from '@/store/gui/notifications/types'
|
||||
|
||||
@Component({
|
||||
components: {},
|
||||
})
|
||||
export default class NotificationMenuEntry extends Mixins(BaseMixin) {
|
||||
mdiClose = mdiClose
|
||||
mdiLinkVariant = mdiLinkVariant
|
||||
mdiBellOffOutline = mdiBellOffOutline
|
||||
|
||||
private expand = false
|
||||
|
||||
@Prop({ required: true })
|
||||
declare readonly entry: GuiNotificationStateEntry
|
||||
|
||||
@Prop({ default: true })
|
||||
declare readonly parentState: boolean
|
||||
|
||||
get formatedText() {
|
||||
return this.entry.description.replace(/\[([^\]]+)\]\(([^)]+)\)/, '<a href="$2" target="_blank">$1</a>')
|
||||
}
|
||||
|
||||
get alertColor() {
|
||||
if (this.entry.priority === 'critical') return 'error'
|
||||
if (this.entry.priority === 'high') return 'warning'
|
||||
|
||||
return 'info'
|
||||
}
|
||||
|
||||
get entryType() {
|
||||
const posFirstSlash = this.entry.id.indexOf('/')
|
||||
if (posFirstSlash === -1) return ''
|
||||
|
||||
return this.entry.id.slice(0, posFirstSlash)
|
||||
}
|
||||
|
||||
close() {
|
||||
this.$store.dispatch('gui/notifications/close', { id: this.entry.id })
|
||||
}
|
||||
|
||||
dismiss(type: 'time' | 'reboot', time: number | null) {
|
||||
this.$store.dispatch('gui/notifications/dismiss', { id: this.entry.id, type, time })
|
||||
}
|
||||
|
||||
@Watch('parentState')
|
||||
parentStateUpdate(newVal: boolean) {
|
||||
if (!newVal) this.expand = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.notification-menu-entry__headline {
|
||||
line-height: 1.2;
|
||||
}
|
||||
</style>
|
112
src/components/notifications/TheNotificationMenu.vue
Normal file
112
src/components/notifications/TheNotificationMenu.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<v-menu
|
||||
v-model="boolMenu"
|
||||
bottom
|
||||
:left="!isMobile"
|
||||
offset-y
|
||||
:close-on-click="true"
|
||||
:close-on-content-click="false"
|
||||
origin="center center"
|
||||
transition="slide-y-transition"
|
||||
:min-width="isMobile ? '100%' : null">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn icon tile class="minwidth-0" v-bind="attrs" v-on="on">
|
||||
<v-badge
|
||||
:content="notifications.length <= 9 ? notifications.length : '9+'"
|
||||
:value="notifications.length > 0"
|
||||
:color="colorBadge"
|
||||
overlap>
|
||||
<v-icon>{{ attrs['aria-expanded'] === 'false' ? mdiBellOutline : mdiBell }}</v-icon>
|
||||
</v-badge>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card flat :min-width="300" :max-width="isMobile ? null : 400">
|
||||
<template v-if="notifications.length">
|
||||
<overlay-scrollbars class="announcement-menu__scrollbar">
|
||||
<v-card-text>
|
||||
<template v-for="(entry, index) in notifications">
|
||||
<notification-menu-entry
|
||||
:key="entry.id"
|
||||
:entry="entry"
|
||||
:class="index < notifications.length - 1 ? '' : 'mb-0'"
|
||||
:parent-state="boolMenu" />
|
||||
</template>
|
||||
</v-card-text>
|
||||
</overlay-scrollbars>
|
||||
<template v-if="notifications.length > 1">
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn text color="primary" class="mr-2" @click="dismissAll">
|
||||
<v-icon left>{{ mdiCloseBoxMultipleOutline }}</v-icon>
|
||||
{{ $t('App.Notifications.DismissAll') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<v-card-text class="text-center">
|
||||
<span class="text-disabled">{{ $t('App.Notifications.NoNotification') }}</span>
|
||||
</v-card-text>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import { Component, Mixins } from 'vue-property-decorator'
|
||||
import NotificationMenuEntry from '@/components/notifications/NotificationMenuEntry.vue'
|
||||
import { mdiBell, mdiBellOutline, mdiCloseBoxMultipleOutline } from '@mdi/js'
|
||||
import { GuiNotificationStateEntry } from '@/store/gui/notifications/types'
|
||||
|
||||
@Component({
|
||||
components: { NotificationMenuEntry },
|
||||
})
|
||||
export default class TheNotificationMenu extends Mixins(BaseMixin) {
|
||||
mdiBell = mdiBell
|
||||
mdiBellOutline = mdiBellOutline
|
||||
mdiCloseBoxMultipleOutline = mdiCloseBoxMultipleOutline
|
||||
|
||||
private boolMenu = false
|
||||
|
||||
get notifications() {
|
||||
return this.$store.getters['gui/notifications/getNotifications'] ?? []
|
||||
}
|
||||
|
||||
get existsCriticalAnnouncements() {
|
||||
return this.notifications.filter((entry: GuiNotificationStateEntry) => entry.priority === 'critical').length > 0
|
||||
}
|
||||
|
||||
get existsHighAnnouncements() {
|
||||
return this.notifications.filter((entry: GuiNotificationStateEntry) => entry.priority === 'high').length > 0
|
||||
}
|
||||
|
||||
get countNormalAnnouncements() {
|
||||
return this.notifications.filter((entry: GuiNotificationStateEntry) => entry.priority === 'normal').length
|
||||
}
|
||||
|
||||
get colorBadge() {
|
||||
if (this.existsCriticalAnnouncements) return 'error'
|
||||
if (this.existsHighAnnouncements) return 'warning'
|
||||
|
||||
return 'primary'
|
||||
}
|
||||
|
||||
dismissAll() {
|
||||
this.notifications.forEach(async (entry: GuiNotificationStateEntry) => {
|
||||
if (entry.id.startsWith('announcement')) {
|
||||
await this.$store.dispatch('gui/notifications/close', { id: entry.id })
|
||||
} else {
|
||||
await this.$store.dispatch('gui/notifications/dismiss', { id: entry.id, type: 'reboot', time: null })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.announcement-menu__scrollbar {
|
||||
max-height: 500px;
|
||||
}
|
||||
</style>
|
@ -1,44 +0,0 @@
|
||||
<template>
|
||||
<panel
|
||||
v-if="socketIsConnected && dependencies.length"
|
||||
:icon="mdiAlertCircle"
|
||||
:title="$tc('Panels.DependenciesPanel.Dependency', dependencies.length) + ' (' + dependencies.length + ')'"
|
||||
:collapsible="true"
|
||||
card-class="dependencies-panel"
|
||||
toolbar-color="orange darken-2">
|
||||
<v-card-text v-for="(dependency, index) in dependencies" :key="index" :class="index > 0 ? 'py-0' : 'pt-3 pb-0'">
|
||||
<v-divider v-if="index" class="my-2"></v-divider>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<p class="mb-0 orange--text">
|
||||
{{
|
||||
$t('Panels.DependenciesPanel.DependencyDescription', {
|
||||
name: dependency.serviceName,
|
||||
installedVersion: dependency.installedVersion,
|
||||
neededVersion: dependency.neededVersion,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions></v-card-actions>
|
||||
</panel>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Component from 'vue-class-component'
|
||||
import { Mixins } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import Panel from '@/components/ui/Panel.vue'
|
||||
import { mdiAlertCircle } from '@mdi/js'
|
||||
@Component({
|
||||
components: { Panel },
|
||||
})
|
||||
export default class DependenciesPanel extends Mixins(BaseMixin) {
|
||||
mdiAlertCircle = mdiAlertCircle
|
||||
get dependencies() {
|
||||
return this.$store.getters['getDependencies'] ?? []
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,81 +0,0 @@
|
||||
<template>
|
||||
<panel
|
||||
v-if="klipperReadyForGui && warnings.length"
|
||||
:icon="mdiAlertCircle"
|
||||
:title="$t('Panels.KlipperWarningsPanel.KlipperWarnings') + ' (' + warnings.length + ')'"
|
||||
:collapsible="true"
|
||||
card-class="klipper-warnings-panel"
|
||||
toolbar-color="orange darken-2">
|
||||
<v-card-text v-for="(warning, index) in warnings" :key="index" :class="index > 0 ? 'py-0' : 'pt-3 pb-0'">
|
||||
<v-divider v-if="index" class="my-2"></v-divider>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<p v-if="warning.type === 'deprecated_option'" class="orange--text mb-0">
|
||||
{{
|
||||
$t('Panels.KlipperWarningsPanel.DeprecatedOption', {
|
||||
section: warning.section,
|
||||
option: warning.option,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<p v-else-if="warning.type === 'deprecated_value'" class="orange--text mb-0">
|
||||
{{
|
||||
$t('Panels.KlipperWarningsPanel.DeprecatedValue', {
|
||||
section: warning.section,
|
||||
option: warning.option,
|
||||
value: warning.value,
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
<p v-else class="orange--text mb-0">{{ warning.message }}</p>
|
||||
</v-col>
|
||||
<v-col class="col-auto d-flex align-center">
|
||||
<a :href="getDocsLink(warning)" target="_blank" class="text-decoration-none">
|
||||
<v-icon>{{ mdiInformation }}</v-icon>
|
||||
</a>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-divider class="mt-3"></v-divider>
|
||||
<v-card-actions class="justify-start">
|
||||
<v-btn small :href="apiUrl + '/server/files/klipper.log'" target="_blank" class="ml-2 primary--text">
|
||||
<v-icon class="mr-2" small>{{ mdiDownload }}</v-icon>
|
||||
{{ $t('Panels.KlipperWarningsPanel.DownloadLog') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</panel>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Component from 'vue-class-component'
|
||||
import BaseMixin from '../mixins/base'
|
||||
import { Mixins } from 'vue-property-decorator'
|
||||
import { caseInsensitiveSort } from '@/plugins/helpers'
|
||||
import Panel from '@/components/ui/Panel.vue'
|
||||
import { mdiAlertCircle, mdiDownload, mdiInformation } from '@mdi/js'
|
||||
@Component({
|
||||
components: { Panel },
|
||||
})
|
||||
export default class KlipperWarningsPanelPanel extends Mixins(BaseMixin) {
|
||||
mdiAlertCircle = mdiAlertCircle
|
||||
mdiInformation = mdiInformation
|
||||
mdiDownload = mdiDownload
|
||||
|
||||
get warnings() {
|
||||
let warnings = this.$store.state.printer.configfile?.warnings ?? []
|
||||
|
||||
return caseInsensitiveSort(warnings, 'option')
|
||||
}
|
||||
|
||||
getDocsLink(warning: { type: string; option: string; value: string }) {
|
||||
let url = 'https://docs.mainsail.xyz/faq/klipper_warnings/' + warning.type
|
||||
|
||||
if (warning.type === 'deprecated_option' && warning.option.startsWith('default_parameter'))
|
||||
url += '#default_parameter'
|
||||
else if (warning.type === 'deprecated_option') url += '#' + warning.option
|
||||
else if (warning.type === 'deprecated_value') url += '#' + warning.value
|
||||
|
||||
return url
|
||||
}
|
||||
}
|
||||
</script>
|
@ -30,7 +30,7 @@
|
||||
<v-divider class="mb-2"></v-divider>
|
||||
</template>
|
||||
<v-card-actions class="justify-center pb-3">
|
||||
<v-btn small href="https://docs.mainsail.xyz/necessary-configuration" target="_blank">
|
||||
<v-btn small href="https://docs.mainsail.xyz/configuration" target="_blank">
|
||||
<v-icon small class="mr-1">{{ mdiInformation }}</v-icon>
|
||||
{{ $t('Panels.MinSettingsPanel.MoreInformation') }}
|
||||
</v-btn>
|
||||
|
@ -1,58 +0,0 @@
|
||||
<template>
|
||||
<panel
|
||||
v-if="failedComponents.length || warnings.length"
|
||||
:icon="mdiAlertCircle"
|
||||
:title="$t('Panels.MoonrakerStatePanel.MoonrakerWarnings')"
|
||||
:collapsible="true"
|
||||
card-class="moonraker-state-panel"
|
||||
toolbar-color="orange darken-2">
|
||||
<v-card-text v-if="failedComponents.length">
|
||||
<v-row>
|
||||
<v-col>
|
||||
<p class="orange--text">{{ $t('Panels.MoonrakerStatePanel.MoonrakerErrorInfo') }}</p>
|
||||
<p class="mb-2 orange--text">{{ $t('Panels.MoonrakerStatePanel.FollowingPluginHasAnError') }}</p>
|
||||
<ul class="mt-0 pt-0">
|
||||
<li v-for="component in failedComponents" :key="component" class="orange--text">
|
||||
<code>{{ component }}</code>
|
||||
</li>
|
||||
</ul>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-divider v-if="failedComponents.length || warnings.length"></v-divider>
|
||||
<v-card-text v-for="(warning, index) in warnings" :key="warning" :class="index > 0 ? 'py-0' : 'pt-3 pb-0'">
|
||||
<v-divider v-if="index" class="my-2"></v-divider>
|
||||
<p class="orange--text mb-0">{{ warning }}</p>
|
||||
</v-card-text>
|
||||
<v-divider class="mt-3"></v-divider>
|
||||
<v-card-actions class="justify-start">
|
||||
<v-btn small :href="apiUrl + '/server/files/moonraker.log'" target="_blank" class="ml-2 primary--text">
|
||||
<v-icon class="mr-2" small>{{ mdiDownload }}</v-icon>
|
||||
{{ $t('Panels.MoonrakerStatePanel.DownloadLog') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</panel>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Component from 'vue-class-component'
|
||||
import BaseMixin from '../mixins/base'
|
||||
import { Mixins } from 'vue-property-decorator'
|
||||
import Panel from '@/components/ui/Panel.vue'
|
||||
import { mdiAlertCircle, mdiDownload } from '@mdi/js'
|
||||
@Component({
|
||||
components: { Panel },
|
||||
})
|
||||
export default class MoonrakerStatePanel extends Mixins(BaseMixin) {
|
||||
mdiDownload = mdiDownload
|
||||
mdiAlertCircle = mdiAlertCircle
|
||||
|
||||
get failedComponents() {
|
||||
return this.$store.state.server.failed_components ?? []
|
||||
}
|
||||
|
||||
get warnings() {
|
||||
return this.$store.state.server.warnings ?? []
|
||||
}
|
||||
}
|
||||
</script>
|
@ -6,11 +6,8 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<dependencies-panel></dependencies-panel>
|
||||
<min-settings-panel></min-settings-panel>
|
||||
<moonraker-state-panel></moonraker-state-panel>
|
||||
<klippy-state-panel></klippy-state-panel>
|
||||
<klipper-warnings-panel></klipper-warnings-panel>
|
||||
<panel
|
||||
v-if="klipperState === 'ready'"
|
||||
:icon="mdiInformation"
|
||||
@ -368,11 +365,8 @@ import Component from 'vue-class-component'
|
||||
import { Mixins, Watch } from 'vue-property-decorator'
|
||||
import { thumbnailSmallMin, thumbnailSmallMax, thumbnailBigMin } from '@/store/variables'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import DependenciesPanel from '@/components/panels/DependenciesPanel.vue'
|
||||
import MinSettingsPanel from '@/components/panels/MinSettingsPanel.vue'
|
||||
import MoonrakerStatePanel from '@/components/panels/MoonrakerStatePanel.vue'
|
||||
import KlippyStatePanel from '@/components/panels/KlippyStatePanel.vue'
|
||||
import KlipperWarningsPanel from '@/components/panels/KlipperWarningsPanel.vue'
|
||||
import StatusPanelExcludeObject from '@/components/panels/StatusPanelExcludeObject.vue'
|
||||
import Panel from '@/components/ui/Panel.vue'
|
||||
import {
|
||||
@ -391,11 +385,8 @@ import {
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
DependenciesPanel,
|
||||
KlipperWarningsPanel,
|
||||
KlippyStatePanel,
|
||||
MinSettingsPanel,
|
||||
MoonrakerStatePanel,
|
||||
Panel,
|
||||
StatusPanelExcludeObject,
|
||||
},
|
||||
|
@ -1,5 +1,11 @@
|
||||
{
|
||||
"App": {
|
||||
"Notifications": {
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "Funktionen \"{option}\" i sektion \"{section}\" er forældet.",
|
||||
"DeprecatedValue": "Værdien \"{value}\" i muligheden \"{option}\" i sektion \"{section}\" er forældet."
|
||||
}
|
||||
},
|
||||
"NumberInput": {
|
||||
"GreaterOrEqualError": "Skal være større eller lig med {min}!",
|
||||
"MustBeBetweenError": "Skal være mellem {min} og {max}!",
|
||||
@ -15,8 +21,6 @@
|
||||
"DescriptionPreviouslyUnderVolted": "rPI strømforsyning faldt til under 4,65V mindst en gang siden sidste opstart.",
|
||||
"DescriptionTemperatureLimitActive": "rPi uC (3A+/3B+ only) temperatur er i øjeblikket over advarselsgrænsen (standard 60°C).",
|
||||
"DescriptionUnderVoltageDetected": "rPI strømforsyning i øjeblikket under 4,65V",
|
||||
"HeadlineCurrentFlags": "\"Lige nu\" varsel",
|
||||
"HeadlinePreviouslyFlags": "\"Tidligere\" varsel",
|
||||
"TitleCurrentlyThrottled": "Begrænset i øjeblikket",
|
||||
"TitleFrequencyCapped": "Maks. frekvens formindsket",
|
||||
"TitlePreviouslyFrequencyCapped": "Maks. frekvens formindsket tidligere",
|
||||
@ -438,12 +442,6 @@
|
||||
"SwitchToPrinter": "Skift til printer",
|
||||
"WebcamOff": "Sluk"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "Funktionen \"{option}\" i sektion \"{section}\" er forældet.",
|
||||
"DeprecatedValue": "Værdien \"{value}\" i muligheden \"{option}\" i sektion \"{section}\" er forældet.",
|
||||
"DownloadLog": "Download log",
|
||||
"KlipperWarnings": "Klipper advarsler"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "Genstart alt",
|
||||
"KlipperCheck": "Check at Klipper service kører og at en UDS (Unix Domain Socket) er konfigureret.",
|
||||
@ -499,12 +497,6 @@
|
||||
"Empty": "Tom"
|
||||
}
|
||||
},
|
||||
"MoonrakerStatePanel": {
|
||||
"DownloadLog": "Download log",
|
||||
"FollowingPluginHasAnError": "Følgende plugin har en fejl:",
|
||||
"MoonrakerErrorInfo": "En fejl blev fundet under indlæsning af moonraker komponenter. Tjek logfilen og ret fejlen.",
|
||||
"MoonrakerWarnings": "Moonraker advarsler"
|
||||
},
|
||||
"PowerControlPanel": {
|
||||
"Error": "Fejl",
|
||||
"Off": "Sluk",
|
||||
|
@ -1,5 +1,28 @@
|
||||
{
|
||||
"App": {
|
||||
"Notifications": {
|
||||
"DependencyName": "Abhängigkeit: {name}",
|
||||
"DependencyDescription": "Die momentane {name} Version unterstützt nicht alle Funktionen von Mainsail. Aktualisiere {name} mindestens auf Version {neededVersion}.",
|
||||
"DismissAll": "Dismiss all",
|
||||
"MoonrakerWarnings": {
|
||||
"MoonrakerComponent": "Moonraker: {component}",
|
||||
"MoonrakerFailedComponentDescription": "Beim Laden der Moonraker-Komponenten wurde ein Fehler festgestellt. Bitte prüfe die Logdatei und behebe das Problem.",
|
||||
"MoonrakerWarning": "Moonraker Warnung",
|
||||
"UnparsedConfigOption": "Nicht erkannte Config-Option '{option}: {value}' in Abschnitt [{section}] entdeckt. Dies kann eine Option sein, die nicht mehr verfügbar ist, oder das Ergebnis eines Moduls sein, das nicht geladen werden konnte. In Zukunft wird dies zu einem Startfehler führen.",
|
||||
"UnparsedConfigSection": "Nicht erkannter Config-Abschnitt [{section}] gefunden. Dies kann das Ergebnis einer Komponente sein, die nicht geladen werden konnte. In Zukunft wird dies zu einem Startfehler führen."
|
||||
},
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "Option '{option}' im Abschnitt '{section}' ist veraltet und wird in einem zukünftigen Release entfernt.",
|
||||
"DeprecatedOptionHeadline": "Veralterte Klipper Option",
|
||||
"DeprecatedValue": "Wert '{value}' in Option '{option}' im Abschnitt '{section}' ist veraltet und wird in einem zukünftigen Release entfernt.",
|
||||
"DeprecatedValueHeadline": "Veralteter Klipper Wert",
|
||||
"KlipperWarning": "Klipper Warnung"
|
||||
},
|
||||
"Never": "nie",
|
||||
"NextReboot": "nächsten Reboot",
|
||||
"NoNotification": "Keine Benachrichtigung vorhanden",
|
||||
"Remind": "Errinnere:"
|
||||
},
|
||||
"NumberInput": {
|
||||
"GreaterOrEqualError": "Muss größer oder gleich {min} sein!",
|
||||
"MustBeBetweenError": "Muss zwischen {min} und {max} liegen!",
|
||||
@ -15,8 +38,6 @@
|
||||
"DescriptionPreviouslyUnderVolted": "rPI-Versorgungsspannung ist seit dem letzten Einschalten mindestens einmal unter 4,65 V gefallen.",
|
||||
"DescriptionTemperatureLimitActive": "Die Temperatur des rPi uC (nur 3A+/3B+) liegt derzeit über dem Soft-Limit (Standard 60C).",
|
||||
"DescriptionUnderVoltageDetected": "rPI-Versorgungsspannung derzeit unter 4,65V",
|
||||
"HeadlineCurrentFlags": "Derzeitige flags",
|
||||
"HeadlinePreviouslyFlags": "Bisherige flags",
|
||||
"TitleCurrentlyThrottled": "Drosselung aktiv",
|
||||
"TitleFrequencyCapped": "Frequenz begrenzt",
|
||||
"TitlePreviouslyFrequencyCapped": "Vorh. Frequenzbegrenzung registriert",
|
||||
@ -433,21 +454,11 @@
|
||||
"Z": "Z",
|
||||
"ZTilt": "Z Tilt"
|
||||
},
|
||||
"DependenciesPanel": {
|
||||
"Dependency": "Abhängigkeit | Abhängigkeiten",
|
||||
"DependencyDescription": "Deine momentane {name} Version unterstützt nicht alle Funktionen von Mainsail. Aktualisiere {name} mindestens auf Version {neededVersion}."
|
||||
},
|
||||
"FarmPrinterPanel": {
|
||||
"ReconnectToPrinter": "Neu verbinden",
|
||||
"SwitchToPrinter": "Zum Drucker wechseln",
|
||||
"WebcamOff": "Aus"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "Option '{option}' im Abschnitt '{section}' ist veraltet und wird in einem zukünftigen Release entfernt.",
|
||||
"DeprecatedValue": "Wert '{value}' in Option '{option}' im Abschnitt '{section}' ist veraltet und wird in einem zukünftigen Release entfernt.",
|
||||
"DownloadLog": "Logdatei herunterladen",
|
||||
"KlipperWarnings": "Klipper Warnungen"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "Firmware Neustart",
|
||||
"KlipperCheck": "Bitte überprüfen Sie, ob der Klipper-Dienst läuft und ein UDS (Unix Domain Socket) konfiguriert ist.",
|
||||
@ -503,12 +514,6 @@
|
||||
"Empty": "Leer"
|
||||
}
|
||||
},
|
||||
"MoonrakerStatePanel": {
|
||||
"DownloadLog": "Log herunterladen",
|
||||
"FollowingPluginHasAnError": "Das folgende Plugin hat einen Fehler:",
|
||||
"MoonrakerErrorInfo": "Beim Laden der Moonraker-Komponenten wurde ein Fehler festgestellt. Bitte prüfe die Logdatei und behebe das Problem.",
|
||||
"MoonrakerWarnings": "Moonraker Warnungen"
|
||||
},
|
||||
"PowerControlPanel": {
|
||||
"Error": "Fehler",
|
||||
"Off": "Aus",
|
||||
|
@ -1,5 +1,28 @@
|
||||
{
|
||||
"App": {
|
||||
"Notifications": {
|
||||
"DependencyName": "Dependency: {name}",
|
||||
"DependencyDescription": "The current {name} version does not support all features of Mainsail. Update {name} to at least {neededVersion}.",
|
||||
"DismissAll": "Dismiss all",
|
||||
"MoonrakerWarnings": {
|
||||
"MoonrakerComponent": "Moonraker: {component}",
|
||||
"MoonrakerFailedComponentDescription": "An error was detected while loading the moonraker component '{component}'. Please check the logfile and fix the issue.",
|
||||
"MoonrakerWarning": "Moonraker warning",
|
||||
"UnparsedConfigOption": "Unparsed config option '{option}: {value}' detected in section [{section}]. This may be an option no longer available or could be the result of a module that failed to load. In the future this will result in a startup error.",
|
||||
"UnparsedConfigSection": "Unparsed config section [{section}] detected. This may be the result of a component that failed to load. In the future this will result in a startup error."
|
||||
},
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "Option '{option}' in section '{section}' is deprecated and will be removed in a future release.",
|
||||
"DeprecatedOptionHeadline": "Deprecated Klipper Option",
|
||||
"DeprecatedValue": "Value '{value}' in option '{option}' in section '{section}' is deprecated and will be removed in a future release.",
|
||||
"DeprecatedValueHeadline": "Deprecated Klipper Value",
|
||||
"KlipperWarning": "Klipper warning"
|
||||
},
|
||||
"Never": "never",
|
||||
"NextReboot": "next reboot",
|
||||
"NoNotification": "No Notification available",
|
||||
"Remind": "Remind:"
|
||||
},
|
||||
"NumberInput": {
|
||||
"GreaterOrEqualError": "Must be grater or equal than {min}!",
|
||||
"MustBeBetweenError": "Must be between {min} and {max}!",
|
||||
@ -15,8 +38,6 @@
|
||||
"DescriptionPreviouslyUnderVolted": "rPI supply voltage dropped below 4.65V at least once since the last power-on.",
|
||||
"DescriptionTemperatureLimitActive": "rPi uC (3A+/3B+ only) temperature is currently above the soft limit (default 60C).",
|
||||
"DescriptionUnderVoltageDetected": "rPI supply voltage currently below 4.65V",
|
||||
"HeadlineCurrentFlags": "Current Flags",
|
||||
"HeadlinePreviouslyFlags": "Previously Flags",
|
||||
"TitleCurrentlyThrottled": "Currently Throttled",
|
||||
"TitleFrequencyCapped": "Frequency Capped",
|
||||
"TitlePreviouslyFrequencyCapped": "Previously Frequency Capped",
|
||||
@ -434,21 +455,11 @@
|
||||
"Z": "Z",
|
||||
"ZTilt": "Z Tilt"
|
||||
},
|
||||
"DependenciesPanel": {
|
||||
"Dependency": "Dependency | Dependencies",
|
||||
"DependencyDescription": "Your current {name} version does not support all features of Mainsail. Update {name} to at least {neededVersion}."
|
||||
},
|
||||
"FarmPrinterPanel": {
|
||||
"ReconnectToPrinter": "Reconnect",
|
||||
"SwitchToPrinter": "Switch to Printer",
|
||||
"WebcamOff": "Off"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "Option '{option}' in section '{section}' is deprecated and will be removed in a future release.",
|
||||
"DeprecatedValue": "Value '{value}' in option '{option}' in section '{section}' is deprecated and will be removed in a future release.",
|
||||
"DownloadLog": "download log",
|
||||
"KlipperWarnings": "Klipper Warnings"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "Firmware Restart",
|
||||
"KlipperCheck": "Please check if the Klipper service is running and an UDS (Unix Domain Socket) is configured.",
|
||||
@ -504,12 +515,6 @@
|
||||
"Empty": "Empty"
|
||||
}
|
||||
},
|
||||
"MoonrakerStatePanel": {
|
||||
"DownloadLog": "Download Log",
|
||||
"FollowingPluginHasAnError": "Following plugin has an error:",
|
||||
"MoonrakerErrorInfo": "An error was detected while loading the moonraker components. Please check the logfile and fix the issue.",
|
||||
"MoonrakerWarnings": "Moonraker Warnings"
|
||||
},
|
||||
"PowerControlPanel": {
|
||||
"Error": "Error",
|
||||
"Off": "Off",
|
||||
|
@ -1,5 +1,11 @@
|
||||
{
|
||||
"App": {
|
||||
"Notifications": {
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "La opción '{option}' en la sección '{section}' está discontinuada y será removida en la próxima versión.",
|
||||
"DeprecatedValue": "El valor '{value}' en la opción '{option}' en la sección '{section}' está discontinuado y será removido en la próxima versión."
|
||||
}
|
||||
},
|
||||
"NumberInput": {
|
||||
"GreaterOrEqualError": "¡Debe ser mayor o igual a {min}!",
|
||||
"MustBeBetweenError": "¡Debe estar entre {min} y {max}!",
|
||||
@ -420,12 +426,6 @@
|
||||
"SwitchToPrinter": "Cambiar a impresora",
|
||||
"WebcamOff": "Apagar"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "La opción '{option}' en la sección '{section}' está discontinuada y será removida en la próxima versión.",
|
||||
"DeprecatedValue": "El valor '{value}' en la opción '{option}' en la sección '{section}' está discontinuado y será removido en la próxima versión.",
|
||||
"DownloadLog": "Descargar registro",
|
||||
"KlipperWarnings": "Alertas de Klipper"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "Reiniciar Firmware",
|
||||
"KlipperCheck": "Verifique que el servicio Klipper está corriendo y que un UDS (Unix Domain Socket) esta configurado.",
|
||||
|
@ -1,6 +1,12 @@
|
||||
{
|
||||
"_last_update:": "09.01.2022",
|
||||
"App": {
|
||||
"Notifications": {
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "L'option '{option}' dans la section '{section}' est obsolète.",
|
||||
"DeprecatedValue": "La valeur '{value}' dans l'option '{option}' dans la section '{section}' est obsolète."
|
||||
}
|
||||
},
|
||||
"Printers": "Imprimantes",
|
||||
"ThrottledStates": {
|
||||
"DescriptionCurrentlyThrottled": "rPi ARM core(s) sont actuellement réduits",
|
||||
@ -404,12 +410,6 @@
|
||||
"SwitchToPrinter": "Changer d'imprimante",
|
||||
"WebcamOff": "Arrêt"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "L'option '{option}' dans la section '{section}' est obsolète.",
|
||||
"DeprecatedValue": "La valeur '{value}' dans l'option '{option}' dans la section '{section}' est obsolète.",
|
||||
"DownloadLog": "téléchargement du fichier log",
|
||||
"KlipperWarnings": "Avertissements Klipper"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "Redémarrage Firmware",
|
||||
"KlipperCheck": "Contrôlez que le sevice Klipper est actif et qu'un UDS (Unix Domain Socket) est configuré",
|
||||
|
@ -1,6 +1,12 @@
|
||||
{
|
||||
"App": {
|
||||
"Printers": "Nyomtatók",
|
||||
"Notifications": {
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "'{section}' / '{option}' opcióját leírtuk, és a következő verzióban már nem lesz benne.",
|
||||
"DeprecatedValue": "'{section}' / '{option}' / Value '{value}' opcióját leírtuk, és a következő verzióban már nem lesz benne."
|
||||
}
|
||||
},
|
||||
"ThrottledStates": {
|
||||
"DescriptionCurrentlyThrottled": "Az rPi ARM mag(ok) jelenleg túlterheltek.",
|
||||
"DescriptionFrequencyCapped": "Az rPi ARM max frekvenciája jelenleg 1,2 GHz -re korlátozódik.",
|
||||
@ -403,12 +409,6 @@
|
||||
"SwitchToPrinter": "Váltás a nyomtatóra",
|
||||
"WebcamOff": "Ki"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "'{section}' / '{option}' opcióját leírtuk, és a következő verzióban már nem lesz benne.",
|
||||
"DeprecatedValue": "'{section}' / '{option}' / Value '{value}' opcióját leírtuk, és a következő verzióban már nem lesz benne.",
|
||||
"DownloadLog": "log letöltése",
|
||||
"KlipperWarnings": "Klipper Figyelmeztetések"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "Firmware újraindítása",
|
||||
"KlipperCheck": "Kérjük, ellenőrizd, a Klipper szolgáltatás fut-e, konfigurálva van-e UDS (Unix Domain Socket).",
|
||||
|
@ -1,6 +1,12 @@
|
||||
{
|
||||
"App": {
|
||||
"Printers": "Stampanti",
|
||||
"Notifications": {
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "L'opzione '{option}' nella sezione '{section}' è obsoleta e sarà rimossa in una versione futura.",
|
||||
"DeprecatedValue": "Il valore '{value}' in '{option}' nella sezione '{section}' è obsoleto e sarà rimosso in una versione futura."
|
||||
}
|
||||
},
|
||||
"ThrottledStates": {
|
||||
"DescriptionCurrentlyThrottled": "Uno o più core ARM dell'rPI sono attualmente rallentati.",
|
||||
"DescriptionFrequencyCapped": "La frequenza massima del processore ARM dell'rPI è attualmente limitata a 1,2 GHz.",
|
||||
@ -403,12 +409,6 @@
|
||||
"SwitchToPrinter": "Passa alla Stampante",
|
||||
"WebcamOff": "Spenta"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "L'opzione '{option}' nella sezione '{section}' è obsoleta e sarà rimossa in una versione futura.",
|
||||
"DeprecatedValue": "Il valore '{value}' in '{option}' nella sezione '{section}' è obsoleto e sarà rimosso in una versione futura.",
|
||||
"DownloadLog": "scarica log",
|
||||
"KlipperWarnings": "Avvisi di Klipper"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "Riavvio Firmware",
|
||||
"KlipperCheck": "Controlla se il servizio Klipper è in esecuzione e se è configurato un UDS (Unix Domain Socket).",
|
||||
|
@ -1,6 +1,12 @@
|
||||
{
|
||||
"App": {
|
||||
"Printers": "Printers",
|
||||
"Notifications": {
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "Optie '{option}' in sectie '{section}' is verouderd en wordt in een toekomstige versie verwijderd.",
|
||||
"DeprecatedValue": "Waarde '{value}' in optie '{option}' in sectie '{section}' is verouderd en wordt in een toekomstige versie verwijderd."
|
||||
}
|
||||
},
|
||||
"ThrottledStates": {
|
||||
"DescriptionCurrentlyThrottled": "rPi ARM core(s) worden momenteel gethrottled.",
|
||||
"DescriptionFrequencyCapped": "rPi ARM max frequency is momenteel beperkt tot 1.2 GHz.",
|
||||
@ -406,12 +412,6 @@
|
||||
"SwitchToPrinter": "Wissel naar Printer",
|
||||
"WebcamOff": "Uit"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "Optie '{option}' in sectie '{section}' is verouderd en wordt in een toekomstige versie verwijderd.",
|
||||
"DeprecatedValue": "Waarde '{value}' in optie '{option}' in sectie '{section}' is verouderd en wordt in een toekomstige versie verwijderd.",
|
||||
"DownloadLog": "log downloaden",
|
||||
"KlipperWarnings": "Klipper Waarschuwingen"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "Firmware Herstart",
|
||||
"KlipperCheck": "Controleer of de Klipper service draait en een UDS (Unix Domain Socket) geconfigureerd is.",
|
||||
|
@ -1,6 +1,12 @@
|
||||
{
|
||||
"App": {
|
||||
"Printers": "Drukarki",
|
||||
"Notifications": {
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "Opcja '{option}' w sekcji '{section}' jest przestarzała i zostanie usunięta w przyszłych wydaniach.",
|
||||
"DeprecatedValue": "Ustawienie '{value}' w opcji '{option}' w sekcji '{section}' jest przestarzała i zostanie usunięta w przyszłych wydaniach."
|
||||
}
|
||||
},
|
||||
"ThrottledStates": {
|
||||
"DescriptionCurrentlyThrottled": "Częstotliwość rdzeni rPi jest aktualnie obniżona.",
|
||||
"DescriptionFrequencyCapped": "Taktowanie rPi ograniczone do 1.2 GHz.",
|
||||
@ -403,12 +409,6 @@
|
||||
"SwitchToPrinter": "Przełącz do drukarki",
|
||||
"WebcamOff": "Wyłącz"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "Opcja '{option}' w sekcji '{section}' jest przestarzała i zostanie usunięta w przyszłych wydaniach.",
|
||||
"DeprecatedValue": "Ustawienie '{value}' w opcji '{option}' w sekcji '{section}' jest przestarzała i zostanie usunięta w przyszłych wydaniach.",
|
||||
"DownloadLog": "Pobierz logi",
|
||||
"KlipperWarnings": "Ostrzeżenia Klippera"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "Ponowne uruchomienie oprogramowania",
|
||||
"KlipperCheck": "Sprawdź , czy Klipper jest uruchomiony oraz czy UDS (Unix Domain Socket) został skonfigurowany poprawnie.",
|
||||
|
@ -1,6 +1,12 @@
|
||||
{
|
||||
"App": {
|
||||
"Printers": "Принтер",
|
||||
"Notifications": {
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "Опция '{option}' в разделе '{section}' устарела и будет удалена в будущем выпуске.",
|
||||
"DeprecatedValue": "Значение '{value}' в опции '{option}' в секции '{section}' устарело и будет удалено в будущем релизе."
|
||||
}
|
||||
},
|
||||
"ThrottledStates": {
|
||||
"DescriptionCurrentlyThrottled": "ARM-ядро(ядра) rPi в настоящее время дросселируется.",
|
||||
"DescriptionFrequencyCapped": "Максимальная частота rPi ARM в настоящее время ограничена 1,2 ГГц.",
|
||||
@ -406,12 +412,6 @@
|
||||
"SwitchToPrinter": "Переключение на принтер",
|
||||
"WebcamOff": "Офф"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "Опция '{option}' в разделе '{section}' устарела и будет удалена в будущем выпуске.",
|
||||
"DeprecatedValue": "Значение '{value}' в опции '{option}' в секции '{section}' устарело и будет удалено в будущем релизе.",
|
||||
"DownloadLog": "Загрузить файл журнала",
|
||||
"KlipperWarnings": "Предупреждения о клиперах"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "Перезапуск прошивки",
|
||||
"KlipperCheck": "Проверьте, запущена ли служба Klipper и настроен ли UDS (Unix Domain Socket).",
|
||||
|
@ -1,6 +1,12 @@
|
||||
{
|
||||
"App": {
|
||||
"Printers": "列印機組",
|
||||
"Notifications": {
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "{section}' 中的部分選項 '{option}' 已棄用,將在未來版本中刪除。",
|
||||
"DeprecatedValue": "不推薦使用'{section}' 部分的選項'{option}' 中的值'{value}',並將在未來版本中刪除。"
|
||||
}
|
||||
},
|
||||
"ThrottledStates": {
|
||||
"DescriptionCurrentlyThrottled": "樹莓派ARM核心目前已被限制.",
|
||||
"DescriptionFrequencyCapped": "樹莓派ARM最大頻率目前限制為1.2 GHz.",
|
||||
@ -351,12 +357,6 @@
|
||||
"SwitchToPrinter": "切換到列印機",
|
||||
"WebcamOff": "關閉"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "{section}' 中的部分選項 '{option}' 已棄用,將在未來版本中刪除。",
|
||||
"DeprecatedValue": "不推薦使用'{section}' 部分的選項'{option}' 中的值'{value}',並將在未來版本中刪除。",
|
||||
"DownloadLog": "下載日誌",
|
||||
"KlipperWarnings": "Klipper 警告"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "韌體重新啟動",
|
||||
"KlipperCheck": "請檢查Klipper服務是否運行並且已經設定UDS(Unix Domain Socket)。",
|
||||
|
@ -1,6 +1,12 @@
|
||||
{
|
||||
"App": {
|
||||
"Printers": "打印机组",
|
||||
"Notifications": {
|
||||
"KlipperWarnings": {
|
||||
"DeprecatedOption": "选项 '{option}' 在章节 '{section}' 已经弃用,在未来版本会被移除.",
|
||||
"DeprecatedValue": "数值 '{value}' 在选项 '{option}' 中的章节'{section}' 已经弃用,在未来版本会被移除."
|
||||
}
|
||||
},
|
||||
"ThrottledStates": {
|
||||
"DescriptionCurrentlyThrottled": "rPi ARM 核心当前被限制.",
|
||||
"DescriptionFrequencyCapped": "rPi ARM 最高频率限制再 1.2 GHz.",
|
||||
@ -403,12 +409,6 @@
|
||||
"SwitchToPrinter": "切换到打印机",
|
||||
"WebcamOff": "关闭"
|
||||
},
|
||||
"KlipperWarningsPanel": {
|
||||
"DeprecatedOption": "选项 '{option}' 在章节 '{section}' 已经弃用,在未来版本会被移除.",
|
||||
"DeprecatedValue": "数值 '{value}' 在选项 '{option}' 中的章节'{section}' 已经弃用,在未来版本会被移除.",
|
||||
"DownloadLog": "下载记录",
|
||||
"KlipperWarnings": "Klipper 警告"
|
||||
},
|
||||
"KlippyStatePanel": {
|
||||
"FirmwareRestart": "Firmware 重启",
|
||||
"KlipperCheck": "请检查Klipper服务是否运行并且已经设置UDS(Unix Domain Socket).",
|
||||
|
@ -30,7 +30,7 @@ Vue.use(VueMeta)
|
||||
import VueLoadImage from 'vue-load-image'
|
||||
Vue.component('VueLoadImage', VueLoadImage)
|
||||
|
||||
//vue-toast-notification
|
||||
//vue-toast-notifications
|
||||
import VueToast from 'vue-toast-notification'
|
||||
import 'vue-toast-notification/dist/theme-sugar.css'
|
||||
import { WebSocketPlugin } from '@/plugins/webSocketClient'
|
||||
|
@ -91,7 +91,6 @@ import MacrosPanel from '@/components/panels/MacrosPanel.vue'
|
||||
import MiniconsolePanel from '@/components/panels/MiniconsolePanel.vue'
|
||||
import MinSettingsPanel from '@/components/panels/MinSettingsPanel.vue'
|
||||
import MiscellaneousPanel from '@/components/panels/MiscellaneousPanel.vue'
|
||||
import MoonrakerStatePanel from '@/components/panels/MoonrakerStatePanel.vue'
|
||||
import PrintsettingsPanel from '@/components/panels/PrintsettingsPanel.vue'
|
||||
import StatusPanel from '@/components/panels/StatusPanel.vue'
|
||||
import ToolsPanel from '@/components/panels/ToolsPanel.vue'
|
||||
@ -109,7 +108,6 @@ import kebabCase from 'lodash.kebabcase'
|
||||
MiniconsolePanel,
|
||||
MinSettingsPanel,
|
||||
MiscellaneousPanel,
|
||||
MoonrakerStatePanel,
|
||||
PrintsettingsPanel,
|
||||
StatusPanel,
|
||||
ToolsPanel,
|
||||
|
@ -411,4 +411,8 @@ export const actions: ActionTree<GuiState, RootState> = {
|
||||
dispatch('saveSetting', { name: `gcodeViewer.klipperCache.${key}`, value })
|
||||
})
|
||||
},
|
||||
|
||||
announcementDismissFlag(_, payload) {
|
||||
window.console.log(payload)
|
||||
},
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import { macros } from '@/store/gui/macros'
|
||||
import { presets } from '@/store/gui/presets'
|
||||
import { remoteprinters } from '@/store/gui/remoteprinters'
|
||||
import { webcams } from '@/store/gui/webcams'
|
||||
import { notifications } from '@/store/gui/notifications'
|
||||
|
||||
export const getDefaultState = (): GuiState => {
|
||||
return {
|
||||
@ -234,6 +235,7 @@ export const gui: Module<GuiState, any> = {
|
||||
console,
|
||||
gcodehistory,
|
||||
macros,
|
||||
notifications,
|
||||
presets,
|
||||
remoteprinters,
|
||||
webcams,
|
||||
|
89
src/store/gui/notifications/actions.ts
Normal file
89
src/store/gui/notifications/actions.ts
Normal file
@ -0,0 +1,89 @@
|
||||
import { ActionTree } from 'vuex'
|
||||
import { GuiNotificationState, GuiNotificationStateDismissEntry } from './types'
|
||||
import { RootState } from '../../types'
|
||||
import Vue from 'vue'
|
||||
|
||||
export const actions: ActionTree<GuiNotificationState, RootState> = {
|
||||
reset({ commit }) {
|
||||
commit('reset')
|
||||
},
|
||||
|
||||
upload({ state }) {
|
||||
Vue.$socket.emit('server.database.post_item', {
|
||||
namespace: 'mainsail',
|
||||
key: 'notifications.dismiss',
|
||||
value: state.dismiss,
|
||||
})
|
||||
},
|
||||
|
||||
close({ dispatch }, payload) {
|
||||
const posFirstSlash = payload.id.indexOf('/')
|
||||
if (posFirstSlash === -1) return
|
||||
|
||||
const category = payload.id.slice(0, posFirstSlash)
|
||||
const id = payload.id.slice(posFirstSlash + 1)
|
||||
|
||||
if (category === 'announcement') {
|
||||
dispatch('server/announcements/close', { entry_id: id }, { root: true })
|
||||
return
|
||||
}
|
||||
|
||||
dispatch('storeDismiss', {
|
||||
entry_id: id,
|
||||
category,
|
||||
type: 'ever',
|
||||
time: null,
|
||||
})
|
||||
},
|
||||
|
||||
dismiss({ dispatch }, payload) {
|
||||
const posFirstSlash = payload.id.indexOf('/')
|
||||
if (posFirstSlash === -1) return
|
||||
|
||||
const category = payload.id.slice(0, posFirstSlash)
|
||||
const id = payload.id.slice(posFirstSlash + 1)
|
||||
|
||||
if (category === 'announcement') {
|
||||
dispatch('server/announcements/dismiss', { entry_id: id, time: payload.time }, { root: true })
|
||||
return
|
||||
}
|
||||
|
||||
dispatch('storeDismiss', {
|
||||
entry_id: id,
|
||||
category,
|
||||
type: payload.type,
|
||||
time: payload.time,
|
||||
})
|
||||
},
|
||||
|
||||
async storeDismiss(
|
||||
{ commit, dispatch, state },
|
||||
payload: { entry_id: string; category: string; type: string; time: number | null }
|
||||
) {
|
||||
let date = new Date().getTime()
|
||||
if (payload.type === 'time') {
|
||||
date = new Date().getTime() + (payload.time ?? 0) * 1000
|
||||
}
|
||||
|
||||
const newDismiss: GuiNotificationStateDismissEntry = {
|
||||
id: payload.entry_id,
|
||||
category: payload.category,
|
||||
type: payload.type,
|
||||
date,
|
||||
}
|
||||
|
||||
if (
|
||||
state.dismiss.filter(
|
||||
(dismiss) =>
|
||||
dismiss.id === newDismiss.id &&
|
||||
dismiss.category === newDismiss.category &&
|
||||
dismiss.type === newDismiss.type
|
||||
).length
|
||||
) {
|
||||
await commit('removeDismiss', newDismiss)
|
||||
}
|
||||
|
||||
await commit('addDismiss', newDismiss)
|
||||
await dispatch('upload')
|
||||
},
|
||||
}
|
299
src/store/gui/notifications/getters.ts
Normal file
299
src/store/gui/notifications/getters.ts
Normal file
@ -0,0 +1,299 @@
|
||||
import { GetterTree } from 'vuex'
|
||||
import { GuiNotificationState, GuiNotificationStateDismissEntry, GuiNotificationStateEntry } from './types'
|
||||
import { ServerAnnouncementsStateEntry } from '@/store/server/announcements/types'
|
||||
import i18n from '@/plugins/i18n.js'
|
||||
import { RootStateDependency } from '@/store/types'
|
||||
import { camelize } from '@/plugins/helpers'
|
||||
import { sha256 } from 'js-sha256'
|
||||
import { PrinterStateKlipperConfigWarning } from '@/store/printer/types'
|
||||
|
||||
export const getters: GetterTree<GuiNotificationState, any> = {
|
||||
getNotifications: (state, getters) => {
|
||||
let notifications: GuiNotificationStateEntry[] = []
|
||||
|
||||
// moonraker announcements
|
||||
notifications = notifications.concat(getters['getNotificationsAnnouncements'])
|
||||
|
||||
// rpi flag notifications
|
||||
notifications = notifications.concat(getters['getNotificationsFlags'])
|
||||
|
||||
// mainsail dependencies
|
||||
notifications = notifications.concat(getters['getNotificationsDependencies'])
|
||||
|
||||
// moonraker warnings
|
||||
notifications = notifications.concat(getters['getNotificationsMoonrakerWarnings'])
|
||||
|
||||
// moonraker failed compontents
|
||||
notifications = notifications.concat(getters['getNotificationsMoonrakerFailedComponents'])
|
||||
|
||||
// klipper warnings
|
||||
notifications = notifications.concat(getters['getNotificationsKlipperWarnings'])
|
||||
|
||||
const mapType = {
|
||||
normal: 2,
|
||||
high: 1,
|
||||
critical: 0,
|
||||
}
|
||||
|
||||
return notifications.sort((a, b) => {
|
||||
if (mapType[a.priority] < mapType[b.priority]) return -1
|
||||
if (mapType[a.priority] > mapType[b.priority]) return 1
|
||||
|
||||
return b.date.getTime() - a.date.getTime()
|
||||
})
|
||||
},
|
||||
|
||||
getNotificationsAnnouncements: (state, getters, rootState, rootGetters) => {
|
||||
const notifications: GuiNotificationStateEntry[] = []
|
||||
|
||||
// moonraker announcements
|
||||
const announcements = rootGetters['server/announcements/getAnnouncements']
|
||||
if (announcements.length) {
|
||||
announcements.forEach((entry: ServerAnnouncementsStateEntry) => {
|
||||
notifications.push({
|
||||
id: 'announcement/' + entry.entry_id,
|
||||
priority: entry.priority,
|
||||
title: entry.title,
|
||||
description: entry.description,
|
||||
date: entry.date,
|
||||
dismissed: entry.dismissed,
|
||||
url: entry.url,
|
||||
} as GuiNotificationStateEntry)
|
||||
})
|
||||
}
|
||||
|
||||
return notifications
|
||||
},
|
||||
|
||||
getNotificationsFlags: (state, getters, rootState, rootGetters) => {
|
||||
const notifications: GuiNotificationStateEntry[] = []
|
||||
|
||||
// get all current flags
|
||||
let flags = rootGetters['server/getThrottledStateFlags']
|
||||
if (flags.length) {
|
||||
const date = rootState.server.system_boot_at ?? new Date()
|
||||
|
||||
// get all dismissed flags and convert it to a string[]
|
||||
const flagDismisses = rootGetters['gui/notifications/getDismissByCategory']('flag').map(
|
||||
(dismiss: GuiNotificationStateDismissEntry) => {
|
||||
return dismiss.id
|
||||
}
|
||||
)
|
||||
|
||||
// filter all dismissed flags
|
||||
flags = flags.filter((flag: string) => !flagDismisses.includes(flag))
|
||||
|
||||
// add all flags to the notifications array
|
||||
flags.forEach((flag: string) => {
|
||||
notifications.push({
|
||||
id: 'flag/' + flag,
|
||||
priority: flag.startsWith('Previously') ? 'high' : 'critical',
|
||||
title: i18n.t(`App.ThrottledStates.Title${flag}`),
|
||||
description: i18n.t(`App.ThrottledStates.Description${flag}`),
|
||||
date,
|
||||
dismissed: false,
|
||||
} as GuiNotificationStateEntry)
|
||||
})
|
||||
}
|
||||
|
||||
return notifications
|
||||
},
|
||||
|
||||
getNotificationsDependencies: (state, getters, rootState, rootGetters) => {
|
||||
const notifications: GuiNotificationStateEntry[] = []
|
||||
|
||||
let dependencies = rootGetters['getDependencies']
|
||||
if (dependencies.length) {
|
||||
const date = rootState.server.system_boot_at ?? new Date()
|
||||
|
||||
// get all dismissed dependencies and convert it to a string[]
|
||||
const flagDismisses = rootGetters['gui/notifications/getDismissByCategory']('dependency').map(
|
||||
(dismiss: GuiNotificationStateDismissEntry) => {
|
||||
return dismiss.id
|
||||
}
|
||||
)
|
||||
|
||||
// filter all dismissed dependencies
|
||||
dependencies = dependencies.filter(
|
||||
(dependency: RootStateDependency) =>
|
||||
!flagDismisses.includes(`${dependency.serviceName}/${dependency.neededVersion}`)
|
||||
)
|
||||
|
||||
dependencies.forEach((dependency: RootStateDependency) => {
|
||||
notifications.push({
|
||||
id: `dependency/${dependency.serviceName}/${dependency.neededVersion}`,
|
||||
priority: 'high',
|
||||
title: i18n.t('App.Notifications.DependencyName', { name: dependency.serviceName }).toString(),
|
||||
description: i18n
|
||||
.t('App.Notifications.DependencyDescription', {
|
||||
name: dependency.serviceName,
|
||||
installedVersion: dependency.installedVersion,
|
||||
neededVersion: dependency.neededVersion,
|
||||
})
|
||||
.toString(),
|
||||
date,
|
||||
dismissed: false,
|
||||
} as GuiNotificationStateEntry)
|
||||
})
|
||||
}
|
||||
|
||||
return notifications
|
||||
},
|
||||
|
||||
getNotificationsMoonrakerWarnings: (state, getters, rootState, rootGetters) => {
|
||||
const notifications: GuiNotificationStateEntry[] = []
|
||||
|
||||
let warnings = rootState.server.warnings ?? []
|
||||
if (warnings.length) {
|
||||
const date = rootState.server.system_boot_at ?? new Date()
|
||||
|
||||
// get all dismissed moonraker warnings and convert it to a string[]
|
||||
const warningsDismisses = rootGetters['gui/notifications/getDismissByCategory']('moonrakerWarning').map(
|
||||
(dismiss: GuiNotificationStateDismissEntry) => {
|
||||
return dismiss.id
|
||||
}
|
||||
)
|
||||
|
||||
// filter all dismissed warnings
|
||||
warnings = warnings.filter((warning: string) => !warningsDismisses.includes(sha256(warning)))
|
||||
|
||||
warnings.forEach((warning: string) => {
|
||||
let description = warning
|
||||
|
||||
// add possible translations
|
||||
if (warning.startsWith('Unparsed config option')) {
|
||||
const warningRegExp = RegExp(/'(?<option>.+): (?<value>.+)'.+\[(?<section>.+)\]/)
|
||||
const output = warningRegExp.exec(warning)?.groups ?? { option: '', section: '', value: '' }
|
||||
description = i18n.t('App.Notifications.MoonrakerWarnings.UnparsedConfigOption', output).toString()
|
||||
} else if (warning.startsWith('Unparsed config section')) {
|
||||
const warningRegExp = RegExp(/\[(?<section>.+)\]/)
|
||||
const output = warningRegExp.exec(warning)?.groups ?? { section: '' }
|
||||
description = i18n.t('App.Notifications.MoonrakerWarnings.UnparsedConfigSection', output).toString()
|
||||
}
|
||||
|
||||
notifications.push({
|
||||
id: `moonrakerWarning/${sha256(warning)}`,
|
||||
priority: 'high',
|
||||
title: i18n.t('App.Notifications.MoonrakerWarnings.MoonrakerWarning').toString(),
|
||||
description: description,
|
||||
date,
|
||||
dismissed: false,
|
||||
} as GuiNotificationStateEntry)
|
||||
})
|
||||
}
|
||||
|
||||
return notifications
|
||||
},
|
||||
|
||||
getNotificationsMoonrakerFailedComponents: (state, getters, rootState, rootGetters) => {
|
||||
const notifications: GuiNotificationStateEntry[] = []
|
||||
|
||||
let failedCompontents = rootState.server.failed_components ?? []
|
||||
if (failedCompontents.length) {
|
||||
const date = rootState.server.system_boot_at ?? new Date()
|
||||
|
||||
// get all dismissed failed components and convert it to a string[]
|
||||
const flagDismisses = rootGetters['gui/notifications/getDismissByCategory']('moonrakerFailedComponent').map(
|
||||
(dismiss: GuiNotificationStateDismissEntry) => {
|
||||
return dismiss.id
|
||||
}
|
||||
)
|
||||
|
||||
// filter all dismissed failed components
|
||||
failedCompontents = failedCompontents.filter((component: string) => !flagDismisses.includes(component))
|
||||
|
||||
failedCompontents.forEach((component: string) => {
|
||||
notifications.push({
|
||||
id: `moonrakerFailedComponent/${component}`,
|
||||
priority: 'high',
|
||||
title: i18n.t('App.Notifications.MoonrakerWarnings.MoonrakerComponent', { component }).toString(),
|
||||
description: i18n
|
||||
.t('App.Notifications.MoonrakerWarnings.MoonrakerFailedComponentDescription', { component })
|
||||
.toString(),
|
||||
date,
|
||||
dismissed: false,
|
||||
} as GuiNotificationStateEntry)
|
||||
})
|
||||
}
|
||||
|
||||
return notifications
|
||||
},
|
||||
|
||||
getNotificationsKlipperWarnings: (state, getters, rootState, rootGetters) => {
|
||||
const notifications: GuiNotificationStateEntry[] = []
|
||||
|
||||
let warnings = (rootState.printer.configfile?.warnings ?? []) as PrinterStateKlipperConfigWarning[]
|
||||
if (warnings.length) {
|
||||
const date = rootState.server.system_boot_at ?? new Date()
|
||||
|
||||
// get all dismissed klipper warnings and convert it to a string[]
|
||||
const warningsDismisses = rootGetters['gui/notifications/getDismissByCategory']('klipperWarning').map(
|
||||
(dismiss: GuiNotificationStateDismissEntry) => {
|
||||
return dismiss.id
|
||||
}
|
||||
)
|
||||
|
||||
// filter all dismissed warnings
|
||||
warnings = warnings.filter((warning) => !warningsDismisses.includes(sha256(warning.message)))
|
||||
|
||||
warnings.forEach((warning) => {
|
||||
let title = i18n.t('App.Notifications.KlipperWarnings.KlipperWarning').toString()
|
||||
let description = warning.message
|
||||
|
||||
// add possible translations
|
||||
if (warning.type === 'deprecated_value') {
|
||||
title = i18n.t('App.Notifications.KlipperWarnings.DeprecatedValueHeadline').toString()
|
||||
description = i18n.t('App.Notifications.KlipperWarnings.DeprecatedValue', warning).toString()
|
||||
} else if (warning.type === 'deprecated_option') {
|
||||
title = i18n.t('App.Notifications.KlipperWarnings.DeprecatedOptionHeadline').toString()
|
||||
description = i18n.t('App.Notifications.KlipperWarnings.DeprecatedOption', warning).toString()
|
||||
}
|
||||
|
||||
// generate url to mainsail docs to fix this warning
|
||||
let url = 'https://docs.mainsail.xyz/faq/klipper_warnings/' + warning.type
|
||||
if (warning.type === 'deprecated_option' && warning.option.startsWith('default_parameter'))
|
||||
url += '#default_parameter'
|
||||
else if (warning.type === 'deprecated_option') url += '#' + warning.option
|
||||
else if (warning.type === 'deprecated_value') url += '#' + warning.value
|
||||
|
||||
notifications.push({
|
||||
id: `klipperWarning/${sha256(warning.message)}`,
|
||||
priority: 'high',
|
||||
title: title,
|
||||
description: description,
|
||||
date,
|
||||
url,
|
||||
dismissed: false,
|
||||
} as GuiNotificationStateEntry)
|
||||
})
|
||||
}
|
||||
|
||||
return notifications
|
||||
},
|
||||
|
||||
getDismiss: (state, getters, rootState) => {
|
||||
const currentTime = new Date()
|
||||
const systemBootAt = rootState.server.system_boot_at ?? new Date()
|
||||
let dismisses = [...state.dismiss]
|
||||
dismisses = dismisses.filter((dismiss) => {
|
||||
if (dismiss.type === 'reboot') {
|
||||
return systemBootAt.getTime() < dismiss.date
|
||||
}
|
||||
|
||||
if (dismiss.type === 'time') {
|
||||
return currentTime.getTime() < dismiss.date
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return dismisses
|
||||
},
|
||||
|
||||
getDismissByCategory: (state, getters) => (category: string) => {
|
||||
let dismisses = getters.getDismiss
|
||||
dismisses = dismisses.filter((dismiss: GuiNotificationStateDismissEntry) => dismiss.category === category)
|
||||
|
||||
return dismisses
|
||||
},
|
||||
}
|
22
src/store/gui/notifications/index.ts
Normal file
22
src/store/gui/notifications/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { GuiNotificationState } from './types'
|
||||
import { Module } from 'vuex'
|
||||
import { actions } from './actions'
|
||||
import { mutations } from './mutations'
|
||||
import { getters } from './getters'
|
||||
|
||||
export const getDefaultState = (): GuiNotificationState => {
|
||||
return {
|
||||
dismiss: [],
|
||||
}
|
||||
}
|
||||
|
||||
// initial state
|
||||
const state = getDefaultState()
|
||||
|
||||
export const notifications: Module<GuiNotificationState, any> = {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
28
src/store/gui/notifications/mutations.ts
Normal file
28
src/store/gui/notifications/mutations.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { getDefaultState } from './index'
|
||||
import { MutationTree } from 'vuex'
|
||||
import { GuiNotificationState } from './types'
|
||||
import Vue from 'vue'
|
||||
|
||||
export const mutations: MutationTree<GuiNotificationState> = {
|
||||
reset(state) {
|
||||
Object.assign(state, getDefaultState())
|
||||
},
|
||||
|
||||
addDismiss(state, payload) {
|
||||
const dismiss = [...state.dismiss]
|
||||
dismiss.push(payload)
|
||||
|
||||
Vue.set(state, 'dismiss', dismiss)
|
||||
},
|
||||
|
||||
removeDismiss(state, payload) {
|
||||
const dismiss = [...state.dismiss]
|
||||
const index = dismiss.findIndex(
|
||||
(dismiss) =>
|
||||
dismiss.id === payload.id && dismiss.category === payload.category && dismiss.type === payload.type
|
||||
)
|
||||
if (index !== -1) dismiss.splice(index)
|
||||
|
||||
Vue.set(state, 'dismiss', dismiss)
|
||||
},
|
||||
}
|
20
src/store/gui/notifications/types.ts
Normal file
20
src/store/gui/notifications/types.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export interface GuiNotificationState {
|
||||
dismiss: GuiNotificationStateDismissEntry[]
|
||||
}
|
||||
|
||||
export interface GuiNotificationStateEntry {
|
||||
id: string
|
||||
priority: 'normal' | 'high' | 'critical'
|
||||
title: string
|
||||
description: string
|
||||
date: Date
|
||||
dismissed: boolean
|
||||
url?: string
|
||||
}
|
||||
|
||||
export interface GuiNotificationStateDismissEntry {
|
||||
id: string
|
||||
category: string
|
||||
type: string
|
||||
date: number
|
||||
}
|
@ -3,6 +3,7 @@ import { GuiConsoleState } from '@/store/gui/console/types'
|
||||
import { GuiPresetsState } from '@/store/gui/presets/types'
|
||||
import { GuiRemoteprintersState } from '@/store/gui/remoteprinters/types'
|
||||
import { ServerHistoryStateJob } from '@/store/server/history/types'
|
||||
import { GuiNotificationState } from '@/store/gui/notifications/types'
|
||||
|
||||
export interface GuiState {
|
||||
general: {
|
||||
@ -79,6 +80,7 @@ export interface GuiState {
|
||||
}
|
||||
}
|
||||
macros?: GuiMacrosState
|
||||
notifications?: GuiNotificationState
|
||||
presets?: GuiPresetsState
|
||||
remoteprinters?: GuiRemoteprintersState
|
||||
uiSettings: {
|
||||
|
@ -285,9 +285,12 @@ export const getters: GetterTree<PrinterState, RootState> = {
|
||||
max_power: undefined,
|
||||
}
|
||||
|
||||
if ('settings' in state.configfile && key.toLowerCase() in state.configfile.settings) {
|
||||
if (
|
||||
'configfile' in state &&
|
||||
'settings' in state.configfile &&
|
||||
key.toLowerCase() in state.configfile.settings
|
||||
) {
|
||||
if ('off_below' in settings) tmp.off_below = settings?.off_below ?? 0
|
||||
|
||||
if ('max_power' in settings) tmp.max_power = settings?.max_power ?? 1
|
||||
}
|
||||
|
||||
|
@ -171,3 +171,11 @@ export interface PrinterStateMcu {
|
||||
measured_max_temp: number | null
|
||||
}
|
||||
}
|
||||
|
||||
export interface PrinterStateKlipperConfigWarning {
|
||||
message: string
|
||||
option: string
|
||||
section: string
|
||||
type: 'deprecated_value' | 'deprecated_option'
|
||||
value: string
|
||||
}
|
||||
|
@ -84,6 +84,10 @@ export const actions: ActionTree<ServerState, RootState> = {
|
||||
|
||||
initProcStats({ commit }, payload) {
|
||||
if (payload.throttled_state !== null) commit('setThrottledState', payload.throttled_state)
|
||||
if (payload.system_uptime) {
|
||||
const system_boot_at = new Date(new Date().getTime() - payload.system_uptime * 1000)
|
||||
commit('setSystemBootAt', system_boot_at)
|
||||
}
|
||||
},
|
||||
|
||||
updateProcStats({ commit }, payload) {
|
||||
|
45
src/store/server/announcements/actions.ts
Normal file
45
src/store/server/announcements/actions.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import Vue from 'vue'
|
||||
import { ActionTree } from 'vuex'
|
||||
import { RootState } from '@/store/types'
|
||||
import { ServerAnnouncementsState } from './types'
|
||||
|
||||
export const actions: ActionTree<ServerAnnouncementsState, RootState> = {
|
||||
reset({ commit }) {
|
||||
commit('reset')
|
||||
},
|
||||
|
||||
init() {
|
||||
Vue.$socket.emit('server.announcements.list', {}, { action: 'server/announcements/getList' })
|
||||
},
|
||||
|
||||
getList({ commit }, payload) {
|
||||
if ('entries' in payload) {
|
||||
const entries = payload.entries.map((entry: any) => {
|
||||
const date = new Date(entry.date * 1000)
|
||||
const date_dismissed = payload.date_dismissed ? new Date(entry.date_dismissed * 1000) : null
|
||||
const dismiss_wake = payload.dismiss_wake ? new Date(entry.dismiss_wake * 1000) : null
|
||||
|
||||
return { ...entry, date, date_dismissed, dismiss_wake }
|
||||
})
|
||||
|
||||
commit('setEntries', entries)
|
||||
}
|
||||
if ('feeds' in payload) commit('setFeeds', payload.feeds)
|
||||
},
|
||||
|
||||
getDismissed({ commit }, payload) {
|
||||
commit('setDismissed', { entry_id: payload.entry_id, status: true })
|
||||
},
|
||||
|
||||
getWaked({ commit }, payload) {
|
||||
commit('setDismissed', { entry_id: payload.entry_id, status: false })
|
||||
},
|
||||
|
||||
close(_, payload) {
|
||||
Vue.$socket.emit('server.announcements.dismiss', { entry_id: payload.entry_id })
|
||||
},
|
||||
|
||||
dismiss(_, payload) {
|
||||
Vue.$socket.emit('server.announcements.dismiss', { entry_id: payload.entry_id, wake_time: payload.time })
|
||||
},
|
||||
}
|
9
src/store/server/announcements/getters.ts
Normal file
9
src/store/server/announcements/getters.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { GetterTree } from 'vuex'
|
||||
import { ServerAnnouncementsState, ServerAnnouncementsStateEntry } from './types'
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const getters: GetterTree<ServerAnnouncementsState, any> = {
|
||||
getAnnouncements: (state) => {
|
||||
return state.entries.filter((entry: ServerAnnouncementsStateEntry) => !entry.dismissed)
|
||||
},
|
||||
}
|
24
src/store/server/announcements/index.ts
Normal file
24
src/store/server/announcements/index.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Module } from 'vuex'
|
||||
import { ServerAnnouncementsState } from '@/store/server/announcements/types'
|
||||
import { actions } from '@/store/server/announcements/actions'
|
||||
import { mutations } from '@/store/server/announcements/mutations'
|
||||
import { getters } from '@/store/server/announcements/getters'
|
||||
|
||||
export const getDefaultState = (): ServerAnnouncementsState => {
|
||||
return {
|
||||
entries: [],
|
||||
feeds: [],
|
||||
}
|
||||
}
|
||||
|
||||
// initial state
|
||||
const state = getDefaultState()
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const announcements: Module<ServerAnnouncementsState, any> = {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
32
src/store/server/announcements/mutations.ts
Normal file
32
src/store/server/announcements/mutations.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { getDefaultState } from './index'
|
||||
import { MutationTree } from 'vuex'
|
||||
import { ServerAnnouncementsState } from './types'
|
||||
import Vue from 'vue'
|
||||
|
||||
export const mutations: MutationTree<ServerAnnouncementsState> = {
|
||||
reset(state) {
|
||||
Object.assign(state, getDefaultState())
|
||||
},
|
||||
|
||||
setEntries(state, payload) {
|
||||
Vue.set(state, 'entries', payload)
|
||||
},
|
||||
|
||||
setFeeds(state, payload) {
|
||||
Vue.set(state, 'feeds', payload)
|
||||
},
|
||||
|
||||
setDismissed(state, payload) {
|
||||
const entries = [...state.entries]
|
||||
const index = entries.findIndex((entry) => entry.entry_id === payload.entry_id)
|
||||
if (index > -1) {
|
||||
entries[index].dismissed = payload.status
|
||||
if (!payload.status) {
|
||||
entries[index].date_dismissed = null
|
||||
entries[index].dismiss_wake = null
|
||||
} else entries[index].date_dismissed = new Date()
|
||||
}
|
||||
|
||||
Vue.set(state, 'entries', entries)
|
||||
},
|
||||
}
|
18
src/store/server/announcements/types.ts
Normal file
18
src/store/server/announcements/types.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export interface ServerAnnouncementsState {
|
||||
entries: ServerAnnouncementsStateEntry[]
|
||||
feeds: string[]
|
||||
}
|
||||
|
||||
export interface ServerAnnouncementsStateEntry {
|
||||
entry_id: string
|
||||
url: string
|
||||
title: string
|
||||
description: string
|
||||
priority: 'normal' | 'high'
|
||||
date: Date
|
||||
dismissed: boolean
|
||||
date_dismissed: Date | null
|
||||
dismiss_wake: Date | null
|
||||
source: string
|
||||
feed: string
|
||||
}
|
@ -159,4 +159,25 @@ export const getters: GetterTree<ServerState, any> = {
|
||||
|
||||
return interfaces
|
||||
},
|
||||
|
||||
getThrottledStateFlags: (state) => {
|
||||
let flags = state.throttled_state.flags.filter((flag: string) => flag !== '?')
|
||||
/*let flags = [
|
||||
'Under-Voltage Detected',
|
||||
'Frequency Capped',
|
||||
'Currently Throttled',
|
||||
'Temperature Limit Active',
|
||||
'Previously Under-Volted',
|
||||
'Previously Frequency Capped',
|
||||
'Previously Throttled',
|
||||
'Previously Temperature Limited',
|
||||
]*/
|
||||
|
||||
flags = flags.map((flag) => {
|
||||
flag = flag.replace(/ /g, '').replace(/-/g, '')
|
||||
return flag.charAt(0).toUpperCase() + flag.slice(1)
|
||||
})
|
||||
|
||||
return flags
|
||||
},
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import { updateManager } from '@/store/server/updateManager'
|
||||
import { history } from '@/store/server/history'
|
||||
import { timelapse } from '@/store/server/timelapse'
|
||||
import { jobQueue } from '@/store/server/jobQueue'
|
||||
import { announcements } from '@/store/server/announcements'
|
||||
|
||||
// create getDefaultState
|
||||
export const getDefaultState = (): ServerState => {
|
||||
@ -26,6 +27,7 @@ export const getDefaultState = (): ServerState => {
|
||||
events: [],
|
||||
config: {},
|
||||
system_info: null,
|
||||
system_boot_at: null,
|
||||
cpu_temp: 0,
|
||||
moonraker_stats: null,
|
||||
throttled_state: {
|
||||
@ -56,5 +58,6 @@ export const server: Module<ServerState, any> = {
|
||||
history,
|
||||
timelapse,
|
||||
jobQueue,
|
||||
announcements,
|
||||
},
|
||||
}
|
||||
|
@ -147,6 +147,10 @@ export const mutations: MutationTree<ServerState> = {
|
||||
if (payload && 'flags' in payload) Vue.set(state.throttled_state, 'flags', payload.flags)
|
||||
},
|
||||
|
||||
setSystemBootAt(state, payload) {
|
||||
Vue.set(state, 'system_boot_at', payload)
|
||||
},
|
||||
|
||||
addRootDirectory(state, payload) {
|
||||
state.registered_directories.push(payload.name)
|
||||
},
|
||||
|
@ -31,7 +31,9 @@ export interface ServerState {
|
||||
network: {
|
||||
[key: string]: ServerStateNetwork
|
||||
}
|
||||
system_uptime: number | null
|
||||
} | null
|
||||
system_boot_at: Date | null
|
||||
moonraker_stats: {
|
||||
cpu_usage: number
|
||||
mem_units: string
|
||||
|
@ -110,6 +110,18 @@ export const actions: ActionTree<SocketState, RootState> = {
|
||||
dispatch('server/jobQueue/getEvent', payload.params[0], { root: true })
|
||||
break
|
||||
|
||||
case 'notify_announcement_update':
|
||||
dispatch('server/announcements/getList', payload.params[0], { root: true })
|
||||
break
|
||||
|
||||
case 'notify_announcement_dismissed':
|
||||
dispatch('server/announcements/getDismissed', payload.params[0], { root: true })
|
||||
break
|
||||
|
||||
case 'notify_announcement_wake':
|
||||
dispatch('server/announcements/getWaked', payload.params[0], { root: true })
|
||||
break
|
||||
|
||||
default:
|
||||
if (payload.result !== 'ok' && payload.error?.message)
|
||||
window.console.error('JSON-RPC: ' + payload.error.message)
|
||||
|
@ -2,7 +2,7 @@ export const defaultLogoColor = '#D41216'
|
||||
export const defaultPrimaryColor = '#2196f3'
|
||||
|
||||
export const minKlipperVersion = 'v0.10.0-271'
|
||||
export const minMoonrakerVersion = 'v0.7.1-449'
|
||||
export const minMoonrakerVersion = 'v0.7.1-486'
|
||||
|
||||
export const colorArray = ['#F44336', '#8e379d', '#03DAC5', '#3F51B5', '#ffde03', '#009688', '#E91E63']
|
||||
|
||||
@ -24,7 +24,7 @@ export const validGcodeExtensions = ['.gcode', '.g', '.gco', '.ufp', '.nc']
|
||||
/*
|
||||
* List of initable server components
|
||||
*/
|
||||
export const initableServerComponents = ['history', 'power', 'updateManager', 'timelapse', 'jobQueue']
|
||||
export const initableServerComponents = ['history', 'power', 'updateManager', 'timelapse', 'jobQueue', 'announcements']
|
||||
|
||||
/*
|
||||
* List of required klipper config modules
|
||||
|
Loading…
x
Reference in New Issue
Block a user