Added more stuff

This commit is contained in:
Fabio Manganiello 2020-06-17 00:45:33 +02:00
parent e11e3127f0
commit 4813183942
7 changed files with 343 additions and 18 deletions

View File

@ -49,4 +49,24 @@ form {
}
}
.hidden {
display: none !important;
}
.code {
padding: 1em;
white-space: pre-wrap;
font-family: monospace;
border: 1px dotted rgba(0, 0, 0, 0.8);
border-radius: 1em;
&.response {
background: rgba(200, 255, 200, 0.3);
}
&.error {
background: rgba(255, 200, 200, 0.3);
}
}
// vim:sw=2:ts=2:et:

View File

@ -5,7 +5,7 @@
<div class="body">
<NewHost @add="addHost" v-if="selectedTab === 'add'" />
<Config v-else-if="selectedTab === 'config'" @reload="reload" />
<LocalCommands v-else-if="selectedHost && selectedHostOption === 'localProc'" />
<LocalCommands :host="selectedHost" v-else-if="selectedHost && selectedHostOption === 'localProc'" />
<RemoteCommands v-else-if="selectedHost && selectedHostOption === 'remoteProc'" />
<Run :host="hosts[selectedHost]" v-else-if="selectedHost && selectedHostOption === 'run'" />
<EditHost :host="hosts[selectedHost]" @save="editHost" @remove="removeHost" v-else-if="selectedHost" />

View File

@ -9,7 +9,7 @@
<input type="radio" id="_file" value="file" v-model="extConfigType" />
<label for="_file">From file</label>
<input type="radio" id="_url" value="url" v-model="extConfigType" />
<label for="_file">From URL</label>
<label for="_url">From URL</label>
</div>
<div class="body">

View File

@ -1,6 +1,49 @@
<template>
<div class="page local-procedures">
<h2>Commands stored on the browser</h2>
<div class="page local-actions">
<h2 v-if="host">Actions stored locally for {{ host }}</h2>
<h2 v-else>Actions stored locally</h2>
<div class="body">
<div class="no-actions" v-if="!actions || !Object.keys(actions).length">No actions available on this device</div>
<form class="action" :class="{ selected: selectedAction === name }" v-for="(action, name) in actions" :key="name" @submit.prevent="runAction">
<div class="head" @click="toggleSelectedAction(name)">
<div class="icon">
<i :class="action.iconClass" v-if="action.iconClass" />
<i class="fas fa-cog" v-else />
</div>
<div class="name" v-text="name" />
<div class="controls">
<button type="button" class="run" :disabled="loading" @click.stop="runAction" v-if="selectedAction === name">
<i class="fas fa-play" />
</button>
<button type="button" class="remove" :disabled="loading" @click.stop="removeAction" v-if="selectedAction === name">
<i class="fas fa-trash" />
</button>
</div>
</div>
<div class="body" v-if="selectedAction === name">
<div class="desc">
<div class="row">
<div class="label">Action</div>
<div class="value" v-text="action.name" />
</div>
<div class="row" :class="{ hidden: argValue == null || argValue == '' }" v-for="(argValue, argName) in action.args" :key="argName">
<div class="label" v-text="argName" />
<div class="value" v-text="argValue" />
</div>
</div>
<div class="code" v-if="response || error || loading" :class="{ response: response, error: error }">
<span v-if="loading">Loading...</span>
<span v-text="error" v-else-if="error" />
<span v-text="response" v-else-if="response" />
</div>
</div>
</form>
</div>
</div>
</template>
@ -10,7 +53,166 @@ import mixins from '../utils';
export default {
name: 'LocalCommands',
mixins: [mixins],
props: {
host: String,
},
data() {
return {
hosts: {},
actions_: {},
selectedAction: null,
response: null,
error: null,
};
},
computed: {
actionsByHost() {
return Object.entries(this.actions_).reduce((obj, [name, action]) => {
const hosts = action.hosts || [];
for (const host of hosts) {
if (!(host in obj)) {
obj[host] = {};
}
obj[host][name] = action;
}
return obj;
}, {});
},
actions() {
return this.host ? this.actionsByHost[this.host] : this.actions_;
},
},
methods: {
async loadActions() {
this.actions_ = await this.getActions();
},
async loadHosts() {
this.hosts = await this.getHosts();
},
async removeAction() {
if (!this.selectedAction || !(this.selectedAction in this.actions_) || !confirm('Are you sure that you want to remove this action from this device?')) {
return;
}
const action = this.actions_[this.selectedAction];
const hostIndex = action.hosts.indexOf(this.host);
if (hostIndex < 0) {
return;
}
action.hosts.splice(hostIndex, 1);
if (action.hosts.length === 0) {
delete this.actions_[this.selectedAction];
} else {
this.actions_[this.selectedAction] = action;
}
await this.saveActions(this.actions_);
await this.loadActions();
},
async runAction() {
if (!(this.selectedAction && this.host && this.selectedAction in this.actions)) {
return;
}
const action = this.actions[this.selectedAction];
this.error = null;
try {
this.response = await this.run(action, this.hosts[this.host]);
} catch (e) {
this.error = e.message;
}
},
toggleSelectedAction(name) {
this.response = null;
this.error = null;
this.selectedAction = this.selectedAction === name ? null : name;
},
},
created() {
this.loadHosts();
this.loadActions();
},
};
</script>
<style lang="scss" scoped>
form {
border-bottom: 1px solid rgba(0, 0, 0, 0.2);
.head {
display: flex;
align-items: center;
position: relative;
padding: 1em;
border-radius: 1em;
cursor: pointer;
.icon {
font-size: 1.2em;
margin-right: 1.5em;
}
.controls {
position: absolute;
right: 0.5em;
}
button {
background: white;
padding: 0.3em 1.3em;
margin-right: 0.5em;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 1em;
}
&:hover {
background-color: rgba(200, 255, 220, 0.7);
}
}
&.selected {
.head {
background-color: rgba(200, 255, 220, 1);
}
}
.body {
.desc {
display: flex;
flex-direction: column;
.row {
display: flex;
flex-direction: row;
padding: 0.5em;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
.label {
width: 20%;
min-width: 10em;
max-width: 30em;
}
&:hover {
background-color: rgba(200, 255, 220, 0.4);
}
}
}
}
}
</style>
<!-- vim:sw=2:ts=2:et: -->

View File

@ -1,6 +1,12 @@
<template>
<ul class="menu">
<li class="host" v-for="(host, hostname) in hosts" :key="hostname" :class="{ selected: hostname === selectedHost }" @click="$emit('select', 'host', hostname)">
<li
class="host"
v-for="(host, hostname) in hosts"
:key="hostname"
:class="{ selected: hostname === selectedHost }"
@click="$emit('select', 'host', hostname, hostname === selectedHost ? null : selectedHostOption)"
>
<i class="fas fa-hdd" /> &nbsp; {{ host.name }}
<ul class="host-menu" v-if="hostname === selectedHost">
<li

View File

@ -0,0 +1,76 @@
<template>
<div class="row hosts-selector">
<span>
<input type="checkbox" id="select-all-hosts" :checked="Object.keys(selectedHosts).length === Object.keys(hosts).length" @click="toggleSelectAll" />
<label for="select-all-hosts">Select All</label>
</span>
<span v-for="(host, name) in hosts" :key="name">
<input type="checkbox" :id="'host_' + name" :value="name" :checked="name in selectedHosts" data-type="host" @click="toggleHost" />
<label :for="'host_' + name" v-text="name" />
</span>
</div>
</template>
<script>
import mixins from '../utils';
export default {
name: 'MultipleHostSelector',
mixins: [mixins],
props: {
hosts: Object,
selected: {
type: Array,
default: () => [],
},
},
data() {
return {
selectedHosts: this.selected.reduce((obj, host) => {
obj[host] = true;
return obj;
}, {}),
};
},
methods: {
toggleHost(event) {
const hostname = event.target.value;
if (hostname in this.selectedHosts) {
delete this.selectedHosts[hostname];
} else {
this.selectedHosts[hostname] = true;
}
},
toggleSelectAll() {
if (Object.keys(this.selectedHosts).length === Object.keys(this.hosts).length) {
this.selectedHosts = {};
} else {
this.selectedHosts = Object.keys(this.hosts).reduce((obj, hostname) => {
obj[hostname] = true;
return obj;
}, {});
}
},
},
};
</script>
<style lang="scss" scoped>
.hosts-selector {
span {
display: flex;
align-items: center;
margin-right: 1em;
label {
margin-left: 0.3em;
}
}
}
</style>
<!-- vim:sw=2:ts=2:et: -->

View File

@ -66,6 +66,14 @@
<input type="text" name="iconClass" placeholder="FontAwesome icon class (e.g. 'fas fa-play')" />
</div>
<div class="row multiple-host-selector">
<div class="desc">
Install action on these devices
</div>
<MultipleHostSelector :hosts="hosts" :selected="[host.name]" />
</div>
<div class="row buttons">
<button type="submit" :disabled="loading"><i class="fas fa-save" /> &nbsp; Save Action</button>
<button type="button" @click="saveMode = false" :disabled="loading"><i class="fas fa-times" /> &nbsp; Cancel</button>
@ -80,15 +88,20 @@
<script>
import mixins from '../utils';
import Autocomplete from './Autocomplete';
import MultipleHostSelector from './MultipleHostSelector';
export default {
name: 'Run',
mixins: [mixins],
components: { Autocomplete },
props: {
host: Object,
},
components: {
Autocomplete,
MultipleHostSelector,
},
data() {
return {
plugins: {},
@ -96,6 +109,7 @@ export default {
saveMode: false,
actionResponse: null,
actionError: null,
hosts: {},
action: {
name: null,
args: [],
@ -198,22 +212,33 @@ export default {
const saveForm = event.target;
const displayName = saveForm.displayName.value.trim();
const iconClass = saveForm.iconClass.value.trim();
const hosts = [...saveForm.querySelectorAll('input[data-type="host"]:checked')].map(el => el.value);
if (!displayName.length) {
this.notify('Please specify an action name', 'No action name provided');
return;
}
if (!hosts.length) {
this.notify('Please specify at least one device where the action should run', 'No devices provided');
return;
}
const action = {
displayName: displayName,
iconClass: iconClass,
name: this.action.name,
args: this.getActionArgs(),
hosts: hosts,
};
await this.saveAction(action);
},
async loadHosts() {
this.hosts = await this.getHosts();
},
onActionChange(action) {
this.action.name = action;
this.action.args = [];
@ -222,6 +247,7 @@ export default {
created() {
this.clearAction();
this.loadHosts();
this.loadPlugins();
},
};
@ -310,19 +336,14 @@ form {
}
}
.code {
padding: 1em;
white-space: pre-wrap;
font-family: monospace;
border: 1px dotted rgba(0, 0, 0, 0.8);
border-radius: 1em;
.multiple-host-selector {
display: flex;
flex-direction: column;
align-items: flex-start !important;
margin-top: 2em;
&.response {
background: rgba(200, 255, 200, 0.3);
}
&.error {
background: rgba(255, 200, 200, 0.3);
.desc {
margin-bottom: 0.75em;
}
}
</style>