Adding users controls on the frontend quit/shutdown/reboot

This commit is contained in:
Félix Aime 2021-01-11 13:42:29 +01:00
parent 2ea8ef49e8
commit 9ed61f201e
7 changed files with 186 additions and 12 deletions

View File

@ -35,6 +35,18 @@
<input type="checkbox" @change="switch_config('frontend', 'virtual_keyboard')" v-model="config.frontend.virtual_keyboard">
<i class="form-icon"></i> Use virtual keyboard (for touch screen)
</label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'reboot_option')" v-model="config.frontend.reboot_option">
<i class="form-icon"></i> Allow the end-user to reboot the device from the interface.
</label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'shutdown_option')" v-model="config.frontend.shutdown_option">
<i class="form-icon"></i> Allow the end-user to shutdown the device from the interface.
</label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'quit_option')" v-model="config.frontend.quit_option">
<i class="form-icon"></i> Allow the end-user to quit the interface
</label>
<label class="form-switch">
<input type="checkbox" @change="switch_config('frontend', 'hide_mouse')" v-model="config.frontend.hide_mouse">
<i class="form-icon"></i> Hide mouse (for touch screen)

View File

@ -1,8 +1,11 @@
<template>
<div id="app">
<transition name="fade" mode="out-in">
<router-view />
</transition>
<div class="wrapper">
<Controls />
<transition name="fade" mode="out-in">
<router-view />
</transition>
</div>
</div>
</template>
@ -26,5 +29,13 @@
<script>
document.title = 'TinyCheck Frontend'
import Controls from "@/components/Controls.vue"
export default {
name: 'app',
components: {
Controls
}
}
</script>

View File

@ -240,6 +240,11 @@
width: 100%;
}
.wrapper {
width:100%;
height:100%;
}
.center {
width: fit-content;
position: relative;
@ -634,3 +639,44 @@ ul {
opacity:1;
}
}
.controls {
position:fixed;
right:0;
top:0;
width: fit-content;
height: fit-content;
z-index: 10000;
}
.quit-icon {
background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMjUyNTI1IiB4bWxuczp4PSJodHRwOi8vbnMuYWRvYmUuY29tL0V4dGVuc2liaWxpdHkvMS4wLyIgeG1sbnM6aT0iaHR0cDovL25zLmFkb2JlLmNvbS9BZG9iZUlsbHVzdHJhdG9yLzEwLjAvIiB4bWxuczpncmFwaD0iaHR0cDovL25zLmFkb2JlLmNvbS9HcmFwaHMvMS4wLyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEwMCAxMDA7IiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3dpdGNoPjxmb3JlaWduT2JqZWN0IHJlcXVpcmVkRXh0ZW5zaW9ucz0iaHR0cDovL25zLmFkb2JlLmNvbS9BZG9iZUlsbHVzdHJhdG9yLzEwLjAvIiB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIj48L2ZvcmVpZ25PYmplY3Q+PGcgaTpleHRyYW5lb3VzPSJzZWxmIj48cGF0aCBkPSJNNTAsMi41QzIzLjgsMi41LDIuNSwyMy44LDIuNSw1MFMyMy44LDk3LjUsNTAsOTcuNVM5Ny41LDc2LjIsOTcuNSw1MFM3Ni4yLDIuNSw1MCwyLjV6IE03My40LDY2LjMgICAgYzAuNywwLjcsMC43LDEuNywwLDIuNGwtNC44LDQuOGMtMC43LDAuNy0xLjcsMC43LTIuNCwwTDUwLDU3LjJMMzMuNyw3My40Yy0wLjcsMC43LTEuNywwLjctMi40LDBsLTQuOC00LjggICAgYy0wLjctMC43LTAuNy0xLjcsMC0yLjRMNDIuOCw1MEwyNi42LDMzLjdjLTAuNy0wLjctMC43LTEuNywwLTIuNGw0LjgtNC44YzAuNy0wLjcsMS43LTAuNywyLjQsMEw1MCw0Mi44bDE2LjMtMTYuMyAgICBjMC43LTAuNywxLjctMC43LDIuNCwwbDQuOCw0LjhjMC43LDAuNywwLjcsMS43LDAsMi40TDU3LjIsNTBMNzMuNCw2Ni4zeiI+PC9wYXRoPjwvZz48L3N3aXRjaD48L3N2Zz4=");
width: 30px;
height: 30px;
display: block;
margin-right: 10px;
margin-top: 10px;
background-size: cover;
opacity: 0.1;
}
.off-icon {
background-image: url("data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMmIyYjJiIiB4bWxuczp4PSJodHRwOi8vbnMuYWRvYmUuY29tL0V4dGVuc2liaWxpdHkvMS4wLyIgeG1sbnM6aT0iaHR0cDovL25zLmFkb2JlLmNvbS9BZG9iZUlsbHVzdHJhdG9yLzEwLjAvIiB4bWxuczpncmFwaD0iaHR0cDovL25zLmFkb2JlLmNvbS9HcmFwaHMvMS4wLyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiB4PSIwcHgiIHk9IjBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDEwMCAxMDA7IiB4bWw6c3BhY2U9InByZXNlcnZlIj48c3dpdGNoPjxmb3JlaWduT2JqZWN0IHJlcXVpcmVkRXh0ZW5zaW9ucz0iaHR0cDovL25zLmFkb2JlLmNvbS9BZG9iZUlsbHVzdHJhdG9yLzEwLjAvIiB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIj48L2ZvcmVpZ25PYmplY3Q+PGcgaTpleHRyYW5lb3VzPSJzZWxmIj48cGF0aCBkPSJNNTAsMi41TDUwLDIuNUMyMy44LDIuNSwyLjUsMjMuOCwyLjUsNTBsMCwwYzAsMjYuMiwyMS4zLDQ3LjUsNDcuNSw0Ny41bDAsMGMyNi4yLDAsNDcuNS0yMS4zLDQ3LjUtNDcuNWwwLDAgICAgQzk3LjUsMjMuOCw3Ni4yLDIuNSw1MCwyLjV6IE00NS43LDI0LjJjMC0yLjQsMS45LTQuMyw0LjMtNC4zczQuMywxLjksNC4zLDQuM3YyMS42YzAsMi40LTEuOSw0LjMtNC4zLDQuM3MtNC4zLTEuOS00LjMtNC4zVjI0LjIgICAgeiBNNTAsNzguNGMtMTUuMiwwLTI3LjYtMTIuNC0yNy42LTI3LjZjMC04LjcsNC0xNi43LDExLTIyYzEuOS0xLjQsNC42LTEsNiwwLjhjMS40LDEuOSwxLDQuNi0wLjgsNmMtNC44LDMuNi03LjYsOS4yLTcuNiwxNS4yICAgIGMwLDEwLjUsOC41LDE5LjEsMTkuMSwxOS4xYzEwLjUsMCwxOS4xLTguNSwxOS4xLTE5LjFjMC02LTIuOC0xMS41LTcuNi0xNS4yYy0xLjktMS40LTIuMy00LjEtMC44LTZjMS40LTEuOSw0LjEtMi4zLDYtMC44ICAgIGM3LDUuMywxMSwxMy4zLDExLDIyQzc3LjYsNjYsNjUuMiw3OC40LDUwLDc4LjR6Ij48L3BhdGg+PC9nPjwvc3dpdGNoPjwvc3ZnPg==");
width: 30px;
height: 30px;
display: block;
margin-right: 10px;
margin-top: 10px;
background-size: cover;
opacity: 0.1;
}
.off-icon:hover {
opacity: 0.3;
cursor:pointer;
}
.quit-icon:hover {
opacity: 0.3;
cursor:pointer;
}

View File

@ -0,0 +1,52 @@
<template>
<div class="controls">
<i class="off-icon" v-on:click="action('shutdown')" v-if="off_available && off_display"></i>
<i class="quit-icon" v-on:click="action('quit')" v-if="quit_available && quit_display"></i>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'Controls',
data: function (){
return {
off_available : false,
off_display : false,
quit_available: false,
quit_display : false
}
},
methods: {
action: function(action) {
axios.get(`/api/misc/${action}`, { timeout: 30000 })
.then(response => {
if(response.data.status)
console.log(`Let's ${action}`)
})
.catch(error => { console.log(error) });
},
load_config: function() {
axios.get(`/api/misc/config`, { timeout: 60000 })
.then(response => {
this.quit_available = response.data.quit_option
this.off_available = response.data.shutdown_option
})
.catch(error => { console.log(error) });
}
},
watch: {
$route (){
if ( ["capture", "report"].includes(this.$router.currentRoute.name) || screen.height != window.innerHeight ){
this.off_display = false;
this.quit_display = false;
} else {
this.off_display = (this.off_available)? true : false;
this.quit_display = (this.quit_available)? true : false;
}
}
},
created: function() {
this.load_config()
},
}
</script>

View File

@ -27,12 +27,11 @@
</div>
<div v-else>
<p>
<strong>Unfortunately, we got some issues.</strong>
<strong>Unfortunately, we got some issues <br />during the AP creation.</strong>
<br /><br />
Please verify that you've two Wifi interfaces on your device<br />
and restart it by clicking on the button below.<br />
Please verify that you've two WiFi interfaces on your device<br /> and try again by restarting it.<br /><br />
</p>
<button class="btn" v-on:click="reboot()">Reboot the device</button>
<button v-if="reboot_option" class="btn" v-on:click="reboot()">Restart the device</button>
</div>
</div>
@ -53,14 +52,15 @@ export default {
capture_token: false,
capture_start: false,
interval: false,
error: false
error: false,
reboot_option: false
}
},
methods: {
generate_ap: function() {
clearInterval(this.interval)
this.ssid_name = false
axios.get(`/api/network/ap/start`, { timeout: 70000 })
axios.get(`/api/network/ap/start`, { timeout: 30000 })
.then(response => (this.show_ap(response.data)))
},
show_ap: function(data) {
@ -109,9 +109,19 @@ export default {
}
});
}
}
},
load_config: function() {
axios.get(`/api/misc/config`, { timeout: 60000 })
.then(response => {
this.reboot_option = response.data.reboot_option
})
.catch(error => {
console.log(error)
});
},
},
created: function() {
this.load_config()
this.generate_ap();
}
}

View File

@ -13,8 +13,35 @@ def api_reboot():
"""
Reboot the device
"""
sp.Popen("reboot", shell=True)
return jsonify({"mesage": "Let's reboot."})
if read_config(("frontend", "reboot_option")):
sp.Popen("shutdown -r now", shell=True)
return jsonify({"mesage": "Let's reboot."})
else:
return jsonify({"message": "Option disabled", "status": False})
@misc_bp.route("/quit", methods=["GET"])
def api_quit():
"""
Quit the interface (Chromium browser)
"""
if read_config(("frontend", "quit_option")):
sp.Popen('pkill -INT -f "chromium-browser"', shell=True)
return jsonify({"message": "Let's quit", "status": True})
else:
return jsonify({"message": "Option disabled", "status": False})
@misc_bp.route("/shutdown", methods=["GET"])
def api_shutdown():
"""
Reboot the device
"""
if read_config(("frontend", "shutdown_option")):
sp.Popen("shutdown -h now", shell=True)
return jsonify({"message": "Let's shutdown", "status": True})
else:
return jsonify({"message": "Option disabled", "status": False})
@misc_bp.route("/config", methods=["GET"])
@ -27,4 +54,7 @@ def get_config():
"hide_mouse": read_config(("frontend", "hide_mouse")),
"download_links": read_config(("frontend", "download_links")),
"sparklines": read_config(("frontend", "sparklines")),
"quit_option": read_config(("frontend", "quit_option")),
"shutdown_option": read_config(("frontend", "shutdown_option")),
"reboot_option": read_config(("frontend", "reboot_option"))
})

View File

@ -45,5 +45,18 @@ elif [ $PWD = "/tmp/tinycheck" ]; then
service tinycheck-frontend restart
service tinycheck-watchers restart
# Updating configuration with new key-val pairs.
if ! grep -q reboot_option /usr/share/tinycheck/config.yaml; then
sed -i 's/frontend:/frontend:\n reboot_option: true/g' /usr/share/tinycheck/config.yaml
fi
if ! grep -q shutdown_option /usr/share/tinycheck/config.yaml; then
sed -i 's/frontend:/frontend:\n shutdown_option: true/g' /usr/share/tinycheck/config.yaml
fi
if ! grep -q quit_option /usr/share/tinycheck/config.yaml; then
sed -i 's/frontend:/frontend:\n quit_option: true/g' /usr/share/tinycheck/config.yaml
fi
echo "[+] TinyCheck updated!"
fi