feat(history): add support for Moonraker sensor history_fields (#1884)
This commit is contained in:
@@ -1,170 +0,0 @@
|
||||
<template>
|
||||
<v-dialog :value="show" :max-width="600" persistent @keydown.esc="close">
|
||||
<panel
|
||||
:title="$t('History.JobDetails')"
|
||||
:icon="mdiUpdate"
|
||||
card-class="history-detail-dialog"
|
||||
:margin-bottom="false">
|
||||
<template #buttons>
|
||||
<v-btn icon tile @click="close">
|
||||
<v-icon>{{ mdiCloseThick }}</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card-text class="px-0">
|
||||
<overlay-scrollbars style="height: 350px" class="px-6">
|
||||
<history-details-dialog-entry v-for="field in fields" :key="field.key" :job="job" :field="field" />
|
||||
</overlay-scrollbars>
|
||||
</v-card-text>
|
||||
</panel>
|
||||
</v-dialog>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Mixins, Prop } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import { ServerHistoryStateJob } from '@/store/server/history/types'
|
||||
import { mdiCloseThick, mdiUpdate } from '@mdi/js'
|
||||
import { formatFilesize, formatPrintTime } from '@/plugins/helpers'
|
||||
import { TranslateResult } from 'vue-i18n'
|
||||
|
||||
export interface HistoryDetailsField {
|
||||
key: string
|
||||
label: string | TranslateResult
|
||||
metadata?: boolean
|
||||
unit?: string
|
||||
format?: (value: any) => string | TranslateResult
|
||||
}
|
||||
|
||||
@Component
|
||||
export default class HistoryDetailsDialog extends Mixins(BaseMixin) {
|
||||
mdiCloseThick = mdiCloseThick
|
||||
mdiUpdate = mdiUpdate
|
||||
|
||||
@Prop({ type: Boolean, required: true }) show!: boolean
|
||||
@Prop({ type: Object, required: true }) job!: ServerHistoryStateJob
|
||||
|
||||
get fields(): HistoryDetailsField[] {
|
||||
return [
|
||||
{
|
||||
key: 'filename',
|
||||
label: this.$t('History.Filename'),
|
||||
},
|
||||
{
|
||||
key: 'size',
|
||||
label: this.$t('History.Filesize'),
|
||||
metadata: true,
|
||||
format: (value: number) => formatFilesize(value),
|
||||
},
|
||||
{
|
||||
key: 'modified',
|
||||
label: this.$t('History.LastModified'),
|
||||
metadata: true,
|
||||
format: (value: number) => this.formatDateTime(value * 1000),
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
label: this.$t('History.Status'),
|
||||
format: (value: string) =>
|
||||
this.$te(`History.StatusValues.${value}`, 'en') ? this.$t(`History.StatusValues.${value}`) : value,
|
||||
},
|
||||
{
|
||||
key: 'end_time',
|
||||
label: this.$t('History.EndTime'),
|
||||
format: (value: number) => this.formatDateTime(value * 1000),
|
||||
},
|
||||
{
|
||||
key: 'estimated_time',
|
||||
label: this.$t('History.EstimatedTime'),
|
||||
metadata: true,
|
||||
format: (value: number) => formatPrintTime(value),
|
||||
},
|
||||
{
|
||||
key: 'print_duration',
|
||||
label: this.$t('History.PrintDuration'),
|
||||
metadata: true,
|
||||
format: (value: number) => formatPrintTime(value),
|
||||
},
|
||||
{
|
||||
key: 'total_duration',
|
||||
label: this.$t('History.TotalDuration'),
|
||||
metadata: true,
|
||||
format: (value: number) => formatPrintTime(value),
|
||||
},
|
||||
{
|
||||
key: 'filament_weight_total',
|
||||
label: this.$t('History.EstimatedFilamentWeight'),
|
||||
metadata: true,
|
||||
unit: 'g',
|
||||
format: (value: number) => value?.toFixed(2),
|
||||
},
|
||||
{
|
||||
key: 'filament_total',
|
||||
label: this.$t('History.EstimatedFilament'),
|
||||
metadata: true,
|
||||
unit: 'mm',
|
||||
format: (value: number) => value?.toFixed(0),
|
||||
},
|
||||
{
|
||||
key: 'filament_used',
|
||||
label: this.$t('History.FilamentUsed'),
|
||||
metadata: true,
|
||||
unit: 'mm',
|
||||
format: (value: number) => value?.toFixed(0),
|
||||
},
|
||||
{
|
||||
key: 'first_layer_extr_temp',
|
||||
label: this.$t('History.FirstLayerExtTemp'),
|
||||
metadata: true,
|
||||
unit: '°C',
|
||||
},
|
||||
{
|
||||
key: 'first_layer_bed_temp',
|
||||
label: this.$t('History.FirstLayerBedTemp'),
|
||||
metadata: true,
|
||||
unit: '°C',
|
||||
},
|
||||
{
|
||||
key: 'first_layer_height',
|
||||
label: this.$t('History.FirstLayerHeight'),
|
||||
metadata: true,
|
||||
unit: 'mm',
|
||||
},
|
||||
{
|
||||
key: 'layer_height',
|
||||
label: this.$t('History.LayerHeight'),
|
||||
metadata: true,
|
||||
unit: 'mm',
|
||||
},
|
||||
{
|
||||
key: 'object_height',
|
||||
label: this.$t('History.ObjectHeight'),
|
||||
metadata: true,
|
||||
unit: 'mm',
|
||||
},
|
||||
{
|
||||
key: 'slicer',
|
||||
label: this.$t('History.Slicer'),
|
||||
metadata: true,
|
||||
},
|
||||
{
|
||||
key: 'slicer_version',
|
||||
label: this.$t('History.SlicerVersion'),
|
||||
metadata: true,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
close() {
|
||||
this.$emit('close')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
::v-deep .history-details-dialog-entry + .history-details-dialog-entry {
|
||||
margin-top: 1em;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.theme--light ::v-deep .history-details-dialog-entry {
|
||||
border-top-color: rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
</style>
|
@@ -106,6 +106,22 @@ export default class HistoryListPanelDetailsDialog extends Mixins(BaseMixin) {
|
||||
},
|
||||
]
|
||||
|
||||
if ('auxiliary_data' in this.job) {
|
||||
this.job.auxiliary_data?.forEach((data) => {
|
||||
let value = data.value.toString()
|
||||
if (!Array.isArray(data.value)) {
|
||||
value = `${Math.round(data.value * 1000) / 1000} ${data.units}`
|
||||
}
|
||||
if (value === '') value = '--'
|
||||
|
||||
entries.push({
|
||||
name: data.description,
|
||||
value,
|
||||
exists: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return entries.filter((entry) => entry.exists)
|
||||
}
|
||||
|
||||
|
@@ -300,6 +300,11 @@ export default class HistoryListPanel extends Mixins(BaseMixin) {
|
||||
//@ts-ignore
|
||||
let value = col.value in item ? item[col.value] : null
|
||||
if (value === null) value = col.value in item.metadata ? item.metadata[col.value] : null
|
||||
if (col.value.startsWith('history_field_')) {
|
||||
const fieldName = col.value.replace('history_field_', '')
|
||||
const field = item.auxiliary_data?.find((field: any) => field.name === fieldName)
|
||||
if (field && !Array.isArray(field.value)) return `${Math.round(field.value * 1000) / 1000} ${field.units}`
|
||||
}
|
||||
if (value === null) return '--'
|
||||
|
||||
if (col.value === 'slicer') value += '<br />' + item.metadata.slicer_version
|
||||
|
@@ -1,226 +0,0 @@
|
||||
<template>
|
||||
<tr
|
||||
v-longpress:600="(e) => showContextMenu(e)"
|
||||
:class="trClasses"
|
||||
@contextmenu="showContextMenu($event)"
|
||||
@click="clickRow">
|
||||
<td class="pr-0">
|
||||
<v-simple-checkbox v-ripple :value="isSelected" class="pa-0 mr-0" @click.stop="select(!isSelected)" />
|
||||
</td>
|
||||
<td class="px-0 text-center" style="width: 32px">
|
||||
<v-icon v-if="!job.exists" class="text--disabled">{{ mdiFileCancel }}</v-icon>
|
||||
<v-tooltip v-else-if="smallThumbnail" top :disabled="!bigThumbnail">
|
||||
<template #activator="{ on, attrs }">
|
||||
<vue-load-image>
|
||||
<img
|
||||
slot="image"
|
||||
:src="smallThumbnail"
|
||||
width="32"
|
||||
height="32"
|
||||
:alt="job.filename"
|
||||
v-bind="attrs"
|
||||
v-on="on" />
|
||||
<div slot="preloader">
|
||||
<v-progress-circular indeterminate color="primary" />
|
||||
</div>
|
||||
<div slot="error">
|
||||
<v-icon>{{ mdiFile }}</v-icon>
|
||||
</div>
|
||||
</vue-load-image>
|
||||
</template>
|
||||
<span><img :src="bigThumbnail" width="250" :alt="job.filename" /></span>
|
||||
</v-tooltip>
|
||||
<v-icon v-else>{{ mdiFile }}</v-icon>
|
||||
</td>
|
||||
<td>{{ job.filename }}</td>
|
||||
<td class="text-right text-no-wrap">
|
||||
<template v-if="job.note ?? false">
|
||||
<v-tooltip top>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-icon small class="mr-2" v-bind="attrs" v-on="on">
|
||||
{{ mdiNotebook }}
|
||||
</v-icon>
|
||||
</template>
|
||||
<span v-html="job.note.replaceAll('\n', '<br />')" />
|
||||
</v-tooltip>
|
||||
</template>
|
||||
<v-tooltip top>
|
||||
<template #activator="{ on, attrs }">
|
||||
<span v-bind="attrs" v-on="on">
|
||||
<v-icon small :color="statusColor" :disabled="!job.exists">{{ statusIcon }}</v-icon>
|
||||
</span>
|
||||
</template>
|
||||
<span>{{ statusText }}</span>
|
||||
</v-tooltip>
|
||||
</td>
|
||||
<history-list-row-cell v-for="col in tableFields" :key="col.value" :job="job" :col="col" />
|
||||
<td v-if="existsSlicerCol" class=" ">
|
||||
{{ job.metadata?.slicer ?? '--' }}
|
||||
<small v-if="job.metadata?.slicer_version ?? false">
|
||||
<br />
|
||||
{{ job.metadata?.slicer_version }}
|
||||
</small>
|
||||
</td>
|
||||
<v-menu v-model="contextMenuShown" :position-x="contextMenuX" :position-y="contextMenuY" absolute offset-y>
|
||||
<v-list>
|
||||
<v-list-item @click="clickRow">
|
||||
<v-icon class="mr-1">{{ mdiTextBoxSearch }}</v-icon>
|
||||
{{ $t('History.Details') }}
|
||||
</v-list-item>
|
||||
<v-list-item v-if="job.note ?? false" @click="showNoteDialog = true">
|
||||
<v-icon class="mr-1">{{ mdiNotebookEdit }}</v-icon>
|
||||
{{ $t('History.EditNote') }}
|
||||
</v-list-item>
|
||||
<v-list-item v-else @click="showNoteDialog = true">
|
||||
<v-icon class="mr-1">{{ mdiNotebookPlus }}</v-icon>
|
||||
{{ $t('History.AddNote') }}
|
||||
</v-list-item>
|
||||
<v-list-item v-if="job.exists" :disabled="printerIsPrinting || !klipperReadyForGui" @click="startPrint">
|
||||
<v-icon class="mr-1">{{ mdiPrinter }}</v-icon>
|
||||
{{ $t('History.Reprint') }}
|
||||
</v-list-item>
|
||||
<v-list-item class="red--text" @click="showDeleteDialog = true">
|
||||
<v-icon class="mr-1" color="error">{{ mdiDelete }}</v-icon>
|
||||
{{ $t('History.Delete') }}
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<history-details-dialog :show="showDetailsDialog" :job="job" @close="showDetailsDialog = false" />
|
||||
<history-note-dialog :show="showNoteDialog" :job="job" @close="showNoteDialog = false" />
|
||||
<history-delete-job-dialog :show="showDeleteDialog" :job="job" @close="showDeleteDialog = false" />
|
||||
</tr>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Mixins, Prop } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import { ServerHistoryStateJob } from '@/store/server/history/types'
|
||||
import { HistoryListPanelCol } from '@/components/panels/HistoryListPanel.vue'
|
||||
import {
|
||||
mdiDelete,
|
||||
mdiFile,
|
||||
mdiFileCancel,
|
||||
mdiNotebook,
|
||||
mdiNotebookEdit,
|
||||
mdiNotebookPlus,
|
||||
mdiPrinter,
|
||||
mdiTextBoxSearch,
|
||||
} from '@mdi/js'
|
||||
import { thumbnailBigMin, thumbnailSmallMax, thumbnailSmallMin } from '@/store/variables'
|
||||
import HistoryListRowCell from '@/components/panels/History/HistoryListRowCell.vue'
|
||||
import HistoryDetailsDialog from '@/components/dialogs/HistoryDetailsDialog.vue'
|
||||
|
||||
@Component({
|
||||
components: { HistoryDetailsDialog, HistoryListRowCell },
|
||||
})
|
||||
export default class HistoryListRow extends Mixins(BaseMixin) {
|
||||
mdiDelete = mdiDelete
|
||||
mdiFile = mdiFile
|
||||
mdiFileCancel = mdiFileCancel
|
||||
mdiNotebook = mdiNotebook
|
||||
mdiNotebookEdit = mdiNotebookEdit
|
||||
mdiNotebookPlus = mdiNotebookPlus
|
||||
mdiPrinter = mdiPrinter
|
||||
mdiTextBoxSearch = mdiTextBoxSearch
|
||||
|
||||
@Prop({ type: Object, required: true }) job!: ServerHistoryStateJob
|
||||
@Prop({ type: Array, required: true }) tableFields!: HistoryListPanelCol[]
|
||||
@Prop({ type: Boolean, required: true }) isSelected!: boolean
|
||||
@Prop({ type: Boolean, required: true }) existsSlicerCol!: boolean
|
||||
|
||||
contextMenuShown = false
|
||||
contextMenuX = 0
|
||||
contextMenuY = 0
|
||||
|
||||
showDetailsDialog = false
|
||||
showDeleteDialog = false
|
||||
showNoteDialog = false
|
||||
|
||||
get trClasses() {
|
||||
return {
|
||||
'file-list-cursor': true,
|
||||
'user-select-none': true,
|
||||
'text--disabled': !this.job.exists,
|
||||
}
|
||||
}
|
||||
|
||||
get thumbnails() {
|
||||
return this.job.metadata?.thumbnails ?? []
|
||||
}
|
||||
|
||||
get smallThumbnail() {
|
||||
const thumbnail = this.thumbnails.find(
|
||||
(thumb: any) =>
|
||||
thumb.width >= thumbnailSmallMin &&
|
||||
thumb.width <= thumbnailSmallMax &&
|
||||
thumb.height >= thumbnailSmallMin &&
|
||||
thumb.height <= thumbnailSmallMax
|
||||
)
|
||||
if (!thumbnail) return false
|
||||
|
||||
let relative_url = ''
|
||||
if (this.job.filename.lastIndexOf('/') !== -1) {
|
||||
relative_url = this.job.filename.substring(0, this.job.filename.lastIndexOf('/') + 1)
|
||||
}
|
||||
|
||||
return `${this.apiUrl}/server/files/gcodes/${encodeURI(relative_url + thumbnail.relative_path)}?timestamp=${this
|
||||
.job.metadata?.modified}`
|
||||
}
|
||||
|
||||
get bigThumbnail() {
|
||||
const thumbnail = this.thumbnails.find((thumb: any) => thumb.width >= thumbnailBigMin)
|
||||
if (!thumbnail) return false
|
||||
|
||||
let relative_url = ''
|
||||
if (this.job.filename.lastIndexOf('/') !== -1) {
|
||||
relative_url = this.job.filename.substring(0, this.job.filename.lastIndexOf('/') + 1)
|
||||
}
|
||||
|
||||
return `${this.apiUrl}/server/files/gcodes/${encodeURI(relative_url + thumbnail.relative_path)}?timestamp=${this
|
||||
.job.metadata?.modified}`
|
||||
}
|
||||
|
||||
get statusColor() {
|
||||
return this.$store.getters['server/history/getPrintStatusIconColor'](this.job.status)
|
||||
}
|
||||
|
||||
get statusIcon() {
|
||||
return this.$store.getters['server/history/getPrintStatusIcon'](this.job.status)
|
||||
}
|
||||
|
||||
get statusText() {
|
||||
// If the translation exists, use it
|
||||
if (this.$te(`History.StatusValues.${this.job.status}`, 'en')) {
|
||||
return this.$t(`History.StatusValues.${this.job.status}`)
|
||||
}
|
||||
|
||||
// fallback uses the status name
|
||||
return this.job.status.replace(/_/g, ' ')
|
||||
}
|
||||
|
||||
showContextMenu(e: MouseEvent) {
|
||||
if (!this.contextMenuShown) {
|
||||
e?.preventDefault()
|
||||
this.contextMenuX = e?.clientX || e?.pageX || window.screenX / 2
|
||||
this.contextMenuY = e?.clientY || e?.pageY || window.screenY / 2
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.contextMenuShown = true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
select(value: boolean) {
|
||||
this.$emit('select', value)
|
||||
}
|
||||
|
||||
clickRow() {
|
||||
this.showDetailsDialog = true
|
||||
}
|
||||
|
||||
startPrint() {
|
||||
if (!this.job.exists) return
|
||||
|
||||
this.$socket.emit('printer.print.start', { filename: this.job.filename }, { action: 'switchToDashboard' })
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -1,61 +0,0 @@
|
||||
<template>
|
||||
<td :class="cssClass">{{ output }}</td>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { Component, Mixins, Prop } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import { ServerHistoryStateJob } from '@/store/server/history/types'
|
||||
import { HistoryListHeadertype } from '@/components/panels/HistoryListPanel.vue'
|
||||
import { formatFilesize, formatPrintTime } from '@/plugins/helpers'
|
||||
|
||||
@Component
|
||||
export default class HistoryListRowCell extends Mixins(BaseMixin) {
|
||||
@Prop({ type: Object, required: true }) job!: ServerHistoryStateJob
|
||||
@Prop({ type: Object, required: true }) col!: HistoryListHeadertype
|
||||
|
||||
get cssClass() {
|
||||
if (this.col.outputType === 'date') return ''
|
||||
|
||||
return 'text-no-wrap'
|
||||
}
|
||||
|
||||
get value() {
|
||||
if (this.col.value in this.job) return this.job[this.col.value]
|
||||
|
||||
const metadata = this.job.metadata ?? null
|
||||
if (metadata && this.col.value in metadata) return metadata[this.col.value]
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
get output() {
|
||||
// return fallback if value is null or 0
|
||||
if (this.value === null || this.value === 0) return '--'
|
||||
|
||||
// direct output of strings or other non-numeric values
|
||||
if (typeof this.value !== 'number') return this.value
|
||||
|
||||
switch (this.col.outputType) {
|
||||
case 'filesize':
|
||||
return formatFilesize(this.value)
|
||||
|
||||
case 'date':
|
||||
return this.formatDateTime(this.value * 1000)
|
||||
|
||||
case 'time':
|
||||
return formatPrintTime(this.value)
|
||||
|
||||
case 'temp':
|
||||
return this.value?.toFixed() + ' °C'
|
||||
|
||||
case 'length':
|
||||
if (this.value > 1000) return (this.value / 1000).toFixed(2) + ' m'
|
||||
|
||||
return this.value?.toFixed(2) + ' mm'
|
||||
|
||||
default:
|
||||
return this.value
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@@ -409,6 +409,16 @@ export default class HistoryListPanel extends Mixins(BaseMixin) {
|
||||
},
|
||||
]
|
||||
|
||||
this.moonrakerSensors.forEach((sensor) => {
|
||||
headers.push({
|
||||
text: sensor.desc,
|
||||
value: sensor.name,
|
||||
align: 'left',
|
||||
configable: true,
|
||||
visible: false,
|
||||
})
|
||||
})
|
||||
|
||||
headers.forEach((header) => {
|
||||
if (header.visible && this.hideColums.includes(header.value)) {
|
||||
header.visible = false
|
||||
@@ -420,6 +430,32 @@ export default class HistoryListPanel extends Mixins(BaseMixin) {
|
||||
return headers
|
||||
}
|
||||
|
||||
get moonrakerSensors() {
|
||||
const config = this.$store.state.server.config?.config ?? {}
|
||||
const sensors = Object.keys(config).filter((key) => key.startsWith('sensor '))
|
||||
const historyFields: { desc: string; unit: string; provider: string; name: string; parameter: string }[] = []
|
||||
|
||||
sensors.forEach((configName) => {
|
||||
const sensor = config[configName] ?? {}
|
||||
|
||||
Object.keys(sensor)
|
||||
.filter((key) => key.startsWith('history_field_'))
|
||||
.forEach((key) => {
|
||||
const historyField = sensor[key]
|
||||
|
||||
historyFields.push({
|
||||
desc: historyField.desc,
|
||||
unit: historyField.units,
|
||||
provider: configName,
|
||||
parameter: historyField.parameter,
|
||||
name: key,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return historyFields
|
||||
}
|
||||
|
||||
get tableFields() {
|
||||
return this.filteredHeaders.filter(
|
||||
(col: any) => !['filename', 'status'].includes(col.value) && col.value !== ''
|
||||
@@ -544,6 +580,12 @@ export default class HistoryListPanel extends Mixins(BaseMixin) {
|
||||
row.push('status')
|
||||
|
||||
this.tableFields.forEach((col) => {
|
||||
if (col.value.startsWith('history_field_')) {
|
||||
const sensorName = col.value.replace('history_field_', '')
|
||||
row.push(sensorName)
|
||||
return
|
||||
}
|
||||
|
||||
row.push(col.value)
|
||||
})
|
||||
|
||||
@@ -612,18 +654,9 @@ export default class HistoryListPanel extends Mixins(BaseMixin) {
|
||||
row.push('job')
|
||||
row.push(job.status)
|
||||
|
||||
this.tableFields
|
||||
.filter((header) => header.value !== 'slicer')
|
||||
.forEach((col) => {
|
||||
row.push(this.outputValue(col, job, csvSeperator))
|
||||
})
|
||||
|
||||
if (this.tableFields.find((header) => header.value === 'slicer')?.visible) {
|
||||
let slicerString = 'slicer' in job.metadata && job.metadata.slicer ? job.metadata.slicer : '--'
|
||||
if ('slicer_version' in job.metadata && job.metadata.slicer_version)
|
||||
slicerString += ' ' + job.metadata.slicer_version
|
||||
row.push(slicerString)
|
||||
}
|
||||
this.tableFields.forEach((col) => {
|
||||
row.push(this.outputValue(col, job, csvSeperator))
|
||||
})
|
||||
|
||||
content.push(row)
|
||||
})
|
||||
@@ -634,7 +667,7 @@ export default class HistoryListPanel extends Mixins(BaseMixin) {
|
||||
const csvContent =
|
||||
'data:text/csv;charset=utf-8,' +
|
||||
content.map((entry) =>
|
||||
entry.map((field) => (field.indexOf(csvSeperator) === -1 ? field : `"${field}"`)).join(csvSeperator)
|
||||
entry.map((field) => (field?.indexOf(csvSeperator) === -1 ? field : `"${field}"`)).join(csvSeperator)
|
||||
).join('\n')
|
||||
|
||||
const link = document.createElement('a')
|
||||
@@ -651,6 +684,36 @@ export default class HistoryListPanel extends Mixins(BaseMixin) {
|
||||
let value = col.value in job ? job[col.value] : null
|
||||
if (value === null) value = col.value in job.metadata ? job.metadata[col.value] : null
|
||||
|
||||
if (col.value === 'slicer') {
|
||||
let slicerString = 'slicer' in job.metadata && job.metadata.slicer ? job.metadata.slicer : '--'
|
||||
if ('slicer_version' in job.metadata && job.metadata.slicer_version)
|
||||
slicerString += ' ' + job.metadata.slicer_version
|
||||
|
||||
if (csvSeperator !== null && value.includes(csvSeperator)) return '"' + slicerString + '"'
|
||||
|
||||
return slicerString
|
||||
}
|
||||
|
||||
if (col.value.startsWith('history_field_')) {
|
||||
const sensorName = col.value.replace('history_field_', '')
|
||||
const sensor = job.auxiliary_data?.find((sensor) => sensor.name === sensorName)
|
||||
|
||||
let value = sensor?.value?.toString()
|
||||
|
||||
// return value, when it is not an array
|
||||
if (sensor && !Array.isArray(sensor.value)) {
|
||||
value = sensor.value?.toLocaleString(this.browserLocale, { useGrouping: false }) ?? 0
|
||||
}
|
||||
|
||||
// return empty string, when value is null
|
||||
if (!value) return '--'
|
||||
|
||||
// escape fields with the csvSeperator in the content
|
||||
if (csvSeperator !== null && value?.includes(csvSeperator)) return `"${value}"`
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
switch (col.outputType) {
|
||||
case 'date':
|
||||
return this.formatDateTime(value * 1000)
|
||||
|
@@ -50,6 +50,15 @@ export interface ServerHistoryStateJob {
|
||||
status: string
|
||||
start_time: number
|
||||
total_duration: number
|
||||
auxiliary_data?: ServerHistoryStateJobAuxiliaryData[]
|
||||
}
|
||||
|
||||
export interface ServerHistoryStateJobAuxiliaryData {
|
||||
description: string
|
||||
name: string
|
||||
provider: string
|
||||
units: string
|
||||
value: number | number[]
|
||||
}
|
||||
|
||||
export interface HistoryListRowJob extends ServerHistoryStateJob {
|
||||
|
@@ -12,6 +12,7 @@ import { timelapse } from '@/store/server/timelapse'
|
||||
import { jobQueue } from '@/store/server/jobQueue'
|
||||
import { announcements } from '@/store/server/announcements'
|
||||
import { spoolman } from '@/store/server/spoolman'
|
||||
import { sensor } from '@/store/server/sensor'
|
||||
|
||||
// create getDefaultState
|
||||
export const getDefaultState = (): ServerState => {
|
||||
@@ -62,5 +63,6 @@ export const server: Module<ServerState, any> = {
|
||||
jobQueue,
|
||||
announcements,
|
||||
spoolman,
|
||||
sensor,
|
||||
},
|
||||
}
|
||||
|
26
src/store/server/sensor/actions.ts
Normal file
26
src/store/server/sensor/actions.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import Vue from 'vue'
|
||||
import { ActionTree } from 'vuex'
|
||||
import { ServerSensorState } from '@/store/server/sensor/types'
|
||||
import { RootState } from '@/store/types'
|
||||
|
||||
export const actions: ActionTree<ServerSensorState, RootState> = {
|
||||
reset({ commit }) {
|
||||
commit('reset')
|
||||
},
|
||||
|
||||
init() {
|
||||
Vue.$socket.emit('server.sensors.list', {}, { action: 'server/sensor/getSensors' })
|
||||
},
|
||||
|
||||
getSensors({ commit, dispatch }, payload) {
|
||||
commit('setSensors', payload.sensors)
|
||||
|
||||
dispatch('socket/removeInitModule', 'server/sensor/init', { root: true })
|
||||
},
|
||||
|
||||
updateSensors({ commit }, payload) {
|
||||
Object.keys(payload).forEach((key) => {
|
||||
commit('updateSensor', { key, value: payload[key] })
|
||||
})
|
||||
},
|
||||
}
|
5
src/store/server/sensor/getters.ts
Normal file
5
src/store/server/sensor/getters.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { GetterTree } from 'vuex'
|
||||
import { ServerSensorState } from '@/store/server/sensor/types'
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const getters: GetterTree<ServerSensorState, any> = {}
|
23
src/store/server/sensor/index.ts
Normal file
23
src/store/server/sensor/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Module } from 'vuex'
|
||||
import { ServerSensorState } from '@/store/server/sensor/types'
|
||||
import { actions } from '@/store/server/sensor/actions'
|
||||
import { mutations } from '@/store/server/sensor/mutations'
|
||||
import { getters } from '@/store/server/sensor/getters'
|
||||
|
||||
export const getDefaultState = (): ServerSensorState => {
|
||||
return {
|
||||
sensors: {},
|
||||
}
|
||||
}
|
||||
|
||||
// initial state
|
||||
const state = getDefaultState()
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const sensor: Module<ServerSensorState, any> = {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
18
src/store/server/sensor/mutations.ts
Normal file
18
src/store/server/sensor/mutations.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import Vue from 'vue'
|
||||
import { getDefaultState } from './index'
|
||||
import { MutationTree } from 'vuex'
|
||||
import { ServerSensorState } from '@/store/server/sensor/types'
|
||||
|
||||
export const mutations: MutationTree<ServerSensorState> = {
|
||||
reset(state) {
|
||||
Object.assign(state, getDefaultState())
|
||||
},
|
||||
|
||||
setSensors(state, payload) {
|
||||
Vue.set(state, 'sensors', payload)
|
||||
},
|
||||
|
||||
updateSensor(state, payload) {
|
||||
Vue.set(state.sensors, payload.key, payload.value)
|
||||
},
|
||||
}
|
14
src/store/server/sensor/types.ts
Normal file
14
src/store/server/sensor/types.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface ServerSensorState {
|
||||
sensors: {
|
||||
[key: string]: ServerSensorStateSensor
|
||||
}
|
||||
}
|
||||
|
||||
export interface ServerSensorStateSensor {
|
||||
friendly_name: string
|
||||
id: string
|
||||
type: string
|
||||
values: {
|
||||
[key: string]: number
|
||||
}
|
||||
}
|
@@ -135,6 +135,10 @@ export const actions: ActionTree<SocketState, RootState> = {
|
||||
dispatch('server/spoolman/getActiveSpoolId', payload.params[0], { root: true })
|
||||
break
|
||||
|
||||
case 'notify_sensor_update':
|
||||
dispatch('server/sensor/updateSensors', payload.params[0], { root: true })
|
||||
break
|
||||
|
||||
default:
|
||||
window.console.debug(payload)
|
||||
}
|
||||
|
@@ -35,6 +35,7 @@ export const initableServerComponents = [
|
||||
'jobQueue',
|
||||
'announcements',
|
||||
'spoolman',
|
||||
'sensor',
|
||||
]
|
||||
|
||||
/*
|
||||
|
Reference in New Issue
Block a user