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-connecting-dialog v-else></the-connecting-dialog>
|
||||||
<the-update-dialog></the-update-dialog>
|
<the-update-dialog></the-update-dialog>
|
||||||
<the-editor></the-editor>
|
<the-editor></the-editor>
|
||||||
<the-timelapse-rendering-snackbar>-</the-timelapse-rendering-snackbar>
|
<the-timelapse-rendering-snackbar></the-timelapse-rendering-snackbar>
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</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>
|
<v-toolbar-title class="text-no-wrap ml-0 pl-2 mr-2">{{ printerName }}</v-toolbar-title>
|
||||||
<printer-selector v-if="countPrinters"></printer-selector>
|
<printer-selector v-if="countPrinters"></printer-selector>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<the-throttled-states></the-throttled-states>
|
|
||||||
<input
|
<input
|
||||||
ref="fileUploadAndStart"
|
ref="fileUploadAndStart"
|
||||||
type="file"
|
type="file"
|
||||||
@ -95,6 +94,7 @@
|
|||||||
<v-icon class="mr-md-2">{{ mdiAlertCircleOutline }}</v-icon>
|
<v-icon class="mr-md-2">{{ mdiAlertCircleOutline }}</v-icon>
|
||||||
<span class="d-none d-md-inline">{{ $t('App.TopBar.EmergencyStop') }}</span>
|
<span class="d-none d-md-inline">{{ $t('App.TopBar.EmergencyStop') }}</span>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
|
<the-notification-menu></the-notification-menu>
|
||||||
<the-settings-menu></the-settings-menu>
|
<the-settings-menu></the-settings-menu>
|
||||||
<the-top-corner-menu></the-top-corner-menu>
|
<the-top-corner-menu></the-top-corner-menu>
|
||||||
</v-app-bar>
|
</v-app-bar>
|
||||||
@ -142,10 +142,10 @@ import axios from 'axios'
|
|||||||
import { formatFilesize } from '@/plugins/helpers'
|
import { formatFilesize } from '@/plugins/helpers'
|
||||||
import TheTopCornerMenu from '@/components/TheTopCornerMenu.vue'
|
import TheTopCornerMenu from '@/components/TheTopCornerMenu.vue'
|
||||||
import TheSettingsMenu from '@/components/TheSettingsMenu.vue'
|
import TheSettingsMenu from '@/components/TheSettingsMenu.vue'
|
||||||
import TheThrottledStates from '@/components/TheThrottledStates.vue'
|
|
||||||
import Panel from '@/components/ui/Panel.vue'
|
import Panel from '@/components/ui/Panel.vue'
|
||||||
import PrinterSelector from '@/components/ui/PrinterSelector.vue'
|
import PrinterSelector from '@/components/ui/PrinterSelector.vue'
|
||||||
import MainsailLogo from '@/components/ui/MainsailLogo.vue'
|
import MainsailLogo from '@/components/ui/MainsailLogo.vue'
|
||||||
|
import TheNotificationMenu from '@/components/notifications/TheNotificationMenu.vue'
|
||||||
import { topbarHeight } from '@/store/variables'
|
import { topbarHeight } from '@/store/variables'
|
||||||
import { mdiAlertCircleOutline, mdiContentSave, mdiFileUpload, mdiClose, mdiCloseThick } from '@mdi/js'
|
import { mdiAlertCircleOutline, mdiContentSave, mdiFileUpload, mdiClose, mdiCloseThick } from '@mdi/js'
|
||||||
|
|
||||||
@ -165,11 +165,11 @@ type uploadSnackbar = {
|
|||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
Panel,
|
Panel,
|
||||||
TheThrottledStates,
|
|
||||||
TheSettingsMenu,
|
TheSettingsMenu,
|
||||||
TheTopCornerMenu,
|
TheTopCornerMenu,
|
||||||
PrinterSelector,
|
PrinterSelector,
|
||||||
MainsailLogo,
|
MainsailLogo,
|
||||||
|
TheNotificationMenu,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class TheTopbar extends Mixins(BaseMixin) {
|
export default class TheTopbar extends Mixins(BaseMixin) {
|
||||||
|
@ -40,7 +40,8 @@ export default class BaseMixin extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get printer_state(): string {
|
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
|
const timelapse_pause = this.$store.state.printer['gcode_macro TIMELAPSE_TAKE_FRAME']?.is_paused ?? false
|
||||||
return printer_state === 'paused' && timelapse_pause ? 'printing' : printer_state
|
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>
|
<v-divider class="mb-2"></v-divider>
|
||||||
</template>
|
</template>
|
||||||
<v-card-actions class="justify-center pb-3">
|
<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>
|
<v-icon small class="mr-1">{{ mdiInformation }}</v-icon>
|
||||||
{{ $t('Panels.MinSettingsPanel.MoreInformation') }}
|
{{ $t('Panels.MinSettingsPanel.MoreInformation') }}
|
||||||
</v-btn>
|
</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>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<dependencies-panel></dependencies-panel>
|
|
||||||
<min-settings-panel></min-settings-panel>
|
<min-settings-panel></min-settings-panel>
|
||||||
<moonraker-state-panel></moonraker-state-panel>
|
|
||||||
<klippy-state-panel></klippy-state-panel>
|
<klippy-state-panel></klippy-state-panel>
|
||||||
<klipper-warnings-panel></klipper-warnings-panel>
|
|
||||||
<panel
|
<panel
|
||||||
v-if="klipperState === 'ready'"
|
v-if="klipperState === 'ready'"
|
||||||
:icon="mdiInformation"
|
:icon="mdiInformation"
|
||||||
@ -368,11 +365,8 @@ import Component from 'vue-class-component'
|
|||||||
import { Mixins, Watch } from 'vue-property-decorator'
|
import { Mixins, Watch } from 'vue-property-decorator'
|
||||||
import { thumbnailSmallMin, thumbnailSmallMax, thumbnailBigMin } from '@/store/variables'
|
import { thumbnailSmallMin, thumbnailSmallMax, thumbnailBigMin } from '@/store/variables'
|
||||||
import BaseMixin from '@/components/mixins/base'
|
import BaseMixin from '@/components/mixins/base'
|
||||||
import DependenciesPanel from '@/components/panels/DependenciesPanel.vue'
|
|
||||||
import MinSettingsPanel from '@/components/panels/MinSettingsPanel.vue'
|
import MinSettingsPanel from '@/components/panels/MinSettingsPanel.vue'
|
||||||
import MoonrakerStatePanel from '@/components/panels/MoonrakerStatePanel.vue'
|
|
||||||
import KlippyStatePanel from '@/components/panels/KlippyStatePanel.vue'
|
import KlippyStatePanel from '@/components/panels/KlippyStatePanel.vue'
|
||||||
import KlipperWarningsPanel from '@/components/panels/KlipperWarningsPanel.vue'
|
|
||||||
import StatusPanelExcludeObject from '@/components/panels/StatusPanelExcludeObject.vue'
|
import StatusPanelExcludeObject from '@/components/panels/StatusPanelExcludeObject.vue'
|
||||||
import Panel from '@/components/ui/Panel.vue'
|
import Panel from '@/components/ui/Panel.vue'
|
||||||
import {
|
import {
|
||||||
@ -391,11 +385,8 @@ import {
|
|||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
DependenciesPanel,
|
|
||||||
KlipperWarningsPanel,
|
|
||||||
KlippyStatePanel,
|
KlippyStatePanel,
|
||||||
MinSettingsPanel,
|
MinSettingsPanel,
|
||||||
MoonrakerStatePanel,
|
|
||||||
Panel,
|
Panel,
|
||||||
StatusPanelExcludeObject,
|
StatusPanelExcludeObject,
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
{
|
{
|
||||||
"App": {
|
"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": {
|
"NumberInput": {
|
||||||
"GreaterOrEqualError": "Skal være større eller lig med {min}!",
|
"GreaterOrEqualError": "Skal være større eller lig med {min}!",
|
||||||
"MustBeBetweenError": "Skal være mellem {min} og {max}!",
|
"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.",
|
"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).",
|
"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",
|
"DescriptionUnderVoltageDetected": "rPI strømforsyning i øjeblikket under 4,65V",
|
||||||
"HeadlineCurrentFlags": "\"Lige nu\" varsel",
|
|
||||||
"HeadlinePreviouslyFlags": "\"Tidligere\" varsel",
|
|
||||||
"TitleCurrentlyThrottled": "Begrænset i øjeblikket",
|
"TitleCurrentlyThrottled": "Begrænset i øjeblikket",
|
||||||
"TitleFrequencyCapped": "Maks. frekvens formindsket",
|
"TitleFrequencyCapped": "Maks. frekvens formindsket",
|
||||||
"TitlePreviouslyFrequencyCapped": "Maks. frekvens formindsket tidligere",
|
"TitlePreviouslyFrequencyCapped": "Maks. frekvens formindsket tidligere",
|
||||||
@ -438,12 +442,6 @@
|
|||||||
"SwitchToPrinter": "Skift til printer",
|
"SwitchToPrinter": "Skift til printer",
|
||||||
"WebcamOff": "Sluk"
|
"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": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "Genstart alt",
|
"FirmwareRestart": "Genstart alt",
|
||||||
"KlipperCheck": "Check at Klipper service kører og at en UDS (Unix Domain Socket) er konfigureret.",
|
"KlipperCheck": "Check at Klipper service kører og at en UDS (Unix Domain Socket) er konfigureret.",
|
||||||
@ -499,12 +497,6 @@
|
|||||||
"Empty": "Tom"
|
"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": {
|
"PowerControlPanel": {
|
||||||
"Error": "Fejl",
|
"Error": "Fejl",
|
||||||
"Off": "Sluk",
|
"Off": "Sluk",
|
||||||
|
@ -1,5 +1,28 @@
|
|||||||
{
|
{
|
||||||
"App": {
|
"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": {
|
"NumberInput": {
|
||||||
"GreaterOrEqualError": "Muss größer oder gleich {min} sein!",
|
"GreaterOrEqualError": "Muss größer oder gleich {min} sein!",
|
||||||
"MustBeBetweenError": "Muss zwischen {min} und {max} liegen!",
|
"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.",
|
"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).",
|
"DescriptionTemperatureLimitActive": "Die Temperatur des rPi uC (nur 3A+/3B+) liegt derzeit über dem Soft-Limit (Standard 60C).",
|
||||||
"DescriptionUnderVoltageDetected": "rPI-Versorgungsspannung derzeit unter 4,65V",
|
"DescriptionUnderVoltageDetected": "rPI-Versorgungsspannung derzeit unter 4,65V",
|
||||||
"HeadlineCurrentFlags": "Derzeitige flags",
|
|
||||||
"HeadlinePreviouslyFlags": "Bisherige flags",
|
|
||||||
"TitleCurrentlyThrottled": "Drosselung aktiv",
|
"TitleCurrentlyThrottled": "Drosselung aktiv",
|
||||||
"TitleFrequencyCapped": "Frequenz begrenzt",
|
"TitleFrequencyCapped": "Frequenz begrenzt",
|
||||||
"TitlePreviouslyFrequencyCapped": "Vorh. Frequenzbegrenzung registriert",
|
"TitlePreviouslyFrequencyCapped": "Vorh. Frequenzbegrenzung registriert",
|
||||||
@ -433,21 +454,11 @@
|
|||||||
"Z": "Z",
|
"Z": "Z",
|
||||||
"ZTilt": "Z Tilt"
|
"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": {
|
"FarmPrinterPanel": {
|
||||||
"ReconnectToPrinter": "Neu verbinden",
|
"ReconnectToPrinter": "Neu verbinden",
|
||||||
"SwitchToPrinter": "Zum Drucker wechseln",
|
"SwitchToPrinter": "Zum Drucker wechseln",
|
||||||
"WebcamOff": "Aus"
|
"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": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "Firmware Neustart",
|
"FirmwareRestart": "Firmware Neustart",
|
||||||
"KlipperCheck": "Bitte überprüfen Sie, ob der Klipper-Dienst läuft und ein UDS (Unix Domain Socket) konfiguriert ist.",
|
"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"
|
"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": {
|
"PowerControlPanel": {
|
||||||
"Error": "Fehler",
|
"Error": "Fehler",
|
||||||
"Off": "Aus",
|
"Off": "Aus",
|
||||||
|
@ -1,5 +1,28 @@
|
|||||||
{
|
{
|
||||||
"App": {
|
"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": {
|
"NumberInput": {
|
||||||
"GreaterOrEqualError": "Must be grater or equal than {min}!",
|
"GreaterOrEqualError": "Must be grater or equal than {min}!",
|
||||||
"MustBeBetweenError": "Must be between {min} and {max}!",
|
"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.",
|
"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).",
|
"DescriptionTemperatureLimitActive": "rPi uC (3A+/3B+ only) temperature is currently above the soft limit (default 60C).",
|
||||||
"DescriptionUnderVoltageDetected": "rPI supply voltage currently below 4.65V",
|
"DescriptionUnderVoltageDetected": "rPI supply voltage currently below 4.65V",
|
||||||
"HeadlineCurrentFlags": "Current Flags",
|
|
||||||
"HeadlinePreviouslyFlags": "Previously Flags",
|
|
||||||
"TitleCurrentlyThrottled": "Currently Throttled",
|
"TitleCurrentlyThrottled": "Currently Throttled",
|
||||||
"TitleFrequencyCapped": "Frequency Capped",
|
"TitleFrequencyCapped": "Frequency Capped",
|
||||||
"TitlePreviouslyFrequencyCapped": "Previously Frequency Capped",
|
"TitlePreviouslyFrequencyCapped": "Previously Frequency Capped",
|
||||||
@ -434,21 +455,11 @@
|
|||||||
"Z": "Z",
|
"Z": "Z",
|
||||||
"ZTilt": "Z Tilt"
|
"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": {
|
"FarmPrinterPanel": {
|
||||||
"ReconnectToPrinter": "Reconnect",
|
"ReconnectToPrinter": "Reconnect",
|
||||||
"SwitchToPrinter": "Switch to Printer",
|
"SwitchToPrinter": "Switch to Printer",
|
||||||
"WebcamOff": "Off"
|
"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": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "Firmware Restart",
|
"FirmwareRestart": "Firmware Restart",
|
||||||
"KlipperCheck": "Please check if the Klipper service is running and an UDS (Unix Domain Socket) is configured.",
|
"KlipperCheck": "Please check if the Klipper service is running and an UDS (Unix Domain Socket) is configured.",
|
||||||
@ -504,12 +515,6 @@
|
|||||||
"Empty": "Empty"
|
"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": {
|
"PowerControlPanel": {
|
||||||
"Error": "Error",
|
"Error": "Error",
|
||||||
"Off": "Off",
|
"Off": "Off",
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
{
|
{
|
||||||
"App": {
|
"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": {
|
"NumberInput": {
|
||||||
"GreaterOrEqualError": "¡Debe ser mayor o igual a {min}!",
|
"GreaterOrEqualError": "¡Debe ser mayor o igual a {min}!",
|
||||||
"MustBeBetweenError": "¡Debe estar entre {min} y {max}!",
|
"MustBeBetweenError": "¡Debe estar entre {min} y {max}!",
|
||||||
@ -420,12 +426,6 @@
|
|||||||
"SwitchToPrinter": "Cambiar a impresora",
|
"SwitchToPrinter": "Cambiar a impresora",
|
||||||
"WebcamOff": "Apagar"
|
"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": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "Reiniciar Firmware",
|
"FirmwareRestart": "Reiniciar Firmware",
|
||||||
"KlipperCheck": "Verifique que el servicio Klipper está corriendo y que un UDS (Unix Domain Socket) esta configurado.",
|
"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",
|
"_last_update:": "09.01.2022",
|
||||||
"App": {
|
"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",
|
"Printers": "Imprimantes",
|
||||||
"ThrottledStates": {
|
"ThrottledStates": {
|
||||||
"DescriptionCurrentlyThrottled": "rPi ARM core(s) sont actuellement réduits",
|
"DescriptionCurrentlyThrottled": "rPi ARM core(s) sont actuellement réduits",
|
||||||
@ -404,12 +410,6 @@
|
|||||||
"SwitchToPrinter": "Changer d'imprimante",
|
"SwitchToPrinter": "Changer d'imprimante",
|
||||||
"WebcamOff": "Arrêt"
|
"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": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "Redémarrage Firmware",
|
"FirmwareRestart": "Redémarrage Firmware",
|
||||||
"KlipperCheck": "Contrôlez que le sevice Klipper est actif et qu'un UDS (Unix Domain Socket) est configuré",
|
"KlipperCheck": "Contrôlez que le sevice Klipper est actif et qu'un UDS (Unix Domain Socket) est configuré",
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"App": {
|
"App": {
|
||||||
"Printers": "Nyomtatók",
|
"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": {
|
"ThrottledStates": {
|
||||||
"DescriptionCurrentlyThrottled": "Az rPi ARM mag(ok) jelenleg túlterheltek.",
|
"DescriptionCurrentlyThrottled": "Az rPi ARM mag(ok) jelenleg túlterheltek.",
|
||||||
"DescriptionFrequencyCapped": "Az rPi ARM max frekvenciája jelenleg 1,2 GHz -re korlátozódik.",
|
"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",
|
"SwitchToPrinter": "Váltás a nyomtatóra",
|
||||||
"WebcamOff": "Ki"
|
"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": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "Firmware újraindítása",
|
"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).",
|
"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": {
|
"App": {
|
||||||
"Printers": "Stampanti",
|
"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": {
|
"ThrottledStates": {
|
||||||
"DescriptionCurrentlyThrottled": "Uno o più core ARM dell'rPI sono attualmente rallentati.",
|
"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.",
|
"DescriptionFrequencyCapped": "La frequenza massima del processore ARM dell'rPI è attualmente limitata a 1,2 GHz.",
|
||||||
@ -403,12 +409,6 @@
|
|||||||
"SwitchToPrinter": "Passa alla Stampante",
|
"SwitchToPrinter": "Passa alla Stampante",
|
||||||
"WebcamOff": "Spenta"
|
"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": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "Riavvio Firmware",
|
"FirmwareRestart": "Riavvio Firmware",
|
||||||
"KlipperCheck": "Controlla se il servizio Klipper è in esecuzione e se è configurato un UDS (Unix Domain Socket).",
|
"KlipperCheck": "Controlla se il servizio Klipper è in esecuzione e se è configurato un UDS (Unix Domain Socket).",
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"App": {
|
"App": {
|
||||||
"Printers": "Printers",
|
"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": {
|
"ThrottledStates": {
|
||||||
"DescriptionCurrentlyThrottled": "rPi ARM core(s) worden momenteel gethrottled.",
|
"DescriptionCurrentlyThrottled": "rPi ARM core(s) worden momenteel gethrottled.",
|
||||||
"DescriptionFrequencyCapped": "rPi ARM max frequency is momenteel beperkt tot 1.2 GHz.",
|
"DescriptionFrequencyCapped": "rPi ARM max frequency is momenteel beperkt tot 1.2 GHz.",
|
||||||
@ -406,12 +412,6 @@
|
|||||||
"SwitchToPrinter": "Wissel naar Printer",
|
"SwitchToPrinter": "Wissel naar Printer",
|
||||||
"WebcamOff": "Uit"
|
"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": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "Firmware Herstart",
|
"FirmwareRestart": "Firmware Herstart",
|
||||||
"KlipperCheck": "Controleer of de Klipper service draait en een UDS (Unix Domain Socket) geconfigureerd is.",
|
"KlipperCheck": "Controleer of de Klipper service draait en een UDS (Unix Domain Socket) geconfigureerd is.",
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"App": {
|
"App": {
|
||||||
"Printers": "Drukarki",
|
"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": {
|
"ThrottledStates": {
|
||||||
"DescriptionCurrentlyThrottled": "Częstotliwość rdzeni rPi jest aktualnie obniżona.",
|
"DescriptionCurrentlyThrottled": "Częstotliwość rdzeni rPi jest aktualnie obniżona.",
|
||||||
"DescriptionFrequencyCapped": "Taktowanie rPi ograniczone do 1.2 GHz.",
|
"DescriptionFrequencyCapped": "Taktowanie rPi ograniczone do 1.2 GHz.",
|
||||||
@ -403,12 +409,6 @@
|
|||||||
"SwitchToPrinter": "Przełącz do drukarki",
|
"SwitchToPrinter": "Przełącz do drukarki",
|
||||||
"WebcamOff": "Wyłącz"
|
"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": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "Ponowne uruchomienie oprogramowania",
|
"FirmwareRestart": "Ponowne uruchomienie oprogramowania",
|
||||||
"KlipperCheck": "Sprawdź , czy Klipper jest uruchomiony oraz czy UDS (Unix Domain Socket) został skonfigurowany poprawnie.",
|
"KlipperCheck": "Sprawdź , czy Klipper jest uruchomiony oraz czy UDS (Unix Domain Socket) został skonfigurowany poprawnie.",
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"App": {
|
"App": {
|
||||||
"Printers": "Принтер",
|
"Printers": "Принтер",
|
||||||
|
"Notifications": {
|
||||||
|
"KlipperWarnings": {
|
||||||
|
"DeprecatedOption": "Опция '{option}' в разделе '{section}' устарела и будет удалена в будущем выпуске.",
|
||||||
|
"DeprecatedValue": "Значение '{value}' в опции '{option}' в секции '{section}' устарело и будет удалено в будущем релизе."
|
||||||
|
}
|
||||||
|
},
|
||||||
"ThrottledStates": {
|
"ThrottledStates": {
|
||||||
"DescriptionCurrentlyThrottled": "ARM-ядро(ядра) rPi в настоящее время дросселируется.",
|
"DescriptionCurrentlyThrottled": "ARM-ядро(ядра) rPi в настоящее время дросселируется.",
|
||||||
"DescriptionFrequencyCapped": "Максимальная частота rPi ARM в настоящее время ограничена 1,2 ГГц.",
|
"DescriptionFrequencyCapped": "Максимальная частота rPi ARM в настоящее время ограничена 1,2 ГГц.",
|
||||||
@ -406,12 +412,6 @@
|
|||||||
"SwitchToPrinter": "Переключение на принтер",
|
"SwitchToPrinter": "Переключение на принтер",
|
||||||
"WebcamOff": "Офф"
|
"WebcamOff": "Офф"
|
||||||
},
|
},
|
||||||
"KlipperWarningsPanel": {
|
|
||||||
"DeprecatedOption": "Опция '{option}' в разделе '{section}' устарела и будет удалена в будущем выпуске.",
|
|
||||||
"DeprecatedValue": "Значение '{value}' в опции '{option}' в секции '{section}' устарело и будет удалено в будущем релизе.",
|
|
||||||
"DownloadLog": "Загрузить файл журнала",
|
|
||||||
"KlipperWarnings": "Предупреждения о клиперах"
|
|
||||||
},
|
|
||||||
"KlippyStatePanel": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "Перезапуск прошивки",
|
"FirmwareRestart": "Перезапуск прошивки",
|
||||||
"KlipperCheck": "Проверьте, запущена ли служба Klipper и настроен ли UDS (Unix Domain Socket).",
|
"KlipperCheck": "Проверьте, запущена ли служба Klipper и настроен ли UDS (Unix Domain Socket).",
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"App": {
|
"App": {
|
||||||
"Printers": "列印機組",
|
"Printers": "列印機組",
|
||||||
|
"Notifications": {
|
||||||
|
"KlipperWarnings": {
|
||||||
|
"DeprecatedOption": "{section}' 中的部分選項 '{option}' 已棄用,將在未來版本中刪除。",
|
||||||
|
"DeprecatedValue": "不推薦使用'{section}' 部分的選項'{option}' 中的值'{value}',並將在未來版本中刪除。"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ThrottledStates": {
|
"ThrottledStates": {
|
||||||
"DescriptionCurrentlyThrottled": "樹莓派ARM核心目前已被限制.",
|
"DescriptionCurrentlyThrottled": "樹莓派ARM核心目前已被限制.",
|
||||||
"DescriptionFrequencyCapped": "樹莓派ARM最大頻率目前限制為1.2 GHz.",
|
"DescriptionFrequencyCapped": "樹莓派ARM最大頻率目前限制為1.2 GHz.",
|
||||||
@ -351,12 +357,6 @@
|
|||||||
"SwitchToPrinter": "切換到列印機",
|
"SwitchToPrinter": "切換到列印機",
|
||||||
"WebcamOff": "關閉"
|
"WebcamOff": "關閉"
|
||||||
},
|
},
|
||||||
"KlipperWarningsPanel": {
|
|
||||||
"DeprecatedOption": "{section}' 中的部分選項 '{option}' 已棄用,將在未來版本中刪除。",
|
|
||||||
"DeprecatedValue": "不推薦使用'{section}' 部分的選項'{option}' 中的值'{value}',並將在未來版本中刪除。",
|
|
||||||
"DownloadLog": "下載日誌",
|
|
||||||
"KlipperWarnings": "Klipper 警告"
|
|
||||||
},
|
|
||||||
"KlippyStatePanel": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "韌體重新啟動",
|
"FirmwareRestart": "韌體重新啟動",
|
||||||
"KlipperCheck": "請檢查Klipper服務是否運行並且已經設定UDS(Unix Domain Socket)。",
|
"KlipperCheck": "請檢查Klipper服務是否運行並且已經設定UDS(Unix Domain Socket)。",
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
{
|
{
|
||||||
"App": {
|
"App": {
|
||||||
"Printers": "打印机组",
|
"Printers": "打印机组",
|
||||||
|
"Notifications": {
|
||||||
|
"KlipperWarnings": {
|
||||||
|
"DeprecatedOption": "选项 '{option}' 在章节 '{section}' 已经弃用,在未来版本会被移除.",
|
||||||
|
"DeprecatedValue": "数值 '{value}' 在选项 '{option}' 中的章节'{section}' 已经弃用,在未来版本会被移除."
|
||||||
|
}
|
||||||
|
},
|
||||||
"ThrottledStates": {
|
"ThrottledStates": {
|
||||||
"DescriptionCurrentlyThrottled": "rPi ARM 核心当前被限制.",
|
"DescriptionCurrentlyThrottled": "rPi ARM 核心当前被限制.",
|
||||||
"DescriptionFrequencyCapped": "rPi ARM 最高频率限制再 1.2 GHz.",
|
"DescriptionFrequencyCapped": "rPi ARM 最高频率限制再 1.2 GHz.",
|
||||||
@ -403,12 +409,6 @@
|
|||||||
"SwitchToPrinter": "切换到打印机",
|
"SwitchToPrinter": "切换到打印机",
|
||||||
"WebcamOff": "关闭"
|
"WebcamOff": "关闭"
|
||||||
},
|
},
|
||||||
"KlipperWarningsPanel": {
|
|
||||||
"DeprecatedOption": "选项 '{option}' 在章节 '{section}' 已经弃用,在未来版本会被移除.",
|
|
||||||
"DeprecatedValue": "数值 '{value}' 在选项 '{option}' 中的章节'{section}' 已经弃用,在未来版本会被移除.",
|
|
||||||
"DownloadLog": "下载记录",
|
|
||||||
"KlipperWarnings": "Klipper 警告"
|
|
||||||
},
|
|
||||||
"KlippyStatePanel": {
|
"KlippyStatePanel": {
|
||||||
"FirmwareRestart": "Firmware 重启",
|
"FirmwareRestart": "Firmware 重启",
|
||||||
"KlipperCheck": "请检查Klipper服务是否运行并且已经设置UDS(Unix Domain Socket).",
|
"KlipperCheck": "请检查Klipper服务是否运行并且已经设置UDS(Unix Domain Socket).",
|
||||||
|
@ -30,7 +30,7 @@ Vue.use(VueMeta)
|
|||||||
import VueLoadImage from 'vue-load-image'
|
import VueLoadImage from 'vue-load-image'
|
||||||
Vue.component('VueLoadImage', VueLoadImage)
|
Vue.component('VueLoadImage', VueLoadImage)
|
||||||
|
|
||||||
//vue-toast-notification
|
//vue-toast-notifications
|
||||||
import VueToast from 'vue-toast-notification'
|
import VueToast from 'vue-toast-notification'
|
||||||
import 'vue-toast-notification/dist/theme-sugar.css'
|
import 'vue-toast-notification/dist/theme-sugar.css'
|
||||||
import { WebSocketPlugin } from '@/plugins/webSocketClient'
|
import { WebSocketPlugin } from '@/plugins/webSocketClient'
|
||||||
|
@ -91,7 +91,6 @@ import MacrosPanel from '@/components/panels/MacrosPanel.vue'
|
|||||||
import MiniconsolePanel from '@/components/panels/MiniconsolePanel.vue'
|
import MiniconsolePanel from '@/components/panels/MiniconsolePanel.vue'
|
||||||
import MinSettingsPanel from '@/components/panels/MinSettingsPanel.vue'
|
import MinSettingsPanel from '@/components/panels/MinSettingsPanel.vue'
|
||||||
import MiscellaneousPanel from '@/components/panels/MiscellaneousPanel.vue'
|
import MiscellaneousPanel from '@/components/panels/MiscellaneousPanel.vue'
|
||||||
import MoonrakerStatePanel from '@/components/panels/MoonrakerStatePanel.vue'
|
|
||||||
import PrintsettingsPanel from '@/components/panels/PrintsettingsPanel.vue'
|
import PrintsettingsPanel from '@/components/panels/PrintsettingsPanel.vue'
|
||||||
import StatusPanel from '@/components/panels/StatusPanel.vue'
|
import StatusPanel from '@/components/panels/StatusPanel.vue'
|
||||||
import ToolsPanel from '@/components/panels/ToolsPanel.vue'
|
import ToolsPanel from '@/components/panels/ToolsPanel.vue'
|
||||||
@ -109,7 +108,6 @@ import kebabCase from 'lodash.kebabcase'
|
|||||||
MiniconsolePanel,
|
MiniconsolePanel,
|
||||||
MinSettingsPanel,
|
MinSettingsPanel,
|
||||||
MiscellaneousPanel,
|
MiscellaneousPanel,
|
||||||
MoonrakerStatePanel,
|
|
||||||
PrintsettingsPanel,
|
PrintsettingsPanel,
|
||||||
StatusPanel,
|
StatusPanel,
|
||||||
ToolsPanel,
|
ToolsPanel,
|
||||||
|
@ -411,4 +411,8 @@ export const actions: ActionTree<GuiState, RootState> = {
|
|||||||
dispatch('saveSetting', { name: `gcodeViewer.klipperCache.${key}`, value })
|
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 { presets } from '@/store/gui/presets'
|
||||||
import { remoteprinters } from '@/store/gui/remoteprinters'
|
import { remoteprinters } from '@/store/gui/remoteprinters'
|
||||||
import { webcams } from '@/store/gui/webcams'
|
import { webcams } from '@/store/gui/webcams'
|
||||||
|
import { notifications } from '@/store/gui/notifications'
|
||||||
|
|
||||||
export const getDefaultState = (): GuiState => {
|
export const getDefaultState = (): GuiState => {
|
||||||
return {
|
return {
|
||||||
@ -234,6 +235,7 @@ export const gui: Module<GuiState, any> = {
|
|||||||
console,
|
console,
|
||||||
gcodehistory,
|
gcodehistory,
|
||||||
macros,
|
macros,
|
||||||
|
notifications,
|
||||||
presets,
|
presets,
|
||||||
remoteprinters,
|
remoteprinters,
|
||||||
webcams,
|
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 { GuiPresetsState } from '@/store/gui/presets/types'
|
||||||
import { GuiRemoteprintersState } from '@/store/gui/remoteprinters/types'
|
import { GuiRemoteprintersState } from '@/store/gui/remoteprinters/types'
|
||||||
import { ServerHistoryStateJob } from '@/store/server/history/types'
|
import { ServerHistoryStateJob } from '@/store/server/history/types'
|
||||||
|
import { GuiNotificationState } from '@/store/gui/notifications/types'
|
||||||
|
|
||||||
export interface GuiState {
|
export interface GuiState {
|
||||||
general: {
|
general: {
|
||||||
@ -79,6 +80,7 @@ export interface GuiState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
macros?: GuiMacrosState
|
macros?: GuiMacrosState
|
||||||
|
notifications?: GuiNotificationState
|
||||||
presets?: GuiPresetsState
|
presets?: GuiPresetsState
|
||||||
remoteprinters?: GuiRemoteprintersState
|
remoteprinters?: GuiRemoteprintersState
|
||||||
uiSettings: {
|
uiSettings: {
|
||||||
|
@ -285,9 +285,12 @@ export const getters: GetterTree<PrinterState, RootState> = {
|
|||||||
max_power: undefined,
|
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 ('off_below' in settings) tmp.off_below = settings?.off_below ?? 0
|
||||||
|
|
||||||
if ('max_power' in settings) tmp.max_power = settings?.max_power ?? 1
|
if ('max_power' in settings) tmp.max_power = settings?.max_power ?? 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,3 +171,11 @@ export interface PrinterStateMcu {
|
|||||||
measured_max_temp: number | null
|
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) {
|
initProcStats({ commit }, payload) {
|
||||||
if (payload.throttled_state !== null) commit('setThrottledState', payload.throttled_state)
|
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) {
|
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
|
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 { history } from '@/store/server/history'
|
||||||
import { timelapse } from '@/store/server/timelapse'
|
import { timelapse } from '@/store/server/timelapse'
|
||||||
import { jobQueue } from '@/store/server/jobQueue'
|
import { jobQueue } from '@/store/server/jobQueue'
|
||||||
|
import { announcements } from '@/store/server/announcements'
|
||||||
|
|
||||||
// create getDefaultState
|
// create getDefaultState
|
||||||
export const getDefaultState = (): ServerState => {
|
export const getDefaultState = (): ServerState => {
|
||||||
@ -26,6 +27,7 @@ export const getDefaultState = (): ServerState => {
|
|||||||
events: [],
|
events: [],
|
||||||
config: {},
|
config: {},
|
||||||
system_info: null,
|
system_info: null,
|
||||||
|
system_boot_at: null,
|
||||||
cpu_temp: 0,
|
cpu_temp: 0,
|
||||||
moonraker_stats: null,
|
moonraker_stats: null,
|
||||||
throttled_state: {
|
throttled_state: {
|
||||||
@ -56,5 +58,6 @@ export const server: Module<ServerState, any> = {
|
|||||||
history,
|
history,
|
||||||
timelapse,
|
timelapse,
|
||||||
jobQueue,
|
jobQueue,
|
||||||
|
announcements,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -147,6 +147,10 @@ export const mutations: MutationTree<ServerState> = {
|
|||||||
if (payload && 'flags' in payload) Vue.set(state.throttled_state, 'flags', payload.flags)
|
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) {
|
addRootDirectory(state, payload) {
|
||||||
state.registered_directories.push(payload.name)
|
state.registered_directories.push(payload.name)
|
||||||
},
|
},
|
||||||
|
@ -31,7 +31,9 @@ export interface ServerState {
|
|||||||
network: {
|
network: {
|
||||||
[key: string]: ServerStateNetwork
|
[key: string]: ServerStateNetwork
|
||||||
}
|
}
|
||||||
|
system_uptime: number | null
|
||||||
} | null
|
} | null
|
||||||
|
system_boot_at: Date | null
|
||||||
moonraker_stats: {
|
moonraker_stats: {
|
||||||
cpu_usage: number
|
cpu_usage: number
|
||||||
mem_units: string
|
mem_units: string
|
||||||
|
@ -110,6 +110,18 @@ export const actions: ActionTree<SocketState, RootState> = {
|
|||||||
dispatch('server/jobQueue/getEvent', payload.params[0], { root: true })
|
dispatch('server/jobQueue/getEvent', payload.params[0], { root: true })
|
||||||
break
|
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:
|
default:
|
||||||
if (payload.result !== 'ok' && payload.error?.message)
|
if (payload.result !== 'ok' && payload.error?.message)
|
||||||
window.console.error('JSON-RPC: ' + 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 defaultPrimaryColor = '#2196f3'
|
||||||
|
|
||||||
export const minKlipperVersion = 'v0.10.0-271'
|
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']
|
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
|
* 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
|
* List of required klipper config modules
|
||||||
|
Loading…
x
Reference in New Issue
Block a user