feature: add farm mode
Signed-off-by: Stefan Dej <meteyou@gmail.com>
This commit is contained in:
parent
21669f11f7
commit
5831e43fba
@ -4,8 +4,9 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
"build": "vue-cli-service build && npm run build.zip",
|
||||
"lint": "vue-cli-service lint",
|
||||
"build.zip": "cd ./dist && zip -r mainsail.zip ./ && cd .."
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
|
55
src/App.vue
55
src/App.vue
@ -29,6 +29,7 @@
|
||||
<v-toolbar-title>{{ printername !== "" ? printername : hostname }}</v-toolbar-title>
|
||||
</div>
|
||||
<ul class="navi" :expand="$vuetify.breakpoint.mdAndUp">
|
||||
<printer-selecter></printer-selecter>
|
||||
<li v-for="(category, index) in routes" :key="index" :prepend-icon="category.icon"
|
||||
:class="[category.path !== '/' && currentPage.includes(category.path) ? 'active' : '', 'nav-item']"
|
||||
:value="true"
|
||||
@ -81,15 +82,8 @@
|
||||
</v-scroll-y-transition>
|
||||
</v-main>
|
||||
|
||||
<v-dialog v-model="overlayDisconnect" persistent width="300">
|
||||
<v-card color="primary" dark >
|
||||
<v-card-text class="pt-2">
|
||||
Connecting...
|
||||
<v-progress-linear indeterminate color="white" class="mb-0 mt-2"></v-progress-linear>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<select-printer-dialog v-if="remoteMode"></select-printer-dialog>
|
||||
<connecting-dialog v-if="!remoteMode"></connecting-dialog>
|
||||
<update-dialog></update-dialog>
|
||||
</v-app>
|
||||
</template>
|
||||
@ -99,21 +93,25 @@
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import TopCornerMenu from "@/components/TopCornerMenu"
|
||||
import UpdateDialog from "@/components/UpdateDialog"
|
||||
import ConnectingDialog from "@/components/ConnectingDialog";
|
||||
import SelectPrinterDialog from "@/components/SelectPrinterDialog";
|
||||
import PrinterSelecter from "@/components/PrinterSelecter"
|
||||
|
||||
export default {
|
||||
props: {
|
||||
source: String,
|
||||
},
|
||||
components: {
|
||||
PrinterSelecter,
|
||||
ConnectingDialog,
|
||||
SelectPrinterDialog,
|
||||
UpdateDialog,
|
||||
TopCornerMenu,
|
||||
|
||||
},
|
||||
data: () => ({
|
||||
overlayDisconnect: true,
|
||||
drawer: null,
|
||||
activeClass: 'active',
|
||||
routes: routes,
|
||||
routes: routes.filter((element) => element.title !== "Printers"),
|
||||
boolNaviHeightmap: false,
|
||||
}),
|
||||
created () {
|
||||
@ -140,6 +138,7 @@ export default {
|
||||
save_config_pending: state => state.printer.configfile.save_config_pending,
|
||||
|
||||
klipperVersion: state => state.printer.software_version,
|
||||
remoteMode: state => state.socket.remoteMode,
|
||||
}),
|
||||
...mapGetters([
|
||||
'getTitle',
|
||||
@ -244,9 +243,6 @@ export default {
|
||||
config() {
|
||||
this.boolNaviHeightmap = (typeof(this.config.bed_mesh) !== "undefined");
|
||||
},
|
||||
isConnected(newVal) {
|
||||
this.overlayDisconnect = !newVal;
|
||||
},
|
||||
customStylesheet(newVal) {
|
||||
if (newVal !== null) {
|
||||
let style = document.getElementById("customStylesheet")
|
||||
@ -318,7 +314,7 @@ export default {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
nav ul.navi a.nav-link {
|
||||
nav ul.navi .nav-link {
|
||||
display: block;
|
||||
color: white;
|
||||
border-radius: .5em;
|
||||
@ -332,25 +328,25 @@ export default {
|
||||
margin: 0.5em 1em;
|
||||
}
|
||||
|
||||
nav ul.navi a.nav-link:hover,
|
||||
nav ul.navi li.active>a.nav-link,
|
||||
nav ul.navi a.nav-link.router-link-active {
|
||||
nav ul.navi .nav-link:hover,
|
||||
nav ul.navi li.active>.nav-link,
|
||||
nav ul.navi .nav-link.router-link-active {
|
||||
background: rgba(255,255,255,.3);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
nav ul.navi li.active>a.nav-link i.nav-arrow ,
|
||||
nav ul.navi a.nav-link.router-link-active i.nav-arrow {
|
||||
nav ul.navi li.active>.nav-link i.nav-arrow ,
|
||||
nav ul.navi .nav-link.router-link-active i.nav-arrow {
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
nav ul.navi a.nav-link>i.v-icon {
|
||||
nav ul.navi .nav-link>i.v-icon {
|
||||
color: white;
|
||||
font-size: 1.7em;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
nav ul.navi a.nav-link>span.nav-title {
|
||||
nav ul.navi .nav-link>span.nav-title {
|
||||
line-height: 30px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
@ -358,12 +354,15 @@ export default {
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
nav ul.navi a.nav-link>i.nav-arrow {
|
||||
nav ul.navi .nav-link>.nav-arrow {
|
||||
float: right;
|
||||
margin-top: 5px;
|
||||
margin-right: 0;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
nav ul.navi .nav-link>.nav-arrow.right {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
nav ul.navi>li>ul.child {
|
||||
display: none;
|
||||
@ -377,16 +376,16 @@ export default {
|
||||
display: block;
|
||||
}
|
||||
|
||||
nav ul.navi>li>ul.child a.nav-link {
|
||||
nav ul.navi>li>ul.child .nav-link {
|
||||
padding: 5px 15px 5px 15px;
|
||||
}
|
||||
|
||||
nav ul.navi>li>ul.child a.nav-link:hover,
|
||||
nav ul.navi>li>ul.child a.nav-link.router-link-active {
|
||||
nav ul.navi>li>ul.child .nav-link:hover,
|
||||
nav ul.navi>li>ul.child .nav-link.router-link-active {
|
||||
background: rgba(255,255,255,.2);
|
||||
}
|
||||
|
||||
nav ul.navi>li>ul.child a.nav-link>span.nav-title {
|
||||
nav ul.navi>li>ul.child .nav-link>span.nav-title {
|
||||
text-transform: capitalize;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
|
86
src/components/ConnectingDialog.vue
Normal file
86
src/components/ConnectingDialog.vue
Normal file
@ -0,0 +1,86 @@
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<v-dialog v-model="showDialog" persistent :width="400">
|
||||
<v-card dark>
|
||||
<v-toolbar flat dense color="primary">
|
||||
<v-toolbar-title>
|
||||
<span class="subheading">
|
||||
<v-icon class="mdi mdi-connection" left></v-icon>Connecting<span v-if="connectingFailed"> failed</span><span v-if="isConnecting"> to {{ parseInt(port) !== 80 ? hostname+":"+port : hostname }}</span>
|
||||
</span>
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card-text class="pt-5" v-if="isConnecting">
|
||||
<v-progress-linear color="white" indeterminate></v-progress-linear>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-5" v-if="!isConnecting && connectingFailed">
|
||||
<p>Cannot not connect to {{ parseInt(port) !== 80 ? hostname+":"+port : hostname }}.</p>
|
||||
<div class="text-center">
|
||||
<v-btn @click="reconnect" color="primary">try again</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex";
|
||||
import Vue from "vue";
|
||||
|
||||
export default {
|
||||
data: function() {
|
||||
return {
|
||||
showDialog: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isConnected: state => state.socket.isConnected,
|
||||
isConnecting: state => state.socket.isConnecting,
|
||||
connectingFailed: state => state.socket.connectingFailed,
|
||||
}),
|
||||
protocol: {
|
||||
get() {
|
||||
return this.$store.state.socket.protocol
|
||||
}
|
||||
},
|
||||
hostname: {
|
||||
get() {
|
||||
return this.$store.state.socket.hostname
|
||||
},
|
||||
set(newName) {
|
||||
return this.$store.dispatch('socket/setData', { hostname: newName });
|
||||
}
|
||||
},
|
||||
port: {
|
||||
get() {
|
||||
return this.$store.state.socket.port
|
||||
},
|
||||
set(newName) {
|
||||
return this.$store.dispatch('socket/setData', { port: newName });
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
connect() {
|
||||
window.console.log("save connection")
|
||||
Vue.prototype.$socket.setUrl(this.protocol+"://"+this.hostname+":"+this.port+"/websocket")
|
||||
Vue.prototype.$socket.connect()
|
||||
},
|
||||
reconnect() {
|
||||
this.$store.dispatch('socket/setData', { connectingFailed: false })
|
||||
Vue.prototype.$socket.connect()
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.tmpHostname = this.hostname+":"+this.port
|
||||
},
|
||||
watch: {
|
||||
isConnected(newVal) {
|
||||
this.showDialog = !newVal
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
84
src/components/PrinterSelecter.vue
Normal file
84
src/components/PrinterSelecter.vue
Normal file
@ -0,0 +1,84 @@
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div v-if="(remoteMode && countPrinters > 1) || (!remoteMode && countPrinters)">
|
||||
<li :class="currentPage === '/allPrinters' ? 'nav-item active' : 'nav-item '">
|
||||
<div
|
||||
class="nav-link "
|
||||
@click.prevent
|
||||
@click="switchToPrinters"
|
||||
role="button"
|
||||
>
|
||||
<v-icon>mdi-view-dashboard-outline</v-icon>
|
||||
<span class="nav-title">Printers</span>
|
||||
|
||||
<v-menu bottom :offset-x="true">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-icon class="nav-arrow right" v-bind="attrs" v-on="on" >mdi-chevron-down</v-icon>
|
||||
</template>
|
||||
|
||||
<v-list dense>
|
||||
<v-list-item two-line v-for="printer in printers" v-bind:key="printer._namespace" @click="changePrinter(printer)" :disabled="!printer.socket.isConnected" link>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>{{ getPrinterName(printer._namespace) }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ getPrinterDescription(printer)}}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
<v-item-group class="v-btn-toggle mx-4 d-block row" name="printers" v-if="false">
|
||||
<v-btn class="col" color="primary" @click="switchToPrinters">Printers</v-btn>
|
||||
|
||||
</v-item-group>
|
||||
</li>
|
||||
<v-divider class="my-4"></v-divider>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import router from "@/plugins/router";
|
||||
|
||||
export default {
|
||||
name: "PrinterSelecter.vue",
|
||||
computed: {
|
||||
...mapState({
|
||||
remoteMode: state => state.socket.remoteMode
|
||||
}),
|
||||
countPrinters: {
|
||||
get() {
|
||||
return this.$store.getters["farm/countPrinters"]
|
||||
}
|
||||
},
|
||||
printers: {
|
||||
get() {
|
||||
return this.$store.getters["farm/getPrinters"]
|
||||
}
|
||||
},
|
||||
currentPage: function() {
|
||||
return this.$route.fullPath;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
switchToPrinters() {
|
||||
router.push("/allPrinters");
|
||||
},
|
||||
getPrinterName(namespace) {
|
||||
return this.$store.getters["farm/"+namespace+"/getPrinterName"]
|
||||
},
|
||||
getPrinterDescription(printer) {
|
||||
return this.$store.getters["farm/"+printer._namespace+"/getStatus"]
|
||||
},
|
||||
changePrinter(printer) {
|
||||
if (printer.socket.isConnected) {
|
||||
if (this.remoteMode) this.$store.dispatch('changePrinter', { printer: printer._namespace })
|
||||
else window.location.href = "//"+printer.socket.hostname+(parseInt(printer.socket.webPort) !== 80 ? ':'+printer.socket.webPort : '')
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
248
src/components/SelectPrinterDialog.vue
Normal file
248
src/components/SelectPrinterDialog.vue
Normal file
@ -0,0 +1,248 @@
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<v-dialog v-model="showDialog" persistent :width="400">
|
||||
<v-card dark>
|
||||
<v-toolbar flat dense color="primary">
|
||||
<v-toolbar-title>
|
||||
<span class="subheading">
|
||||
<v-icon class="mdi mdi-connection" left></v-icon>
|
||||
<span v-if="isConnecting">Connection to {{ parseInt(port) !== 80 ? hostname+':'+port : hostname }}</span>
|
||||
<span v-if="connectingFailed">Connection failed</span>
|
||||
<span v-if="!isConnecting && !connectingFailed && !dialogAddPrinter.bool && !dialogEditPrinter.bool">Select Printer</span>
|
||||
<span v-if="dialogAddPrinter.bool">Add Printer</span>
|
||||
<span v-if="dialogEditPrinter.bool">Edit Printer</span>
|
||||
</span>
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn v-if="!isConnecting && !connectingFailed && !dialogAddPrinter.bool && !dialogEditPrinter.bool" small class="minwidth-0" @click="checkPrinters"><v-icon small>mdi-sync</v-icon></v-btn>
|
||||
</v-toolbar>
|
||||
<v-card-text class="pt-5" v-if="isConnecting">
|
||||
<v-progress-linear color="white" indeterminate></v-progress-linear>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-5" v-if="!isConnecting && connectingFailed">
|
||||
<p>Cannot not connect to {{ parseInt(port) !== 80 ? hostname+":"+port : hostname }}.</p>
|
||||
<div class="text-center">
|
||||
<v-btn @click="switchToChangePrinter" color="white" outlined class="mr-3">change printer</v-btn>
|
||||
<v-btn @click="reconnect" color="primary">try again</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-3" v-if="!isConnecting && dialogAddPrinter.bool">
|
||||
<v-container class="px-0 py-0">
|
||||
<v-row>
|
||||
<v-col class="col-8">
|
||||
<v-text-field
|
||||
v-model="dialogAddPrinter.hostname"
|
||||
:rules="[v => !!v || 'Hostname is required']"
|
||||
label="Hostname/IP"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col class="col-4">
|
||||
<v-text-field
|
||||
v-model="dialogAddPrinter.port"
|
||||
:rules="[v => !!v || 'Port is required']"
|
||||
label="Port"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="text-right">
|
||||
<v-btn
|
||||
color="white"
|
||||
outlined
|
||||
class="middle"
|
||||
@click="addPrinter"
|
||||
>
|
||||
add printer
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-3" v-if="!isConnecting && dialogEditPrinter.bool">
|
||||
<v-container class="px-0 py-0">
|
||||
<v-row>
|
||||
<v-col class="col-8">
|
||||
<v-text-field
|
||||
v-model="dialogEditPrinter.hostname"
|
||||
:rules="[v => !!v || 'Hostname is required']"
|
||||
label="Hostname/IP"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col class="col-4">
|
||||
<v-text-field
|
||||
v-model="dialogEditPrinter.port"
|
||||
:rules="[v => !!v || 'Port is required']"
|
||||
label="Port"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="">
|
||||
<v-btn
|
||||
color="red"
|
||||
outlined
|
||||
class="middle minwidth-0"
|
||||
@click="delPrinter"
|
||||
>
|
||||
<v-icon small>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col class="text-right">
|
||||
<v-btn
|
||||
color="white"
|
||||
outlined
|
||||
class="middle"
|
||||
@click="updatePrinter"
|
||||
>
|
||||
update printer
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-3" v-if="!isConnecting && !connectingFailed && !dialogAddPrinter.bool && !dialogEditPrinter.bool">
|
||||
<v-container class="px-0 pb-0">
|
||||
<v-row v-for="(printer, index) in this['farm/getPrinters']" v-bind:key="index">
|
||||
<v-col class="rounded transition-swing secondary py-2 px-2 mb-6" style="cursor: pointer;" @click="connect(printer)">
|
||||
<v-row align="center">
|
||||
<v-col class="col-auto pr-0">
|
||||
<v-progress-circular
|
||||
indeterminate
|
||||
color="primary"
|
||||
v-if="printer.socket.isConnecting"
|
||||
></v-progress-circular>
|
||||
<v-icon
|
||||
:color="printer.socket.isConnected ? 'green' : 'red'"
|
||||
v-if="!printer.socket.isConnecting"
|
||||
>mdi-{{ printer.socket.isConnected ? 'checkbox-marked-circle' : 'cancel' }}</v-icon>
|
||||
</v-col>
|
||||
<v-col>{{ printer.socket.hostname }}{{ parseInt(printer.socket.port) !== 80 ? ":"+printer.socket.port : "" }}</v-col>
|
||||
<v-col class="col-auto"><v-btn small class="minwidth-0" v-on:click.stop.prevent="editPrinter(index)"><v-icon small>mdi-pencil</v-icon></v-btn></v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="text-center mt-0">
|
||||
<v-btn @click="dialogAddPrinter.bool = true">add printer</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters, mapActions } from "vuex";
|
||||
import Vue from "vue";
|
||||
|
||||
export default {
|
||||
data: function() {
|
||||
return {
|
||||
showDialog: true,
|
||||
dialogAddPrinter: {
|
||||
bool: false,
|
||||
hostname: "",
|
||||
port: 7125
|
||||
},
|
||||
dialogEditPrinter: {
|
||||
bool: false,
|
||||
index: 0,
|
||||
hostname: "",
|
||||
port: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
isConnected: state => state.socket.isConnected,
|
||||
isConnecting: state => state.socket.isConnecting,
|
||||
connectingFailed: state => state.socket.connectingFailed,
|
||||
remoteMode: state => state.socket.remoteMode,
|
||||
protocol: state => state.socket.protocol,
|
||||
hostname: state => state.socket.hostname,
|
||||
port: state => state.socket.port,
|
||||
}),
|
||||
...mapGetters([
|
||||
"farm/countPrinters",
|
||||
"farm/getPrinters"
|
||||
]),
|
||||
...mapActions({
|
||||
readPrinters: "farm/readStoredPrinters"
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
addPrinter() {
|
||||
this.$store.commit('farm/addPrinter',{
|
||||
hostname: this.dialogAddPrinter.hostname,
|
||||
port: this.dialogAddPrinter.port,
|
||||
protocol: this.protocol
|
||||
})
|
||||
|
||||
this.dialogAddPrinter.hostname = ""
|
||||
this.dialogAddPrinter.bool = false
|
||||
|
||||
this.$store.dispatch("farm/savePrinters")
|
||||
},
|
||||
editPrinter(index) {
|
||||
this.dialogEditPrinter.hostname = this["farm/getPrinters"][index].socket.hostname
|
||||
this.dialogEditPrinter.port = this["farm/getPrinters"][index].socket.port
|
||||
this.dialogEditPrinter.index = index
|
||||
this.dialogEditPrinter.bool = true
|
||||
},
|
||||
updatePrinter() {
|
||||
this.$store.commit("farm/"+this.dialogEditPrinter.index+"/setSocketData", {
|
||||
hostname: this.dialogEditPrinter.hostname,
|
||||
port: this.dialogEditPrinter.port,
|
||||
isConnecting: true,
|
||||
})
|
||||
this.$store.dispatch("farm/"+this.dialogEditPrinter.index+"/reconnect")
|
||||
this.dialogEditPrinter.bool = false
|
||||
|
||||
this.checkPrinters()
|
||||
},
|
||||
delPrinter() {
|
||||
this.$store.commit("farm/removePrinter", { name: this.dialogEditPrinter.index })
|
||||
this.$store.dispatch("farm/savePrinters")
|
||||
this.dialogEditPrinter.bool = false
|
||||
},
|
||||
connect(printer) {
|
||||
this.$store.dispatch('socket/setData', {
|
||||
hostname: printer.socket.hostname,
|
||||
port: printer.socket.port
|
||||
})
|
||||
Vue.prototype.$socket.setUrl(this.protocol+"://"+printer.socket.hostname+":"+printer.socket.port+"/websocket")
|
||||
Vue.prototype.$socket.connect()
|
||||
},
|
||||
reconnect() {
|
||||
this.$store.dispatch('socket/setData', { connectingFailed: false })
|
||||
Vue.prototype.$socket.connect()
|
||||
},
|
||||
switchToChangePrinter() {
|
||||
this.$store.dispatch('socket/setData', { connectingFailed: false })
|
||||
},
|
||||
checkPrinters() {
|
||||
Object.entries(this['farm/getPrinters']).forEach(([key, printer]) => {
|
||||
if (!printer.socket.isConnected && !printer.socket.isConnecting) {
|
||||
this.$store.dispatch('farm/'+key+'/connect')
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch("farm/readStoredPrinters")
|
||||
},
|
||||
watch: {
|
||||
isConnected(newVal) {
|
||||
this.showDialog = !newVal
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
@ -33,7 +33,7 @@
|
||||
<v-subheader class="pt-2" style="height: auto;">Power Devices</v-subheader>
|
||||
<v-list-item v-for="(device, index) in devices" v-bind:key="index" class="minheight30" @click="changeSwitch(device, device.status)" :disabled="(device.status === 'error')">
|
||||
<v-list-item-title>
|
||||
<v-icon class="mr-2">mdi-{{ device.status === 'on' ? 'toggle-switch' : 'toggle-switch-off' }}</v-icon>{{ device.device }}
|
||||
<v-icon class="mr-2" :color="device.status === 'on' ? '' : 'grey darken-2'">mdi-{{ device.status === 'on' ? 'toggle-switch' : 'toggle-switch-off' }}</v-icon>{{ device.device }}
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</div>
|
||||
|
@ -2,9 +2,15 @@
|
||||
import panels from './panels'
|
||||
import topCornerMenu from './TopCornerMenu'
|
||||
import updateDialog from './UpdateDialog'
|
||||
import connectingDialog from './ConnectingDialog'
|
||||
import selectPrinterDialog from './SelectPrinterDialog'
|
||||
import printerSelecter from './PrinterSelecter'
|
||||
|
||||
export default {
|
||||
panels,
|
||||
topCornerMenu,
|
||||
updateDialog
|
||||
updateDialog,
|
||||
connectingDialog,
|
||||
selectPrinterDialog,
|
||||
printerSelecter,
|
||||
}
|
||||
|
121
src/components/panels/FarmPrinterPanel.vue
Normal file
121
src/components/panels/FarmPrinterPanel.vue
Normal file
@ -0,0 +1,121 @@
|
||||
<style>
|
||||
.v-card.disabledPrinter {
|
||||
opacity: 0.6;
|
||||
filter: grayscale(70%);
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<v-card
|
||||
:class="(!printer.socket.isConnected && !printer.socket.isConnecting ? 'disabledPrinter' : '')"
|
||||
:loading="printer.socket.isConnecting"
|
||||
@click="clickPrinter"
|
||||
>
|
||||
<v-toolbar flat dense :color="isCurrentPrinter ? 'primary' : ''">
|
||||
<v-toolbar-title>
|
||||
<span class="subheading"><v-icon left>mdi-printer-3d</v-icon>{{ printer_name }}</span>
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-img
|
||||
height="200px"
|
||||
:src="printer_image"
|
||||
class="d-flex align-end"
|
||||
>
|
||||
<v-card-title class="white--text py-2" style="background-color: rgba(0,0,0,0.3); backdrop-filter: blur(3px);">
|
||||
<v-row>
|
||||
<v-col class="col-auto pr-0 d-flex align-center" style="width: 58px">
|
||||
<img class="my-auto" :src="printer_logo" style="width: 100%;" />
|
||||
</v-col>
|
||||
<v-col class="col" style="width: 100px">
|
||||
<h3 class="font-weight-regular">{{ printer_status }}</h3>
|
||||
<span class="subtitle-2 text-truncate px-0 text--disabled d-block" v-if="printer_current_filename !== ''"><v-icon small class="mr-1">mdi-file-outline</v-icon>{{ printer_current_filename }}</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-title>
|
||||
</v-img>
|
||||
<v-card-text class="px-0 py-2" v-if="printer_preview.length">
|
||||
<v-container class="py-0">
|
||||
<v-row>
|
||||
<v-col :class="object.name === 'ETA' ? 'col-auto' : 'col' + ' px-2'" v-for="object in printer_preview" v-bind:key="object.name">
|
||||
<strong class="d-block text-center">{{ object.name }}</strong>
|
||||
<span class="d-block text-center">{{ object.value }}</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
},
|
||||
props: {
|
||||
printer: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
remoteMode: state => state.socket.remoteMode
|
||||
}),
|
||||
isCurrentPrinter: {
|
||||
get() {
|
||||
return this.$store.getters["farm/"+this.printer._namespace+"/isCurrentPrinter"]
|
||||
}
|
||||
},
|
||||
printer_name: {
|
||||
get() {
|
||||
return this.$store.getters["farm/"+this.printer._namespace+"/getPrinterName"]
|
||||
}
|
||||
},
|
||||
printer_status: {
|
||||
get() {
|
||||
return this.$store.getters["farm/"+this.printer._namespace+"/getStatus"]
|
||||
}
|
||||
},
|
||||
printer_current_filename: {
|
||||
get() {
|
||||
return this.$store.getters["farm/"+this.printer._namespace+"/getCurrentFilename"]
|
||||
}
|
||||
},
|
||||
printer_image: {
|
||||
get() {
|
||||
return this.$store.getters["farm/"+this.printer._namespace+"/getImage"]
|
||||
}
|
||||
},
|
||||
printer_logo: {
|
||||
get() {
|
||||
return this.$store.getters["farm/"+this.printer._namespace+"/getLogo"]
|
||||
}
|
||||
},
|
||||
printer_position: {
|
||||
get() {
|
||||
return this.$store.getters["farm/"+this.printer._namespace+"/getPosition"]
|
||||
}
|
||||
},
|
||||
printer_preview: {
|
||||
get() {
|
||||
return this.$store.getters["farm/"+this.printer._namespace+"/getPrinterPreview"]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickPrinter() {
|
||||
if (this.printer.socket.isConnected) this.changePrinter()
|
||||
else this.reconnectPrinter()
|
||||
},
|
||||
changePrinter() {
|
||||
if (this.remoteMode) this.$store.dispatch('changePrinter', { printer: this.printer._namespace })
|
||||
else window.location.href = "//"+this.printer.socket.hostname+(parseInt(this.printer.socket.webPort) !== 80 ? ':'+this.printer.socket.webPort : '')
|
||||
},
|
||||
reconnectPrinter() {
|
||||
this.$store.dispatch("farm/"+this.printer._namespace+"/reconnect")
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -11,7 +11,7 @@
|
||||
<span class="subheading"><v-icon left>mdi-cog</v-icon>General</span>
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card-text class="py-0">
|
||||
<v-card-text class="pt-2 pb-0">
|
||||
<v-text-field
|
||||
v-model="printerName"
|
||||
label="Printer Name"
|
||||
|
323
src/components/panels/Settings/RemotePrintersPanel.vue
Normal file
323
src/components/panels/Settings/RemotePrintersPanel.vue
Normal file
@ -0,0 +1,323 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<v-card>
|
||||
<v-toolbar flat dense >
|
||||
<v-toolbar-title>
|
||||
<span class="subheading"><v-icon left>mdi-printer-3d</v-icon>Remote Printers</span>
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card-text class="py-3">
|
||||
<v-container>
|
||||
<v-row v-for="(printer, index) in this['farm/getPrinters']" v-bind:key="index">
|
||||
<v-col class="rounded transition-swing secondary py-2 px-2 mb-6" style="cursor: pointer;">
|
||||
<v-row align="center">
|
||||
<v-col class="col-auto pr-0">
|
||||
<v-progress-circular
|
||||
indeterminate
|
||||
color="primary"
|
||||
v-if="printer.socket.isConnecting"
|
||||
></v-progress-circular>
|
||||
<v-icon
|
||||
:color="printer.socket.isConnected ? 'green' : 'red'"
|
||||
v-if="!printer.socket.isConnecting"
|
||||
>mdi-{{ printer.socket.isConnected ? 'checkbox-marked-circle' : 'cancel' }}</v-icon>
|
||||
</v-col>
|
||||
<v-col>{{ printer.socket.hostname }}{{ parseInt(printer.socket.port) !== 80 ? ":"+printer.socket.port : "" }}</v-col>
|
||||
<v-col class="col-auto"><v-btn small class="minwidth-0" v-on:click.stop.prevent="editPrinter(index)"><v-icon small>mdi-pencil</v-icon></v-btn></v-col>
|
||||
</v-row>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="text-center mt-0">
|
||||
<v-btn @click="dialogAddPrinter.bool = true">add printer</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-dialog v-model="dialogAddPrinter.bool" persistent :width="400">
|
||||
<v-card dark>
|
||||
<v-toolbar flat dense color="primary">
|
||||
<v-toolbar-title>
|
||||
<span class="subheading">
|
||||
<v-icon class="mdi mdi-connection" left></v-icon>
|
||||
Add Printer
|
||||
</span>
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn small class="minwidth-0" @click="dialogAddPrinter.bool = false"><v-icon small>mdi-close-thick</v-icon></v-btn>
|
||||
</v-toolbar>
|
||||
<v-card-text class="pt-3" v-if="remoteMode">
|
||||
<v-container class="px-0 py-0">
|
||||
<v-row>
|
||||
<v-col class="col-8">
|
||||
<v-text-field
|
||||
v-model="dialogAddPrinter.hostname"
|
||||
:rules="[v => !!v || 'Hostname is required']"
|
||||
label="Hostname/IP"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col class="col-4">
|
||||
<v-text-field
|
||||
v-model="dialogAddPrinter.port"
|
||||
:rules="[v => !!v || 'Port is required']"
|
||||
label="Port"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="text-right">
|
||||
<v-btn
|
||||
color="white"
|
||||
outlined
|
||||
class="middle"
|
||||
@click="addPrinter"
|
||||
>
|
||||
add printer
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-3" v-if="!remoteMode">
|
||||
<v-container class="px-0 py-0">
|
||||
<v-row>
|
||||
<v-col class="col-12">
|
||||
<v-text-field
|
||||
v-model="dialogAddPrinter.hostname"
|
||||
:rules="[v => !!v || 'Hostname is required']"
|
||||
label="Hostname/IP"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="col-6">
|
||||
<v-text-field
|
||||
v-model="dialogAddPrinter.webPort"
|
||||
:rules="[v => !!v || 'Web-Port is required']"
|
||||
label="Web-Port"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col class="col-6">
|
||||
<v-text-field
|
||||
v-model="dialogAddPrinter.port"
|
||||
:rules="[v => !!v || 'API-Port is required']"
|
||||
label="API-Port"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="text-right">
|
||||
<v-btn
|
||||
color="white"
|
||||
outlined
|
||||
class="middle"
|
||||
@click="addPrinter"
|
||||
>
|
||||
add printer
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog v-model="dialogEditPrinter.bool" persistent :width="400">
|
||||
<v-card dark>
|
||||
<v-toolbar flat dense color="primary">
|
||||
<v-toolbar-title>
|
||||
<span class="subheading">
|
||||
<v-icon class="mdi mdi-connection" left></v-icon>
|
||||
Edit Printer
|
||||
</span>
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn small class="minwidth-0" @click="dialogEditPrinter.bool = false"><v-icon small>mdi-close-thick</v-icon></v-btn>
|
||||
</v-toolbar>
|
||||
<v-card-text class="pt-3" v-if="remoteMode">
|
||||
<v-container class="px-0 py-0">
|
||||
<v-row>
|
||||
<v-col class="col-8">
|
||||
<v-text-field
|
||||
v-model="dialogEditPrinter.hostname"
|
||||
:rules="[v => !!v || 'Hostname is required']"
|
||||
label="Hostname/IP"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col class="col-4">
|
||||
<v-text-field
|
||||
v-model="dialogEditPrinter.port"
|
||||
:rules="[v => !!v || 'Port is required']"
|
||||
label="Port"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="">
|
||||
<v-btn
|
||||
color="red"
|
||||
outlined
|
||||
class="middle minwidth-0"
|
||||
@click="delPrinter"
|
||||
>
|
||||
<v-icon small>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col class="text-right">
|
||||
<v-btn
|
||||
color="white"
|
||||
outlined
|
||||
class="middle"
|
||||
@click="updatePrinter"
|
||||
>
|
||||
update printer
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-card-text class="pt-3" v-if="!remoteMode">
|
||||
<v-container class="px-0 py-0">
|
||||
<v-row>
|
||||
<v-col class="col-12">
|
||||
<v-text-field
|
||||
v-model="dialogEditPrinter.hostname"
|
||||
:rules="[v => !!v || 'Hostname is required']"
|
||||
label="Hostname/IP"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="col-6">
|
||||
<v-text-field
|
||||
v-model="dialogEditPrinter.webPort"
|
||||
:rules="[v => !!v || 'Web-Port is required']"
|
||||
label="Web-Port"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
<v-col class="col-6">
|
||||
<v-text-field
|
||||
v-model="dialogEditPrinter.port"
|
||||
:rules="[v => !!v || 'API-Port is required']"
|
||||
label="API-Port"
|
||||
required
|
||||
></v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row>
|
||||
<v-col class="">
|
||||
<v-btn
|
||||
color="red"
|
||||
outlined
|
||||
class="middle minwidth-0"
|
||||
@click="delPrinter"
|
||||
>
|
||||
<v-icon small>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col class="text-right">
|
||||
<v-btn
|
||||
color="white"
|
||||
outlined
|
||||
class="middle"
|
||||
@click="updatePrinter"
|
||||
>
|
||||
update printer
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
dialogAddPrinter: {
|
||||
bool: false,
|
||||
hostname: "",
|
||||
port: "7125",
|
||||
webPort: "80"
|
||||
},
|
||||
dialogEditPrinter: {
|
||||
bool: false,
|
||||
hostname: "",
|
||||
port: "",
|
||||
webPort: "80",
|
||||
index: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
remoteMode: state => state.socket.remoteMode,
|
||||
protocol: state => state.socket.protocol,
|
||||
}),
|
||||
...mapGetters([
|
||||
'farm/getPrinters',
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
addPrinter() {
|
||||
this.$store.commit('farm/addPrinter',{
|
||||
hostname: this.dialogAddPrinter.hostname,
|
||||
port: this.dialogAddPrinter.port,
|
||||
webPort: this.dialogAddPrinter.webPort,
|
||||
protocol: this.protocol
|
||||
})
|
||||
|
||||
this.dialogAddPrinter.hostname = ""
|
||||
this.dialogAddPrinter.port = "7125"
|
||||
this.dialogAddPrinter.webPort = "80"
|
||||
this.dialogAddPrinter.bool = false
|
||||
|
||||
this.$store.dispatch("farm/savePrinters")
|
||||
},
|
||||
editPrinter(index) {
|
||||
this.dialogEditPrinter.hostname = this["farm/getPrinters"][index].socket.hostname
|
||||
this.dialogEditPrinter.port = this["farm/getPrinters"][index].socket.port
|
||||
this.dialogEditPrinter.webPort = this["farm/getPrinters"][index].socket.webPort
|
||||
this.dialogEditPrinter.index = index
|
||||
this.dialogEditPrinter.bool = true
|
||||
},
|
||||
updatePrinter() {
|
||||
this.$store.commit("farm/"+this.dialogEditPrinter.index+"/setSocketData", {
|
||||
hostname: this.dialogEditPrinter.hostname,
|
||||
port: this.dialogEditPrinter.port,
|
||||
webPort: this.dialogEditPrinter.webPort,
|
||||
isConnecting: true,
|
||||
})
|
||||
this.$store.dispatch("farm/"+this.dialogEditPrinter.index+"/reconnect")
|
||||
this.dialogEditPrinter.bool = false
|
||||
|
||||
this.$store.dispatch("farm/savePrinters")
|
||||
},
|
||||
delPrinter() {
|
||||
this.$store.commit("farm/removePrinter", { name: this.dialogEditPrinter.index })
|
||||
this.$store.dispatch("farm/savePrinters")
|
||||
this.dialogEditPrinter.bool = false
|
||||
|
||||
this.$store.dispatch("farm/savePrinters")
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
@ -2,7 +2,7 @@
|
||||
<v-card>
|
||||
<v-toolbar flat dense >
|
||||
<v-toolbar-title>
|
||||
<span class="subheading"><v-icon left>mdi-update</v-icon>Update</span>
|
||||
<span class="subheading"><v-icon left>mdi-update</v-icon>Update Manager</span>
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-tooltip top>
|
||||
@ -12,82 +12,95 @@
|
||||
<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-card-text class="px-0 py-0">
|
||||
<v-container py-0 px-0>
|
||||
<v-row v-if="'version' in klipper" class="py-2">
|
||||
<v-col class="pl-6 text-no-wrap">
|
||||
<strong>Klipper</strong><br />
|
||||
<span v-if="'remote_version' in klipper && klipper.version !== klipper.remote_version">{{ klipper.version+' > '+klipper.remote_version }}</span>
|
||||
<span v-if="!('remote_version' in klipper && klipper.version !== klipper.remote_version)">{{ klipper.version }}</span>
|
||||
</v-col>
|
||||
<v-col class="pr-6 py-2 text-right">
|
||||
<v-col class="pr-6 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>
|
||||
:color="getColor(klipper)"
|
||||
@click="updateKlipper"
|
||||
:disabled="is_disabled(klipper)"
|
||||
class="minwidth-0 mt-3 px-2 text-uppercase"
|
||||
><v-icon small class="mr-1">mdi-{{ getIcon(klipper) }}</v-icon>{{ getText(klipper) }}</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)"> > {{ 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>
|
||||
<div v-if="'version' in moonraker">
|
||||
<v-divider class="my-0" ></v-divider>
|
||||
<v-row class="py-2">
|
||||
<v-col class="pl-6 text-no-wrap">
|
||||
<strong>Moonraker</strong><br />
|
||||
<span v-if="'remote_version' in moonraker && moonraker.version !== moonraker.remote_version">{{ moonraker.version+' > '+moonraker.remote_version }}</span>
|
||||
<span v-if="!('remote_version' in moonraker && moonraker.version !== moonraker.remote_version)">{{ moonraker.version }}</span>
|
||||
</v-col>
|
||||
<v-col class="pr-6 text-right">
|
||||
<v-chip
|
||||
small
|
||||
label
|
||||
outlined
|
||||
:color="getColor(moonraker)"
|
||||
@click="updateMoonraker"
|
||||
:disabled="is_disabled(moonraker)"
|
||||
class="minwidth-0 mt-3 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 class="py-2">
|
||||
<v-col class="pl-6 text-no-wrap">
|
||||
<strong>Mainsail</strong><br />
|
||||
{{ 'v'+package_version }}
|
||||
<span v-if="!is_disabled(mainsail)"> > {{ mainsail.remote_version.replace('Version ', 'v') }}</span>
|
||||
</v-col>
|
||||
<v-col class="pr-6 text-right">
|
||||
<v-chip
|
||||
small
|
||||
label
|
||||
outlined
|
||||
:color="getColor(mainsail)"
|
||||
@click="updateMainsail"
|
||||
:disabled="is_disabled(mainsail)"
|
||||
class="minwidth-0 mt-3 px-2 text-uppercase"
|
||||
><v-icon small class="mr-1">mdi-{{ getIcon(mainsail) }}</v-icon>{{ getText(mainsail) }}</v-chip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
<div v-if="system !== false && 'package_count' in system">
|
||||
<v-divider class="my-0 border-top-2" ></v-divider>
|
||||
<v-row class="pt-2">
|
||||
<v-col class="pl-6 text-no-wrap">
|
||||
<strong>System</strong><br />
|
||||
<v-tooltip top v-if="system.package_count > 0">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<span v-bind="attrs" v-on="on">{{ system.package_count }} packages can be upgraded</span>
|
||||
</template>
|
||||
<span>{{ system.package_list.join(', ') }}</span>
|
||||
</v-tooltip>
|
||||
<span v-if="system.package_count === 0">OS-Packages</span>
|
||||
</v-col>
|
||||
<v-col class="pr-6 text-right">
|
||||
<v-chip
|
||||
small
|
||||
label
|
||||
outlined
|
||||
:color="system.package_count ? 'primary' : 'green'"
|
||||
:disabled="!(system.package_count)"
|
||||
@click="updateSystem"
|
||||
class="minwidth-0 mt-3 px-2 text-uppercase"
|
||||
><v-icon small class="mr-1">mdi-{{ system.package_count ? 'progress-upload' : 'check' }}</v-icon>{{ system.package_count ? 'upgrade' : 'up-to-date' }}</v-chip>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
@ -109,6 +122,7 @@
|
||||
package_version: state => state.packageVersion,
|
||||
klipper: state => state.server.updateManager.klipper,
|
||||
moonraker: state => state.server.updateManager.moonraker,
|
||||
system: state => state.server.updateManager.system,
|
||||
loadings: state => state.socket.loadings,
|
||||
}),
|
||||
mainsail:{
|
||||
|
@ -12,7 +12,7 @@
|
||||
<v-card-text>
|
||||
<v-container px-0 py-0>
|
||||
<v-row>
|
||||
<v-col class="py-2">
|
||||
<v-col class="pt-2 mb-1">
|
||||
<v-text-field
|
||||
v-model="webcamUrl"
|
||||
hide-details
|
||||
|
@ -11,6 +11,7 @@ import ConfigFilesPanel from "./ConfigFilesPanel";
|
||||
import RunoutPanel from "./RunoutPanel";
|
||||
import LogfilesPanel from "./LogfilesPanel";
|
||||
import UpdatePanel from "./UpdatePanel";
|
||||
import RemotePrintersPanel from "./RemotePrintersPanel";
|
||||
|
||||
Vue.component('settings-general-panel', GeneralPanel);
|
||||
Vue.component('settings-webcam-panel', WebcamPanel);
|
||||
@ -23,6 +24,7 @@ Vue.component('settings-runout-panel', RunoutPanel);
|
||||
Vue.component('settings-logfiles-panel', LogfilesPanel);
|
||||
Vue.component('settings-config-files-panel', ConfigFilesPanel);
|
||||
Vue.component('settings-update-panel', UpdatePanel);
|
||||
Vue.component('settings-remote-printers-panel', RemotePrintersPanel);
|
||||
|
||||
export default {
|
||||
|
||||
|
@ -24,13 +24,13 @@ fetch('/config.json')
|
||||
.then(file => {
|
||||
store.commit('socket/setData', file);
|
||||
|
||||
const websocketProtocol = document.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||
const socketClient = new WebSocketClient(websocketProtocol + store.state.socket.hostname + ':' + store.state.socket.port + '/websocket', {
|
||||
const socketClient = new WebSocketClient(store.state.socket.protocol + '://' + store.state.socket.hostname + ':' + store.state.socket.port + '/websocket', {
|
||||
store: store,
|
||||
reconnectEnabled: true,
|
||||
reconnectInterval: store.state.socket.reconnectInterval,
|
||||
});
|
||||
socketClient.connect();
|
||||
|
||||
if (!store.state.socket.remoteMode) socketClient.connect()
|
||||
Vue.prototype.$socket = socketClient;
|
||||
|
||||
new Vue({
|
||||
|
37
src/pages/Farm.vue
Normal file
37
src/pages/Farm.vue
Normal file
@ -0,0 +1,37 @@
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="(printer,key) in printers"
|
||||
v-bind:key="key"
|
||||
class="col-12 col-sm-6 col-md-4"
|
||||
>
|
||||
<farm-printer-panel v-bind:printer="printer"></farm-printer-panel>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<script>
|
||||
import FarmPrinterPanel from "@/components/panels/FarmPrinterPanel";
|
||||
|
||||
export default {
|
||||
components: { FarmPrinterPanel },
|
||||
data () {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
printers: {
|
||||
get() {
|
||||
return this.$store.getters["farm/getPrinters"]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
@ -4,14 +4,15 @@
|
||||
<v-col class="col-12 col-md-6 col-lg-4">
|
||||
<settings-general-panel></settings-general-panel>
|
||||
<settings-webcam-panel class="mt-6"></settings-webcam-panel>
|
||||
</v-col>
|
||||
<v-col class="col-12 col-md-6 col-lg-4">
|
||||
<settings-dashboard-panel></settings-dashboard-panel>
|
||||
<settings-dashboard-panel class="mt-6"></settings-dashboard-panel>
|
||||
<settings-console-panel class="mt-6"></settings-console-panel>
|
||||
</v-col>
|
||||
<v-col class="col-12 col-md-6 col-lg-4">
|
||||
<settings-macros-panel></settings-macros-panel>
|
||||
</v-col>
|
||||
<v-col class="col-12 col-md-6 col-lg-4">
|
||||
<settings-remote-printers-panel></settings-remote-printers-panel>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,26 +1,32 @@
|
||||
export default class WebSocketClient {
|
||||
|
||||
constructor (url, options) {
|
||||
this.instance = null;
|
||||
this.url = url;
|
||||
this.reconnectInterval = options.reconnectInterval;
|
||||
this.store = options.store;
|
||||
this.wsData = [];
|
||||
this.timerId = 0;
|
||||
this.keepAliveTimeout = 1000;
|
||||
this.instance = null
|
||||
this.url = url
|
||||
this.reconnects = 0
|
||||
this.maxReconnects = options.maxReconnects || 10
|
||||
this.reconnectInterval = options.reconnectInterval || 1000
|
||||
this.store = options.store
|
||||
this.wsData = []
|
||||
this.timerId = 0
|
||||
this.keepAliveTimeout = 1000
|
||||
|
||||
this.onOpen = null;
|
||||
this.onMessage = null;
|
||||
this.onClose = null;
|
||||
this.onError = null;
|
||||
this.onOpen = null
|
||||
this.onMessage = null
|
||||
this.onClose = null
|
||||
this.onError = null
|
||||
this.blacklistMessages = [
|
||||
"Metadata not available for",
|
||||
"Klippy Request Timed Out",
|
||||
"Klippy Host not connected",
|
||||
];
|
||||
]
|
||||
this.blacklistFunctions = [
|
||||
"getPowerDevices",
|
||||
];
|
||||
]
|
||||
}
|
||||
|
||||
setUrl(url) {
|
||||
this.url = url
|
||||
}
|
||||
|
||||
createMessage (method, params, id) {
|
||||
@ -42,26 +48,37 @@ export default class WebSocketClient {
|
||||
}
|
||||
|
||||
connect () {
|
||||
this.instance = new WebSocket(this.url);
|
||||
this.store.dispatch("socket/setData", { isConnecting: true })
|
||||
this.instance = new WebSocket(this.url)
|
||||
|
||||
this.instance.onopen = () => {
|
||||
if (this.store) this.passToStore('socket/onOpen', event);
|
||||
};
|
||||
this.reconnects = 0
|
||||
if (this.store) this.passToStore('socket/onOpen', event)
|
||||
}
|
||||
|
||||
this.instance.onclose = (e) => {
|
||||
this.passToStore('socket/onClose', e);
|
||||
this.passToStore('socket/onClose', e)
|
||||
window.console.log("reconnectInterval: "+this.reconnectInterval)
|
||||
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, this.reconnectInterval);
|
||||
};
|
||||
if (!e.wasClean && this.reconnects < this.maxReconnects) {
|
||||
this.reconnects++
|
||||
setTimeout(() => {
|
||||
this.connect()
|
||||
}, this.reconnectInterval)
|
||||
} else {
|
||||
this.store.dispatch("socket/setData", {
|
||||
isConnecting: false,
|
||||
connectingFailed: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.instance.onerror = () => {
|
||||
this.instance.close();
|
||||
};
|
||||
this.instance.close()
|
||||
}
|
||||
|
||||
this.instance.onmessage = (msg) => {
|
||||
let data = JSON.parse(msg.data);
|
||||
let data = JSON.parse(msg.data)
|
||||
if (this.store) {
|
||||
if (this.wsData.filter(item => item.id === data.id).length > 0 &&
|
||||
this.wsData.filter(item => item.id === data.id)[0].action !== "") {
|
||||
@ -79,7 +96,7 @@ export default class WebSocketClient {
|
||||
error: data.error,
|
||||
requestParams: tmpWsData.params
|
||||
})
|
||||
);
|
||||
)
|
||||
}
|
||||
} else {
|
||||
let result = data.result
|
||||
@ -95,7 +112,11 @@ export default class WebSocketClient {
|
||||
}
|
||||
} else this.passToStore('socket/onMessage', data)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.instance) this.instance.close()
|
||||
}
|
||||
|
||||
sendObj (method, params, action = '', actionPreload = null) {
|
||||
@ -107,7 +128,7 @@ export default class WebSocketClient {
|
||||
params: params,
|
||||
actionPreload: actionPreload,
|
||||
})
|
||||
this.instance.send(this.createMessage(method, params, id));
|
||||
this.instance.send(this.createMessage(method, params, id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Dashboard from '../pages/Dashboard.vue'
|
||||
import Webcam from '../pages/Webcam.vue'
|
||||
import Farm from '../pages/Farm.vue'
|
||||
import Console from '../pages/Console.vue'
|
||||
import Heightmap from '../pages/Heightmap.vue'
|
||||
import Files from '../pages/Files.vue'
|
||||
@ -11,10 +12,16 @@ const routes = [
|
||||
{
|
||||
title: "Dashboard",
|
||||
path: '/',
|
||||
icon: 'view-dashboard',
|
||||
icon: 'monitor-dashboard',
|
||||
component: Dashboard,
|
||||
alwaysShow: true,
|
||||
},
|
||||
{
|
||||
title: "Printers",
|
||||
path: '/allPrinters',
|
||||
component: Farm,
|
||||
alwaysShow: false,
|
||||
},
|
||||
{
|
||||
title: "Webcam",
|
||||
path: '/cam',
|
||||
|
@ -1,3 +1,4 @@
|
||||
//import Vue from 'vue'
|
||||
import router from "../plugins/router";
|
||||
|
||||
|
||||
@ -6,4 +7,18 @@ export default {
|
||||
switchToDashboard() {
|
||||
router.push("/");
|
||||
},
|
||||
|
||||
changePrinter({ dispatch, getters }, payload) {
|
||||
dispatch('files/reset')
|
||||
dispatch('gui/reset')
|
||||
dispatch('printer/reset')
|
||||
dispatch('socket/reset')
|
||||
|
||||
const printerSocket = getters["farm/"+payload.printer+"/getSocketData"]
|
||||
|
||||
dispatch('socket/setSocket', {
|
||||
hostname: printerSocket.hostname,
|
||||
port: printerSocket.port
|
||||
})
|
||||
}
|
||||
}
|
||||
|
90
src/store/farm/index.js
Normal file
90
src/store/farm/index.js
Normal file
@ -0,0 +1,90 @@
|
||||
import printer from './printer'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: () => {
|
||||
|
||||
},
|
||||
getters: {
|
||||
countPrinters: state => {
|
||||
return Object.keys(state).length
|
||||
},
|
||||
getPrinters: state => {
|
||||
return state
|
||||
},
|
||||
getPrinterName: (getters) => (namespace) => {
|
||||
return getters[namespace+"/getPrinterName"]
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
readStoredPrinters({ rootState, commit }) {
|
||||
if (rootState.socket.remoteMode) {
|
||||
if (localStorage.getItem('printers')) {
|
||||
try {
|
||||
const printers = JSON.parse(localStorage.getItem('printers')) || []
|
||||
printers.forEach((printer) => {
|
||||
commit('addPrinter',{
|
||||
hostname: printer.hostname,
|
||||
port: printer.port,
|
||||
protocol: rootState.socket.protocol
|
||||
})
|
||||
})
|
||||
} catch(e) {
|
||||
localStorage.removeItem('printers')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rootState.gui.remotePrinters.forEach((printer) => {
|
||||
commit('addPrinter',{
|
||||
hostname: printer.hostname,
|
||||
port: printer.port,
|
||||
webPort: printer.webPort,
|
||||
protocol: rootState.socket.protocol
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
savePrinters({ rootState, state, dispatch }) {
|
||||
const printers = []
|
||||
|
||||
if (rootState.socket.remoteMode) {
|
||||
for (const key in state) {
|
||||
printers.push({
|
||||
hostname: state[key].socket.hostname,
|
||||
port: state[key].socket.port,
|
||||
})
|
||||
}
|
||||
|
||||
localStorage.setItem('printers', JSON.stringify(printers))
|
||||
} else {
|
||||
for (const key in state) {
|
||||
printers.push({
|
||||
hostname: state[key].socket.hostname,
|
||||
port: state[key].socket.port,
|
||||
webPort: state[key].socket.webPort,
|
||||
})
|
||||
}
|
||||
|
||||
dispatch("gui/setSettings", { remotePrinters: printers }, { root: true })
|
||||
}
|
||||
}
|
||||
},
|
||||
mutations: {
|
||||
addPrinter(state, payload) {
|
||||
if ('hostname' in payload && payload.hostname !== "") {
|
||||
const nextPrinterName = 'printer'+Object.entries(state).length
|
||||
if (!this.hasModule(['farm', nextPrinterName])) {
|
||||
this.registerModule(['farm', nextPrinterName], printer)
|
||||
this.commit('farm/'+nextPrinterName+'/setSocketData', {...payload, _namespace: nextPrinterName })
|
||||
this.dispatch('farm/'+nextPrinterName+'/connect', {}, { root: true })
|
||||
}
|
||||
}
|
||||
},
|
||||
removePrinter(state, payload) {
|
||||
if (payload.name in state) {
|
||||
if (state[payload.name].socket.instance) state[payload.name].socket.instance.close()
|
||||
this.unregisterModule(['farm', payload.name])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
175
src/store/farm/printer/actions.js
Normal file
175
src/store/farm/printer/actions.js
Normal file
@ -0,0 +1,175 @@
|
||||
export default {
|
||||
reset({commit}) {
|
||||
commit('reset')
|
||||
},
|
||||
|
||||
connect({ state, commit, dispatch, getters }) {
|
||||
commit("setSocketData", { isConnecting: true })
|
||||
const socket = new WebSocket(getters.getSocketUrl)
|
||||
|
||||
socket.onopen = () => {
|
||||
commit("setSocketData", {
|
||||
instance: socket,
|
||||
reconnects: 0,
|
||||
isConnecting: false,
|
||||
isConnected: true
|
||||
})
|
||||
|
||||
dispatch("initPrinter")
|
||||
}
|
||||
|
||||
socket.onclose = (e) => {
|
||||
if (!e.wasClean && state.socket.reconnects < state.socket.maxReconnects) {
|
||||
commit("setSocketData", { reconnects: state.socket.reconnects + 1 })
|
||||
|
||||
setTimeout(() => {
|
||||
dispatch("connect")
|
||||
}, state.socket.reconnectInterval)
|
||||
} else {
|
||||
commit("setSocketData", {
|
||||
isConnecting: false,
|
||||
isConnected: false,
|
||||
reconnects: 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
socket.onerror = () => {
|
||||
|
||||
}
|
||||
|
||||
socket.onmessage = (msg) => {
|
||||
let data = JSON.parse(msg.data)
|
||||
if (data && data.method) {
|
||||
switch (data.method) {
|
||||
case "notify_status_update":
|
||||
commit("setData", data.params[0])
|
||||
break
|
||||
|
||||
case "notify_filelist_changed":
|
||||
commit("notifyFilelistChanged", data.params[0])
|
||||
break
|
||||
|
||||
case "notify_klippy_disconnected":
|
||||
dispatch("disconnectKlippy")
|
||||
break
|
||||
|
||||
case "notify_klippy_ready":
|
||||
dispatch("initPrinter")
|
||||
break
|
||||
}
|
||||
} else if ("result" in data) {
|
||||
if (
|
||||
state.socket.wsData.filter(item => item.id === data.id).length > 0 &&
|
||||
state.socket.wsData.filter(item => item.id === data.id)[0].action !== undefined &&
|
||||
state.socket.wsData.filter(item => item.id === data.id)[0].action !== ""
|
||||
) {
|
||||
let result = data.result
|
||||
if (result === "ok") result = { result: result }
|
||||
if (typeof(result) === "string") result = { result: result }
|
||||
|
||||
let preload = {}
|
||||
let wsData = state.socket.wsData.filter(item => item.id === data.id)[0]
|
||||
if (wsData.actionPreload) Object.assign(preload, wsData.actionPreload)
|
||||
Object.assign(preload, { requestParams: wsData.params })
|
||||
Object.assign(preload, result)
|
||||
|
||||
dispatch(wsData.action, preload)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
reconnect({ state, dispatch }) {
|
||||
if(state.socket.instance) state.socket.instance.close()
|
||||
dispatch("connect")
|
||||
},
|
||||
|
||||
sendObj ({ state, commit }, payload) {
|
||||
if (state.socket.instance && state.socket.instance.readyState === WebSocket.OPEN) {
|
||||
const id = Math.floor(Math.random() * 10000) + 1
|
||||
|
||||
commit("addWsData", {
|
||||
id: id,
|
||||
action: payload.action,
|
||||
params: payload.params || {},
|
||||
actionPreload: payload.actionPreload || null,
|
||||
})
|
||||
|
||||
state.socket.instance.send(JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: payload.method,
|
||||
params: payload.params || {},
|
||||
id: id
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
disconnectKlippy({ commit }) {
|
||||
commit("setData", { print_stats: { state: "error" }})
|
||||
},
|
||||
|
||||
initPrinter({ commit, dispatch }) {
|
||||
commit("resetData")
|
||||
|
||||
dispatch("sendObj", {
|
||||
method: "printer.objects.list",
|
||||
action: "getObjectsList",
|
||||
})
|
||||
|
||||
dispatch("sendObj", {
|
||||
method: "server.files.list",
|
||||
action: "getConfigDir",
|
||||
params: { root: "config"}
|
||||
})
|
||||
},
|
||||
|
||||
getObjectsList({ dispatch }, payload) {
|
||||
const allowed = [
|
||||
'webhooks',
|
||||
'print_stats',
|
||||
'virtual_sdcard',
|
||||
'display_status',
|
||||
'heaters',
|
||||
'heater_bed',
|
||||
'heater_fan',
|
||||
'fan',
|
||||
'temperature_fan',
|
||||
'temperature_sensor',
|
||||
'idle_timeout',
|
||||
'toolhead'
|
||||
]
|
||||
|
||||
let subscripts = {}
|
||||
payload.objects.forEach((object) => {
|
||||
const splits = object.split(" ")
|
||||
const objectName = splits[0]
|
||||
|
||||
if (
|
||||
allowed.includes(objectName) ||
|
||||
objectName.startsWith("extruder")
|
||||
) {
|
||||
subscripts = {...subscripts, [object]: null }
|
||||
}
|
||||
})
|
||||
|
||||
if (subscripts !== {})
|
||||
dispatch("sendObj", {
|
||||
method: 'printer.objects.subscribe',
|
||||
params: { objects: subscripts },
|
||||
action: "getData"
|
||||
});
|
||||
},
|
||||
|
||||
getData({ commit }, payload) {
|
||||
commit("setData", payload)
|
||||
},
|
||||
|
||||
getMetadataCurrentFile({ commit }, payload) {
|
||||
commit("setCurrentFile", payload)
|
||||
},
|
||||
|
||||
getConfigDir({ commit }, payload) {
|
||||
commit("setConfigDir", payload)
|
||||
},
|
||||
}
|
174
src/store/farm/printer/getters.js
Normal file
174
src/store/farm/printer/getters.js
Normal file
@ -0,0 +1,174 @@
|
||||
import {themeDir} from "@/store/variables";
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
getSocketUrl: (state) => {
|
||||
return state.socket.protocol+"://"+state.socket.hostname+":"+state.socket.port+"/websocket"
|
||||
},
|
||||
|
||||
getSocketData: (state) => {
|
||||
return state.socket
|
||||
},
|
||||
|
||||
isCurrentPrinter: (state, getters, rootState) => {
|
||||
return (
|
||||
rootState.socket.hostname === state.socket.hostname &&
|
||||
rootState.socket.port === state.socket.port
|
||||
)
|
||||
},
|
||||
|
||||
getPrinterName: (state) => {
|
||||
if (
|
||||
'gui' in state.data &&
|
||||
'general' in state.data.gui &&
|
||||
'printername' in state.data.gui.general &&
|
||||
state.data.gui.general.printername !== ""
|
||||
) return state.data.gui.general.printername
|
||||
|
||||
return parseInt(state.socket.port) !== 80 ? state.socket.hostname+':'+state.socket.port : state.socket.hostname
|
||||
},
|
||||
|
||||
getStatus: (state, getters) => {
|
||||
if (!state.socket.isConnected) {
|
||||
return state.socket.isConnecting ? "Connecting..." : "Disconnected"
|
||||
} else if (state.data.print_stats && state.data.print_stats.state) {
|
||||
if (state.data.print_stats.state === "printing") {
|
||||
const percent = getters["getPrintPercent"]
|
||||
|
||||
return Math.round(percent*100)+"% Printing"
|
||||
}
|
||||
|
||||
return state.data.print_stats.state.charAt(0).toUpperCase() + state.data.print_stats.state.slice(1)
|
||||
}
|
||||
|
||||
return "Unknown"
|
||||
},
|
||||
|
||||
getCurrentFilename: (state) => {
|
||||
if (
|
||||
'print_stats' in state.data &&
|
||||
'filename' in state.data.print_stats
|
||||
) {
|
||||
return state.data.print_stats.filename
|
||||
}
|
||||
|
||||
return ""
|
||||
},
|
||||
|
||||
getPrintPercent: (state) => {
|
||||
if (
|
||||
'filename' in state.current_file &&
|
||||
'gcode_start_byte' in state.current_file &&
|
||||
'gcode_end_byte' in state.current_file &&
|
||||
state.current_file.filename === state.data.print_stats.filename
|
||||
) {
|
||||
if (state.data.virtual_sdcard.file_position <= state.current_file.gcode_start_byte) return 0
|
||||
if (state.data.virtual_sdcard.file_position >= state.current_file.gcode_end_byte) return 1
|
||||
|
||||
let currentPosition = state.data.virtual_sdcard.file_position - state.current_file.gcode_start_byte
|
||||
let maxPosition = state.current_file.gcode_end_byte - state.current_file.gcode_start_byte
|
||||
|
||||
if (currentPosition > 0 && maxPosition > 0) return 1 / maxPosition * currentPosition
|
||||
}
|
||||
|
||||
return state.data.virtual_sdcard.progress
|
||||
},
|
||||
|
||||
getImage: state => {
|
||||
if (
|
||||
'gui' in state.data &&
|
||||
'webcam' in state.data.gui.webcam &&
|
||||
'url' in state.data.gui.webcam &&
|
||||
state.data.gui.webcam.url !== "" &&
|
||||
'bool' in state.data.gui.webcam &&
|
||||
state.data.gui.webcam.bool &&
|
||||
'dashbaord' in state.data.gui &&
|
||||
'boolWebcam' in state.data.gui.dashboard &&
|
||||
state.data.gui.dashboard.boolWebcam
|
||||
) {
|
||||
return state.data.gui.webcam.url
|
||||
} else if (
|
||||
state.current_file &&
|
||||
"thumbnails" in state.current_file &&
|
||||
state.current_file.thumbnails.find((element) => element.width === 400 && element.height === 300)
|
||||
) {
|
||||
return 'data:image/gif;base64,'+state.current_file.thumbnails.find((element) => element.width === 400 && element.height === 300).data
|
||||
} else return "/img/sidebar-background.png"
|
||||
},
|
||||
|
||||
getLogo: state => {
|
||||
let file = state.theme_files.find(element =>
|
||||
element === themeDir+'/sidebar-logo.svg' ||
|
||||
element === themeDir+'/sidebar-logo.jpg' ||
|
||||
element === themeDir+'/sidebar-logo.png' ||
|
||||
element === themeDir+'/sidebar-logo.gif'
|
||||
)
|
||||
if (file) return "//"+state.socket.hostname+":"+state.socket.port+'/server/files/config/'+file
|
||||
|
||||
return '/img/logo.svg'
|
||||
},
|
||||
|
||||
getPosition: state => {
|
||||
if (
|
||||
'toolhead' in state.data &&
|
||||
'position' in state.data.toolhead
|
||||
) return state.data.toolhead.position
|
||||
|
||||
return []
|
||||
},
|
||||
|
||||
getPrinterPreview: (state, getters) => {
|
||||
const output = []
|
||||
|
||||
Object.keys(state.data).filter((key) => key.startsWith("extruder")).forEach((key) => {
|
||||
if (
|
||||
'temperature' in state.data[key] &&
|
||||
'target' in state.data[key]
|
||||
) {
|
||||
output.push({
|
||||
name: key,
|
||||
value: state.data[key].temperature.toFixed(0)+"° / "+state.data[key].target.toFixed(0)+"°",
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
if ('heater_bed' in state.data) {
|
||||
output.push({
|
||||
name: 'heater_bed',
|
||||
value: state.data.heater_bed.temperature.toFixed(0)+"° / "+state.data.heater_bed.target.toFixed(0)+"°"
|
||||
})
|
||||
}
|
||||
|
||||
if ('temperature_fan chamber' in state.data) {
|
||||
output.push({
|
||||
name: 'chamber',
|
||||
value: state.data['temperature_fan chamber'].temperature.toFixed(0)+"° / "+state.data['temperature_fan chamber'].target.toFixed(0)+"°"
|
||||
})
|
||||
}
|
||||
|
||||
if ('temperature_sensor chamber' in state.data) {
|
||||
output.push({
|
||||
name: 'chamber',
|
||||
value: state.data['temperature_sensor chamber'].temperature.toFixed(0)+"°"
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
'print_stats' in state.data &&
|
||||
'state' in state.data.print_stats &&
|
||||
'print_duration' in state.data.print_stats &&
|
||||
state.data.print_stats.state === "printing" &&
|
||||
state.data.print_stats.print_duration &&
|
||||
getters["getPrintPercent"] > 0
|
||||
) {
|
||||
const eta = new Date(new Date().getTime() + (state.data.print_stats.print_duration / getters["getPrintPercent"] - state.data.print_stats.print_duration).toFixed() * 1000)
|
||||
output.push({
|
||||
name: "ETA",
|
||||
value: (eta.getHours() > 9 ? eta.getHours() : '0'+eta.getHours())+":"+(eta.getMinutes() > 9 ? eta.getMinutes() : '0'+eta.getMinutes())
|
||||
})
|
||||
}
|
||||
|
||||
return output
|
||||
},
|
||||
}
|
40
src/store/farm/printer/index.js
Normal file
40
src/store/farm/printer/index.js
Normal file
@ -0,0 +1,40 @@
|
||||
import actions from './actions'
|
||||
import mutations from './mutations'
|
||||
import getters from './getters'
|
||||
|
||||
export function getDefaultState() {
|
||||
return {
|
||||
_namespace: "",
|
||||
socket: {
|
||||
instance: null,
|
||||
hostname: "",
|
||||
port: 7125,
|
||||
webPort: 80,
|
||||
protocol: 'ws',
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
reconnects: 0,
|
||||
maxReconnects: 2,
|
||||
reconnectInterval: 1000,
|
||||
wsData: [],
|
||||
},
|
||||
data: {
|
||||
|
||||
},
|
||||
current_file: {},
|
||||
theme_files: []
|
||||
}
|
||||
}
|
||||
|
||||
// initial state
|
||||
const state = () => {
|
||||
return getDefaultState()
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
88
src/store/farm/printer/mutations.js
Normal file
88
src/store/farm/printer/mutations.js
Normal file
@ -0,0 +1,88 @@
|
||||
import Vue from 'vue'
|
||||
import { getDefaultState } from './index'
|
||||
|
||||
export default {
|
||||
reset(state) {
|
||||
Object.assign(state, getDefaultState())
|
||||
},
|
||||
|
||||
resetData(state) {
|
||||
Object.assign(state.data, getDefaultState().data)
|
||||
},
|
||||
|
||||
setSocketData (state, payload) {
|
||||
if ("status" in payload) payload = payload.status;
|
||||
if ("requestParams" in payload) delete payload.requestParams
|
||||
if ("_namespace" in payload) {
|
||||
Vue.set(state, "_namespace", payload._namespace)
|
||||
delete payload._namespace
|
||||
}
|
||||
|
||||
Object.entries(payload).forEach(([key, value]) => {
|
||||
Vue.set(state.socket, key, value)
|
||||
})
|
||||
},
|
||||
|
||||
setData(state, payload) {
|
||||
if ("status" in payload) payload = payload.status
|
||||
if ("requestParams" in payload) delete payload.requestParams
|
||||
|
||||
Object.entries(payload).forEach(([key, value]) => {
|
||||
if (key === "print_stats" && 'filename' in value) {
|
||||
this.dispatch("farm/"+state._namespace+"/sendObj", {
|
||||
method: "server.files.metadata",
|
||||
params: {filename: value.filename},
|
||||
action: "getMetadataCurrentFile"
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof (value) === "object") {
|
||||
Vue.set(state.data, key, {
|
||||
...state.data[key],
|
||||
...value
|
||||
})
|
||||
} else Vue.set(state.data, key, value)
|
||||
})
|
||||
},
|
||||
|
||||
addWsData(state, payload) {
|
||||
state.socket.wsData.push(payload)
|
||||
},
|
||||
|
||||
setCurrentFile(state, payload) {
|
||||
if ("requestParams" in payload) delete payload.requestParams
|
||||
Vue.set(state, 'current_file', payload)
|
||||
},
|
||||
|
||||
setConfigDir(state, payload) {
|
||||
for (const [, file] of Object.entries(payload)) {
|
||||
if ("filename" in file) {
|
||||
if (file.filename.startsWith(".theme/")) {
|
||||
state.theme_files.push(file.filename)
|
||||
} else if (file.filename === ".mainsail.json") {
|
||||
fetch('//'+state.socket.hostname+':'+state.socket.port+'/server/files/config/.mainsail.json?time='+Date.now())
|
||||
.then(res => res.json()).then(file => {
|
||||
this.commit("farm/"+state._namespace+"/setMainsailJson", file)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setMainsailJson(state, payload) {
|
||||
Vue.set(state.data, 'gui', payload.state)
|
||||
},
|
||||
|
||||
notifyFilelistChanged( state, payload) {
|
||||
if (
|
||||
payload.action === "upload_file" &&
|
||||
payload.item.root === "config" &&
|
||||
payload.item.path === ".mainsail.json"
|
||||
) {
|
||||
fetch('//'+state.socket.hostname+':'+state.socket.port+'/server/files/config/.mainsail.json?time='+Date.now())
|
||||
.then(res => res.json()).then(file => {
|
||||
this.commit("farm/"+state._namespace+"/setMainsailJson", file)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -61,6 +61,7 @@ export default {
|
||||
fetch('//'+store.state.socket.hostname+':'+store.state.socket.port+'/server/files/config/.mainsail.json?time='+Date.now())
|
||||
.then(res => res.json()).then(file => {
|
||||
this.commit('gui/setData', file, { root: true })
|
||||
if (!store.state.socket.remoteMode) this.dispatch('farm/readStoredPrinters', {}, { root: true })
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,8 @@ export function getDefaultState() {
|
||||
countPerPage: 10,
|
||||
showHiddenFiles: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
remotePrinters: []
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import server from './server'
|
||||
import printer from './printer'
|
||||
import files from './files'
|
||||
import gui from './gui'
|
||||
import farm from './farm'
|
||||
|
||||
Vue.use(Vuex);
|
||||
Vue.use(VueToast);
|
||||
@ -25,6 +26,7 @@ export default new Vuex.Store({
|
||||
printer,
|
||||
files,
|
||||
gui,
|
||||
farm,
|
||||
},
|
||||
getters: getters,
|
||||
mutations: mutations,
|
||||
|
@ -42,8 +42,8 @@ export default {
|
||||
if (!blocklist.includes(nameSplit[0])) subscripts = {...subscripts, [key]: null }
|
||||
}
|
||||
|
||||
if (subscripts !== {}) Vue.prototype.$socket.sendObj('printer.objects.subscribe', { objects: subscripts }, "printer/getData");
|
||||
Vue.prototype.$socket.sendObj("server.temperature_store", {}, "printer/tempHistory/getHistory");
|
||||
if (subscripts !== {}) Vue.prototype.$socket.sendObj('printer.objects.subscribe', { objects: subscripts }, "printer/getData")
|
||||
Vue.prototype.$socket.sendObj("server.temperature_store", {}, "printer/tempHistory/getHistory")
|
||||
|
||||
commit('void', null, { root: true })
|
||||
},
|
||||
|
@ -7,6 +7,10 @@ export function getDefaultState() {
|
||||
moonraker: {},
|
||||
klipper: {},
|
||||
client: {},
|
||||
system: {
|
||||
package_count: 0,
|
||||
package_list: []
|
||||
},
|
||||
updateResponse: {
|
||||
application: "",
|
||||
complete: true,
|
||||
|
@ -1,8 +1,24 @@
|
||||
import Vue from 'vue'
|
||||
|
||||
export default {
|
||||
reset({ commit }) {
|
||||
commit('reset')
|
||||
},
|
||||
|
||||
setData({ commit }, payload) {
|
||||
commit('setData', payload)
|
||||
},
|
||||
|
||||
setSocket({ commit, state }, payload) {
|
||||
commit('setData', payload)
|
||||
|
||||
if ('$socket' in Vue.prototype) {
|
||||
Vue.prototype.$socket.close()
|
||||
Vue.prototype.$socket.setUrl(state.protocol+"://"+payload.hostname+":"+payload.port+"/websocket")
|
||||
Vue.prototype.$socket.connect()
|
||||
}
|
||||
},
|
||||
|
||||
onOpen ({ commit, dispatch }) {
|
||||
commit('setConnected')
|
||||
dispatch('server/init', null, { root: true })
|
||||
@ -10,6 +26,7 @@ export default {
|
||||
|
||||
onClose ({ commit }, event) {
|
||||
commit('setDisconnected');
|
||||
window.console.log(event)
|
||||
|
||||
if (event.wasClean) window.console.log('Socket closed clear')
|
||||
},
|
||||
|
@ -4,10 +4,14 @@ import getters from './getters'
|
||||
|
||||
export function getDefaultState() {
|
||||
return {
|
||||
hostname: process.env.VUE_APP_HOSTNAME || window.location.hostname,
|
||||
port: process.env.VUE_APP_PORT || window.location.port,
|
||||
reconnectInterval: process.env.VUE_APP_RECONNECT_INTERVAL || 5000,
|
||||
remoteMode: process.env.VUE_APP_REMOTE_MODE || (document.location.hostname === "my.mainsail.app"),
|
||||
hostname: process.env.VUE_APP_HOSTNAME || (process.env.VUE_APP_REMOTE_MODE || document.location.hostname === "my.mainsail.app" ? "" : window.location.hostname),
|
||||
port: process.env.VUE_APP_PORT || (process.env.VUE_APP_REMOTE_MODE || document.location.hostname === "my.mainsail.app" ? 7125 : window.location.port),
|
||||
protocol: document.location.protocol === 'https:' ? 'wss' : 'ws',
|
||||
reconnectInterval: process.env.VUE_APP_RECONNECT_INTERVAL || 2000,
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
connectingFailed: false,
|
||||
|
||||
loadings: []
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user