feature: update manager for klipper, moonraker, mainsail & os packages

Signed-off-by: Stefan Dej <meteyou@gmail.com>
This commit is contained in:
Stefan Dej 2020-12-26 21:15:37 +01:00
parent 974b7aab28
commit 339dda6455
13 changed files with 310 additions and 39 deletions

View File

@ -9,6 +9,12 @@
#page-container {
max-width: 1400px;
}
#sidebarVersions {
position: absolute;
left: 0;
bottom: 0;
}
</style>
<template>
@ -52,6 +58,9 @@
</ul>
</li>
</ul>
<p id="sidebarVersions" class="mb-0 text-body-2 pl-3 pb-2">
v{{ getVersion }}<span class="" v-if="klipperVersion"> - {{ klipperVersion.substr(0, klipperVersion.lastIndexOf('-')) }}</span>
</p>
</v-navigation-drawer>
<v-app-bar app elevate-on-scroll>
@ -76,15 +85,19 @@
<v-card color="primary" dark >
<v-card-text class="pt-2">
Connecting...
<v-progress-linear indeterminate color="white" class="mb-0"></v-progress-linear>
<v-progress-linear indeterminate color="white" class="mb-0 mt-2"></v-progress-linear>
</v-card-text>
</v-card>
</v-dialog>
<v-footer app class="d-block">
<span>v{{ getVersion }}</span>
<span class="float-right d-none d-sm-inline" v-if="version">{{ version }}</span>
</v-footer>
<v-dialog :value="!updateStatus" persistent width="500">
<v-card color="primary" dark >
<v-card-text class="pt-2">
{{ updateMessage}}
<v-progress-linear indeterminate color="white" class="mb-0 mt-2"></v-progress-linear>
</v-card-text>
</v-card>
</v-dialog>
</v-app>
</template>
@ -118,7 +131,6 @@ export default {
...mapState({
isConnected: state => state.socket.isConnected,
hostname: state => state.printer.hostname,
version: state => state.printer.software_version,
klippy_state: state => state.server.klippy_state,
printer_state: state => state.printer.print_stats.state,
loadings: state => state.socket.loadings,
@ -130,6 +142,11 @@ export default {
boolNaviWebcam: state => state.gui.webcam.bool,
config: state => state.printer.configfile.config,
save_config_pending: state => state.printer.configfile.save_config_pending,
klipperVersion: state => state.printer.software_version,
updateStatus: state => state.server.updateManager.updateResponse.complete,
updateMessage: state => state.server.updateManager.updateResponse.message,
}),
...mapGetters([
'getTitle',

View File

@ -1,24 +1,99 @@
<template>
<div>
<v-card class="mt-6">
<v-toolbar flat dense >
<v-toolbar-title>
<span class="subheading"><v-icon left>mdi-webcam</v-icon>Update</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-item-group class="v-btn-toggle" name="controllers">
<v-btn small class="px-2 minwidth-0" color="primary" @click="btnSync" title="Pause print"><v-icon small>mdi-lifebuoy</v-icon></v-btn>
</v-item-group>
</v-toolbar>
<v-card-text class="px-0 pt-0 pb-2 content">
</v-card-text>
</v-card>
</div>
<v-card>
<v-toolbar flat dense >
<v-toolbar-title>
<span class="subheading"><v-icon left>mdi-update</v-icon>Update</span>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-tooltip top>
<template v-slot:activator="{ on, attrs }">
<v-btn small class="px-2 minwidth-0" color="primary" @click="btnSync" v-bind="attrs" v-on="on"><v-icon small>mdi-refresh</v-icon></v-btn>
</template>
<span>Check for updates</span>
</v-tooltip>
</v-toolbar>
<v-card-text class="px-0 pt-0 pb-0 content">
<v-row v-if="'version' in klipper">
<v-col class="pl-6 py-2 text-no-wrap">
<strong>Klipper</strong><br />
{{ klipper.version }}
</v-col>
<v-col class="pr-6 py-2 text-right">
<v-chip
small
label
outlined
:color="getColor(klipper)"
@click="updateKlipper"
:disabled="is_disabled(klipper)"
class="minwidth-0 mt-2 px-2 text-uppercase"
><v-icon small class="mr-1">mdi-{{ getIcon(klipper) }}</v-icon>{{ getText(klipper) }}</v-chip>
</v-col>
</v-row>
<div v-if="'version' in moonraker">
<v-divider class="mt-0 mb-0" ></v-divider>
<v-row>
<v-col class="pl-6 py-2 text-no-wrap">
<strong>Moonraker</strong><br />
{{ moonraker.version }}
</v-col>
<v-col class="pr-6 py-2 text-right">
<v-chip
small
label
outlined
:color="getColor(moonraker)"
@click="updateMoonraker"
:disabled="is_disabled(moonraker)"
class="minwidth-0 mt-2 px-2 text-uppercase"
><v-icon small class="mr-1">mdi-{{ getIcon(moonraker) }}</v-icon>{{ getText(moonraker) }}</v-chip>
</v-col>
</v-row>
</div>
<div v-if="mainsail !== false && 'version' in mainsail">
<v-divider class="mt-0 mb-0" ></v-divider>
<v-row>
<v-col class="pl-6 py-2 text-no-wrap">
<strong>Mainsail</strong><br />
{{ 'v'+package_version }}
<span v-if="!is_disabled(mainsail)"> &gt; {{ mainsail.remote_version.replace('Version ', 'v') }}</span>
</v-col>
<v-col class="pr-6 py-2 text-right">
<v-chip
small
label
outlined
:color="getColor(mainsail)"
@click="updateMainsail"
:disabled="is_disabled(mainsail)"
class="minwidth-0 mt-2 px-2 text-uppercase"
><v-icon small class="mr-1">mdi-{{ getIcon(mainsail) }}</v-icon>{{ getText(mainsail) }}</v-chip>
</v-col>
</v-row>
</div>
<v-divider class="mt-0 mb-0 border-top-2" ></v-divider>
<v-row>
<v-col class="pl-6 py-2 text-no-wrap">
<strong>System</strong><br />
OS-Packages
</v-col>
<v-col class="pr-6 py-2 text-right">
<v-chip
small
label
outlined
color="gray"
@click="updateSystem"
class="minwidth-0 mt-2 px-2 text-uppercase"
><v-icon small class="mr-1">mdi-progress-upload</v-icon>upgrade</v-chip>
</v-col>
</v-row>
</v-card-text>
</v-card>
</template>
<script>
import { mapGetters } from 'vuex'
import { mapState } from 'vuex';
export default {
components: {
@ -30,14 +105,104 @@
}
},
computed: {
...mapGetters([
...mapState({
package_version: state => state.packageVersion,
klipper: state => state.server.updateManager.klipper,
moonraker: state => state.server.updateManager.moonraker,
}),
mainsail:{
get() {
if ('name' in this.$store.state.server.updateManager.client)
return this.$store.state.server.updateManager.client
])
return false
}
}
},
methods: {
btnSync() {
this.$socket.sendObj('machine.update.status', { refresh: true }, 'server/updateManager/getStatus')
},
getColor(object) {
if (typeof object === 'object' && object !== false) {
if ('is_valid' in object && !object.is_valid) return 'red'
if ('is_dirty' in object && object.is_dirty) return 'red'
}
if ('current_hash' in object && 'remote_hash' in object && object.current_hash !== object.remote_hash) return 'primary'
if ('name' in object && object.name === "mainsail") {
let remote_version = object.remote_version.split(' ')
remote_version = (remote_version.length > 1) ? remote_version[1] : remote_version[0]
if (this.package_version !== remote_version) return 'primary'
}
return 'green'
}
return 'red'
},
getText(object) {
if (typeof object === 'object' && object !== false) {
if ('is_valid' in object && !object.is_valid) return 'invalid'
if ('is_dirty' in object && object.is_dirty) return 'dirty'
if ('current_hash' in object && 'remote_hash' in object && object.current_hash !== object.remote_hash) return 'update'
if ('name' in object && object.name === "mainsail") {
let remote_version = object.remote_version.split(' ')
remote_version = (remote_version.length > 1) ? remote_version[1] : remote_version[0]
if (this.package_version !== remote_version) return 'update'
}
return 'up-to-date'
}
return 'ERROR'
},
getIcon(object) {
if (typeof object === 'object' && object !== false) {
if ('is_valid' in object && !object.is_valid) return 'alert-circle'
if ('is_dirty' in object && object.is_dirty) return 'alert-circle'
if ('current_hash' in object && 'remote_hash' in object && object.current_hash !== object.remote_hash) return 'progress-upload'
if ('name' in object && object.name === "mainsail") {
let remote_version = object.remote_version.split(' ')
remote_version = (remote_version.length > 1) ? remote_version[1] : remote_version[0]
if (this.package_version !== remote_version) return 'progress-upload'
}
return 'check'
}
return 'ERROR'
},
is_disabled(object) {
if (typeof object === 'object' && object !== false) {
if ('current_hash' in object && 'remote_hash' in object && object.current_hash !== object.remote_hash) return false
if ('name' in object && object.name === "mainsail") {
if ('v'+this.package_version !== object.remote_version.replace('Version ', 'v')) return false
}
}
return true
},
updateKlipper() {
this.$socket.sendObj('machine.update.klipper', { })
},
updateMoonraker() {
this.$socket.sendObj('machine.update.moonraker', { })
},
updateMainsail() {
this.$socket.sendObj('machine.update.client', { })
},
updateSystem() {
this.$socket.sendObj('machine.update.system', { })
},
}
}
</script>

View File

@ -3,7 +3,6 @@
<v-row>
<v-col class="col-12 col-md-6 col-lg-4">
<settings-general-panel></settings-general-panel>
<settings-update-panel></settings-update-panel>
<settings-webcam-panel class="mt-6"></settings-webcam-panel>
</v-col>
<v-col class="col-12 col-md-6 col-lg-4">

View File

@ -21,7 +21,8 @@
<settings-runout-panel></settings-runout-panel>
</v-col>
<v-col :class="(klippy_state !== 'ready' ? 'col-md-12' : 'col-md-6')">
<settings-logfiles-panel></settings-logfiles-panel>
<settings-update-panel v-if="updateManager"></settings-update-panel>
<settings-logfiles-panel :class="updateManager ? 'mt-6' : ''"></settings-logfiles-panel>
</v-col>
</v-row>
</v-col>
@ -41,6 +42,11 @@
...mapState({
klippy_state: state => state.server.klippy_state,
}),
updateManager:{
get() {
return this.$store.state.server.plugins.includes('update_manager')
}
}
},
created() {

View File

@ -1,6 +1,6 @@
export default {
reportError(state, payload) {
window.console.log(payload)
window.console.error(payload)
},
void() {

View File

@ -3,10 +3,11 @@ import { getDefaultState } from './index'
export default {
reset(state) {
window.console.log("printer/reset");
Object.assign(state, getDefaultState())
this.dispatch('socket/clearLoadings', null, { root: true })
if (this.state.server.plugins.includes("update_manager"))
Vue.prototype.$socket.sendObj('machine.update.status', { refresh: false }, 'server/updateManager/getStatus')
},
setData(state, payload) {

View File

@ -5,27 +5,34 @@ export default {
commit('reset')
},
init({ dispatch }) {
init({ dispatch, state }) {
Vue.prototype.$socket.sendObj('server.info', {}, 'server/getInfo')
Vue.prototype.$socket.sendObj('server.gcode_store', {}, 'server/getGcodeStore')
Vue.prototype.$socket.sendObj('server.files.get_directory', { path: 'config' }, 'files/getDirectory')
Vue.prototype.$socket.sendObj('server.files.get_directory', { path: 'config_examples' }, 'files/getDirectory')
if (state.plugins.length > 0 && state.plugins.includes("update_manager"))
Vue.prototype.$socket.sendObj('machine.update.status', { refresh: false }, 'server/updateManager/getStatus')
dispatch('printer/init', null, { root: true })
},
getInfo({ commit, state }, payload) {
if (
payload.plugins.includes("power") !== false &&
state.plugins.length === 0
) Vue.prototype.$socket.sendObj('machine.device_power.devices', {}, 'server/power/getDevices');
if (state.plugins.length === 0) {
if (payload.plugins.includes("power") !== false)
Vue.prototype.$socket.sendObj('machine.device_power.devices', {}, 'server/power/getDevices')
if (payload.plugins.includes("update_manager") !== false)
Vue.prototype.$socket.sendObj('machine.update.status', {}, 'server/updateManager/getStatus')
}
commit('setData', payload)
if (!payload.klippy_connected) {
setTimeout(function(){
Vue.prototype.$socket.sendObj('server.info', {}, 'server/getInfo');
}, 1000);
Vue.prototype.$socket.sendObj('server.info', {}, 'server/getInfo')
}, 1000)
}
},

View File

@ -4,6 +4,7 @@ import getters from './getters'
// import modules
import power from './power'
import updateManager from './updateManager'
// create getDefaultState
export function getDefaultState() {
@ -26,6 +27,7 @@ export default {
actions,
mutations,
modules: {
power
power,
updateManager,
}
}

View File

@ -0,0 +1,9 @@
export default {
reset({ commit }) {
commit('reset')
},
getStatus({ commit }, payload) {
commit('setStatus', payload)
},
}

View File

@ -0,0 +1,3 @@
export default {
}

View File

@ -0,0 +1,27 @@
import actions from './actions'
import mutations from './mutations'
import getters from './getters'
export function getDefaultState() {
return {
moonraker: {},
klipper: {},
client: {},
updateResponse: {
application: "",
complete: true,
message: "",
}
}
}
// initial state
const state = getDefaultState()
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

View File

@ -0,0 +1,26 @@
import { getDefaultState } from './index'
import Vue from "vue";
export default {
reset(state) {
Object.assign(state, getDefaultState())
},
setStatus(state, payload) {
if ('version_info' in payload) {
Object.entries(payload.version_info).forEach(([key, value]) => {
Vue.set(state, key, value)
})
}
if ('busy' in payload && payload.busy === false) {
Vue.set(state.updateResponse, 'complete', true)
}
},
setUpdateResponse(state, payload) {
Vue.set(state, 'updateResponse', payload)
if (payload.application === "client" && payload.complete) window.location.reload(true)
}
}

View File

@ -70,6 +70,10 @@ export default {
commit('server/power/setStatus', payload.params[0], { root: true })
break
case 'notify_update_response':
commit('server/updateManager/setUpdateResponse', payload.params[0], { root: true })
break
default:
if (payload.result !== "ok") {
if (
@ -88,5 +92,10 @@ export default {
clearLoadings({ commit }) {
commit('clearLoadings')
}
},
reportDebug({ commit }, payload) {
window.console.log(payload)
commit('void', {}, { root: true })
},
}