Skip to content

Commit

Permalink
feat: add option to hide other Klipper & Moonraker instances (#2029)
Browse files Browse the repository at this point in the history
  • Loading branch information
meteyou authored Dec 1, 2024
1 parent a0f003c commit 34f3f08
Show file tree
Hide file tree
Showing 10 changed files with 324 additions and 146 deletions.
4 changes: 4 additions & 0 deletions src/assets/styles/utils.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
min-width: auto !important;
}

.minHeight30 {
min-height: 30px !important;
}

.minHeight36 {
min-height: 36px;
}
Expand Down
216 changes: 70 additions & 146 deletions src/components/TheTopCornerMenu.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
<style scoped>
.minheight30 {
min-height: 30px;
}
</style>

<template>
<div>
<v-menu v-model="showMenu" bottom left :offset-y="true" :close-on-content-click="false">
Expand All @@ -18,7 +12,7 @@
{{ $t('App.TopCornerMenu.KlipperControl') }}
</v-subheader>
<v-list-item
class="minheight30 pr-2"
class="minHeight30 pr-2"
link
@click="checkDialog(klipperRestart, 'klipper', 'restart')">
<v-list-item-title>{{ $t('App.TopCornerMenu.KlipperRestart') }}</v-list-item-title>
Expand All @@ -27,7 +21,7 @@
</v-list-item-action>
</v-list-item>
<v-list-item
class="minheight30 pr-2"
class="minHeight30 pr-2"
link
@click="checkDialog(klipperFirmwareRestart, 'klipper', 'firmwareRestart')">
<v-list-item-title>{{ $t('App.TopCornerMenu.KlipperFirmwareRestart') }}</v-list-item-title>
Expand All @@ -37,42 +31,15 @@
</v-list-item>
</template>
<template v-if="services.length">
<v-divider v-if="klipperState !== 'disconnected'" class="mt-0"></v-divider>
<v-divider v-if="klipperState !== 'disconnected'" class="mt-0" />
<v-subheader class="pt-2" style="height: auto">
{{ $t('App.TopCornerMenu.ServiceControl') }}
</v-subheader>
<v-list-item v-for="service in services" :key="service" class="minheight30 pr-2">
<v-list-item-title>
<v-tooltip left>
<template #activator="{ on, attrs }">
<span v-bind="attrs" v-on="on">
{{ service.charAt(0).toUpperCase() + service.slice(1) }}
</span>
</template>
<span>{{ getServiceState(service) }} ({{ getServiceSubState(service) }})</span>
</v-tooltip>
</v-list-item-title>
<v-list-item-action class="my-0 d-flex flex-row" style="min-width: auto">
<v-btn
v-if="getServiceState(service) === 'inactive'"
icon
small
@click="checkDialog(serviceStart, service, 'start')">
<v-icon small>{{ mdiPlay }}</v-icon>
</v-btn>
<v-btn v-else icon small @click="checkDialog(serviceRestart, service, 'restart')">
<v-icon small>{{ mdiRestart }}</v-icon>
</v-btn>
<v-btn
icon
small
:disabled="getServiceState(service) === 'inactive' || service === 'moonraker'"
:style="service === 'moonraker' ? 'visibility: hidden;' : ''"
@click="checkDialog(serviceStop, service, 'stop')">
<v-icon small>{{ mdiStop }}</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
<top-corner-menu-service
v-for="service in services"
:key="service"
:service="service"
@close-menu="showMenu = false" />
</template>
<template v-if="powerDevices.length">
<v-divider class="mt-0"></v-divider>
Expand All @@ -82,7 +49,7 @@
<v-list-item
v-for="(device, index) in powerDevices"
:key="index"
class="minheight30 pr-2"
class="minHeight30 pr-2"
:disabled="
device.status === 'error' ||
(device.locked_while_printing && ['printing', 'paused'].includes(printer_state))
Expand All @@ -98,13 +65,13 @@
</template>
<v-divider class="mt-0"></v-divider>
<v-subheader class="pt-2" style="height: auto">{{ $t('App.TopCornerMenu.HostControl') }}</v-subheader>
<v-list-item class="minheight30 pr-2" link @click="checkDialog(hostReboot, 'host', 'reboot')">
<v-list-item class="minHeight30 pr-2" link @click="checkDialog(hostReboot, 'host', 'reboot')">
<v-list-item-title>{{ $t('App.TopCornerMenu.Reboot') }}</v-list-item-title>
<v-list-item-action class="my-0 d-flex flex-row" style="min-width: auto">
<v-icon class="mr-2" small>{{ mdiPower }}</v-icon>
</v-list-item-action>
</v-list-item>
<v-list-item class="minheight30 pr-2" link @click="checkDialog(hostShutdown, 'host', 'shutdown')">
<v-list-item class="minHeight30 pr-2" link @click="checkDialog(hostShutdown, 'host', 'shutdown')">
<v-list-item-title>{{ $t('App.TopCornerMenu.Shutdown') }}</v-list-item-title>
<v-list-item-action class="my-0 d-flex flex-row" style="min-width: auto">
<v-icon class="mr-2" small>{{ mdiPower }}</v-icon>
Expand Down Expand Up @@ -133,35 +100,14 @@
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="dialogConfirmation.show" width="400" :fullscreen="isMobile">
<panel
card-class="confirm-top-corner-menu-dialog"
:icon="mdiAlert"
:title="dialogConfirmation.title"
:margin-bottom="false">
<template #buttons>
<v-btn icon tile @click="dialogConfirmation.show = false">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-card-text class="pt-3">
<v-row>
<v-col>
<p class="body-2">{{ dialogConfirmation.description }}</p>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="dialogConfirmation.show = false">
{{ $t('App.TopCornerMenu.Cancel') }}
</v-btn>
<v-btn text color="error" @click="executeDialog">
{{ dialogConfirmation.actionButtonText }}
</v-btn>
</v-card-actions>
</panel>
</v-dialog>
<confirmation-dialog
:show="dialogConfirmation.show"
:title="dialogConfirmation.title"
:text="dialogConfirmation.description"
:action-button-text="dialogConfirmation.actionButtonText"
:cancel-button-text="$t('App.TopCornerMenu.Cancel')"
@action="executeDialog"
@close="dialogConfirmation.show = false" />
</div>
</template>

Expand All @@ -171,17 +117,10 @@ import { Mixins } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { ServerPowerStateDevice } from '@/store/server/power/types'
import Panel from '@/components/ui/Panel.vue'
import {
mdiAlert,
mdiCloseThick,
mdiPowerStandby,
mdiRestart,
mdiPlay,
mdiPower,
mdiStop,
mdiToggleSwitch,
mdiToggleSwitchOff,
} from '@mdi/js'
import { mdiCloseThick, mdiPowerStandby, mdiRestart, mdiPower, mdiToggleSwitch, mdiToggleSwitchOff } from '@mdi/js'
import TopCornerMenuService from '@/components/ui/TopCornerMenuService.vue'
import ConfirmationDialog from '@/components/dialogs/ConfirmationDialog.vue'
import ServiceMixins from '@/components/mixins/services'
interface dialogPowerDeviceChange {
show: boolean
Expand All @@ -199,16 +138,13 @@ interface dialogConfirmation {
}
@Component({
components: { Panel },
components: { ConfirmationDialog, TopCornerMenuService, Panel },
})
export default class TheTopCornerMenu extends Mixins(BaseMixin) {
mdiAlert = mdiAlert
export default class TheTopCornerMenu extends Mixins(BaseMixin, ServiceMixins) {
mdiCloseThick = mdiCloseThick
mdiPowerStandby = mdiPowerStandby
mdiRestart = mdiRestart
mdiPlay = mdiPlay
mdiPower = mdiPower
mdiStop = mdiStop
mdiToggleSwitch = mdiToggleSwitch
mdiToggleSwitchOff = mdiToggleSwitchOff
Expand All @@ -229,64 +165,67 @@ export default class TheTopCornerMenu extends Mixins(BaseMixin) {
}
get services() {
const services =
let services =
this.$store.state.server.system_info?.available_services?.filter(
(name: string) => name !== 'klipper_mcu'
) ?? []
services.sort()
return services
}
get powerDevices() {
const devices = this.$store.getters['server/power/getDevices'] ?? []
return devices.filter((device: ServerPowerStateDevice) => !device.device.startsWith('_'))
}
get service_states() {
return this.$store.state.server.system_info?.service_state ?? {}
}
if (this.hideOtherInstances && this.klipperInstance !== '') {
services = services.filter(
(name: string) =>
(!name.toLowerCase().startsWith('klipper-') && name.toLowerCase() !== 'klipper') ||
name === this.klipperInstance
)
}
getServiceState(name: string) {
if (name in this.service_states) return this.service_states[name].active_state
if (this.hideOtherInstances && this.moonrakerInstance !== '') {
services = services.filter(
(name: string) =>
(!name.toLowerCase().startsWith('moonraker-') && name.toLowerCase() !== 'moonraker') ||
name === this.moonrakerInstance
)
}
return null
return services.sort()
}
getServiceSubState(name: string) {
if (name in this.service_states) return this.service_states[name].sub_state
get powerDevices() {
const devices = this.$store.getters['server/power/getDevices'] ?? []
return null
return devices.filter((device: ServerPowerStateDevice) => !device.device.startsWith('_'))
}
checkDialog(executableFunction: any, serviceName: string, action: string) {
if (this.printerIsPrinting) {
this.dialogConfirmation.executableFunction = executableFunction
this.dialogConfirmation.serviceName = serviceName
if (!this.printerIsPrinting) {
executableFunction(serviceName)
return
}
const actionUppercase = action.trim().charAt(0).toUpperCase() + action.trim().slice(1)
let titleKey = 'App.TopCornerMenu.ConfirmationDialog.Title.Service' + actionUppercase
let descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Service' + actionUppercase
let buttonKey = 'App.TopCornerMenu.' + actionUppercase
this.dialogConfirmation.executableFunction = executableFunction
this.dialogConfirmation.serviceName = serviceName
if (serviceName === 'klipper' && ['stop', 'restart', 'firmwareRestart'].includes(action)) {
titleKey =
'App.TopCornerMenu.ConfirmationDialog.Title.' +
(action !== 'stop' ? 'Klipper' : 'Service') +
actionUppercase
descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Klipper' + actionUppercase
const actionUppercase = action.trim().charAt(0).toUpperCase() + action.trim().slice(1)
let titleKey = 'App.TopCornerMenu.ConfirmationDialog.Title.Service' + actionUppercase
let descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Service' + actionUppercase
let buttonKey = 'App.TopCornerMenu.' + actionUppercase
if (action === 'firmwareRestart') buttonKey = 'App.TopCornerMenu.KlipperFirmwareRestart'
} else if (serviceName === 'host') {
titleKey = 'App.TopCornerMenu.ConfirmationDialog.Title.Host' + actionUppercase
descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Host' + actionUppercase
}
if (serviceName === 'klipper' && ['stop', 'restart', 'firmwareRestart'].includes(action)) {
titleKey =
'App.TopCornerMenu.ConfirmationDialog.Title.' +
(action !== 'stop' ? 'Klipper' : 'Service') +
actionUppercase
descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Klipper' + actionUppercase
if (action === 'firmwareRestart') buttonKey = 'App.TopCornerMenu.KlipperFirmwareRestart'
} else if (serviceName === 'host') {
titleKey = 'App.TopCornerMenu.ConfirmationDialog.Title.Host' + actionUppercase
descriptionKey = 'App.TopCornerMenu.ConfirmationDialog.Description.Host' + actionUppercase
}
this.dialogConfirmation.title = this.$t(titleKey).toString()
this.dialogConfirmation.description = this.$t(descriptionKey).toString()
this.dialogConfirmation.actionButtonText = this.$t(buttonKey).toString()
this.dialogConfirmation.show = true
} else executableFunction(serviceName)
this.dialogConfirmation.title = this.$t(titleKey).toString()
this.dialogConfirmation.description = this.$t(descriptionKey).toString()
this.dialogConfirmation.actionButtonText = this.$t(buttonKey).toString()
this.dialogConfirmation.show = true
}
executeDialog() {
Expand All @@ -306,21 +245,6 @@ export default class TheTopCornerMenu extends Mixins(BaseMixin) {
this.$socket.emit('printer.gcode.script', { script: 'FIRMWARE_RESTART' })
}
serviceStart(service: string) {
this.showMenu = false
this.$socket.emit('machine.services.start', { service: service })
}
serviceRestart(service: string) {
this.showMenu = false
this.$socket.emit('machine.services.restart', { service: service })
}
serviceStop(service: string) {
this.showMenu = false
this.$socket.emit('machine.services.stop', { service: service })
}
changeSwitch(device: ServerPowerStateDevice, value: string) {
this.dialogPowerDeviceChange.device = device.device
this.dialogPowerDeviceChange.value = value
Expand Down
52 changes: 52 additions & 0 deletions src/components/dialogs/ConfirmationDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<template>
<v-dialog :value="show" width="400" :fullscreen="isMobile">
<panel card-class="confirm-top-corner-menu-dialog" :icon="mdiAlert" :title="title" :margin-bottom="false">
<template #buttons>
<v-btn icon tile @click="close">
<v-icon>{{ mdiCloseThick }}</v-icon>
</v-btn>
</template>
<v-card-text class="pt-3">
<v-row>
<v-col>
<p class="body-2">{{ text }}</p>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="close">{{ cancelButtonText }}</v-btn>
<v-btn text color="error" @click="action">{{ actionButtonText }}</v-btn>
</v-card-actions>
</panel>
</v-dialog>
</template>
<script lang="ts">
import Component from 'vue-class-component'
import Panel from '@/components/ui/Panel.vue'
import { Mixins, Prop } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import { mdiAlert, mdiCloseThick } from '@mdi/js'
@Component({
components: { Panel },
})
export default class ConfirmationDialog extends Mixins(BaseMixin) {
mdiAlert = mdiAlert
mdiCloseThick = mdiCloseThick
@Prop({ type: Boolean, required: true }) show!: boolean
@Prop({ type: String, required: true }) title!: string
@Prop({ type: String, required: true }) text!: string
@Prop({ type: String, required: true }) actionButtonText!: string
@Prop({ type: String, required: true }) cancelButtonText!: string
action() {
this.$emit('action')
}
close() {
this.$emit('close')
}
}
</script>
Loading

0 comments on commit 34f3f08

Please sign in to comment.