Support for saving/loading configuration/actions
This commit is contained in:
parent
f8deaa9cb2
commit
8fb4237e39
7 changed files with 244 additions and 36 deletions
|
@ -5,13 +5,19 @@
|
||||||
:selectedHost="selectedHost"
|
:selectedHost="selectedHost"
|
||||||
:selectedHostOption="selectedHostOption"
|
:selectedHostOption="selectedHostOption"
|
||||||
:isAddHost="isAddHost"
|
:isAddHost="isAddHost"
|
||||||
|
:isBackupMode="isBackupMode"
|
||||||
|
:isRestoreMode="isRestoreMode"
|
||||||
@select-host="selectHost"
|
@select-host="selectHost"
|
||||||
@select-host-option="selectHostOption"
|
@select-host-option="selectHostOption"
|
||||||
@select-add-host="selectAddHost"
|
@select-add-host="selectAddHost"
|
||||||
|
@select-backup-mode="selectBackupMode"
|
||||||
|
@select-restore-mode="selectRestoreMode"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<NewHost @add="addHost" v-if="isAddHost" />
|
<NewHost @add="addHost" v-if="isAddHost" />
|
||||||
|
<Backup v-else-if="isBackupMode" />
|
||||||
|
<Restore v-else-if="isRestoreMode" />
|
||||||
<LocalCommands v-else-if="selectedHost >= 0 && selectedHostOption === 'localProc'" />
|
<LocalCommands v-else-if="selectedHost >= 0 && selectedHostOption === 'localProc'" />
|
||||||
<RemoteCommands v-else-if="selectedHost >= 0 && selectedHostOption === 'remoteProc'" />
|
<RemoteCommands v-else-if="selectedHost >= 0 && selectedHostOption === 'remoteProc'" />
|
||||||
<Run :host="hosts[selectedHost]" v-else-if="selectedHost >= 0 && selectedHostOption === 'run'" />
|
<Run :host="hosts[selectedHost]" v-else-if="selectedHost >= 0 && selectedHostOption === 'run'" />
|
||||||
|
@ -28,6 +34,8 @@ import NewHost from './NewHost';
|
||||||
import EditHost from './EditHost';
|
import EditHost from './EditHost';
|
||||||
import LocalCommands from './LocalCommands';
|
import LocalCommands from './LocalCommands';
|
||||||
import RemoteCommands from './RemoteCommands';
|
import RemoteCommands from './RemoteCommands';
|
||||||
|
import Backup from './Backup';
|
||||||
|
import Restore from './Restore';
|
||||||
import Run from './Run';
|
import Run from './Run';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -39,6 +47,8 @@ export default {
|
||||||
EditHost,
|
EditHost,
|
||||||
LocalCommands,
|
LocalCommands,
|
||||||
RemoteCommands,
|
RemoteCommands,
|
||||||
|
Backup,
|
||||||
|
Restore,
|
||||||
Run,
|
Run,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -47,6 +57,8 @@ export default {
|
||||||
selectedHost: -1,
|
selectedHost: -1,
|
||||||
selectedHostOption: null,
|
selectedHostOption: null,
|
||||||
isAddHost: false,
|
isAddHost: false,
|
||||||
|
isBackupMode: false,
|
||||||
|
isRestoreMode: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -64,6 +76,20 @@ export default {
|
||||||
selectAddHost() {
|
selectAddHost() {
|
||||||
this.selectedHost = -1;
|
this.selectedHost = -1;
|
||||||
this.isAddHost = true;
|
this.isAddHost = true;
|
||||||
|
this.isBackupMode = false;
|
||||||
|
this.isRestoreMode = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
selectBackupMode() {
|
||||||
|
this.isAddHost = false;
|
||||||
|
this.isBackupMode = true;
|
||||||
|
this.isRestoreMode = false;
|
||||||
|
},
|
||||||
|
|
||||||
|
selectRestoreMode() {
|
||||||
|
this.isAddHost = false;
|
||||||
|
this.isBackupMode = false;
|
||||||
|
this.isRestoreMode = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
async addHost(form) {
|
async addHost(form) {
|
||||||
|
@ -83,7 +109,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hosts.push(host);
|
this.hosts.push(host);
|
||||||
await this.saveHosts();
|
await this.saveHosts(this.hosts);
|
||||||
this.selectedHost = this.hosts.length - 1;
|
this.selectedHost = this.hosts.length - 1;
|
||||||
this.isAddHost = false;
|
this.isAddHost = false;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -134,7 +160,10 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
this.loadHosts();
|
const self = this;
|
||||||
|
this.loadHosts().then(hosts => {
|
||||||
|
self.hosts = hosts;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -72,23 +72,49 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
onKeyDown(event) {
|
onKeyDown(event) {
|
||||||
if (event.key === 'Enter') {
|
if (!this.$refs.items) {
|
||||||
this.showItems = false;
|
return;
|
||||||
if (!this.$refs.items) {
|
}
|
||||||
return;
|
|
||||||
|
const selected = this.$refs.items.querySelector('.selected');
|
||||||
|
if (event.key === 'Enter' || event.key === ' ' || event.key === 'Tab') {
|
||||||
|
switch (event.key) {
|
||||||
|
case 'Enter':
|
||||||
|
case ' ':
|
||||||
|
this.showItems = false;
|
||||||
|
if (!selected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$refs.input.value = selected.innerText;
|
||||||
|
this.$emit('change', selected.innerText);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Tab':
|
||||||
|
this.selectNext();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const selected = this.$refs.items.querySelector('.selected');
|
|
||||||
if (!selected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$refs.input.value = selected.innerText;
|
|
||||||
this.$emit('change', selected.innerText);
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectPrev() {
|
||||||
|
if (this.selectedItem > 0) {
|
||||||
|
this.selectedItem--;
|
||||||
|
} else {
|
||||||
|
this.selectedItem = this.filteredItems.length - 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
selectNext() {
|
||||||
|
if (this.selectedItem >= this.filteredItems.length - 1) {
|
||||||
|
this.selectedItem = this.filteredItems.length ? 0 : -1;
|
||||||
|
} else {
|
||||||
|
this.selectedItem++;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
onKeyUp(event) {
|
onKeyUp(event) {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
this.showItems = false;
|
this.showItems = false;
|
||||||
|
@ -100,19 +126,10 @@ export default {
|
||||||
if (['ArrowUp', 'ArrowDown'].indexOf(event.key) >= 0) {
|
if (['ArrowUp', 'ArrowDown'].indexOf(event.key) >= 0) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case 'ArrowUp':
|
case 'ArrowUp':
|
||||||
if (this.selectedItem > 0) {
|
this.selectPrev();
|
||||||
this.selectedItem--;
|
|
||||||
} else {
|
|
||||||
this.selectedItem = this.filteredItems.length - 1;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'ArrowDown':
|
case 'ArrowDown':
|
||||||
if (this.selectedItem >= this.filteredItems.length - 1) {
|
this.selectNext();
|
||||||
this.selectedItem = this.filteredItems.length ? 0 : -1;
|
|
||||||
} else {
|
|
||||||
this.selectedItem++;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
32
src/options/Backup.vue
Normal file
32
src/options/Backup.vue
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<template>
|
||||||
|
<div class="page backup">
|
||||||
|
<h2>Backup the current configuration</h2>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<textarea v-text="loading ? 'Loading...' : config" :disabled="loading" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import mixins from '../utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Backup',
|
||||||
|
mixins: [mixins],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
config: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
const self = this;
|
||||||
|
this.loadConfig().then(config => {
|
||||||
|
self.config = config;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- vim:sw=2:ts=2:et: -->
|
|
@ -8,7 +8,10 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="add" :class="{ selected: isAddHost }" @click="$emit('select-add-host')"><i class="fas fa-plus" /> Add Device</li>
|
<li class="add" :class="{ selected: isAddHost }" @click="$emit('select-add-host')"><i class="fas fa-plus" /> Add Device</li>
|
||||||
|
<li class="backup" :class="{ selected: isBackupMode }" @click="$emit('select-backup-mode')"><i class="fas fa-save" /> Backup Configuration</li>
|
||||||
|
<li class="restore" :class="{ selected: isRestoreMode }" @click="$emit('select-restore-mode')"><i class="fas fa-window-restore" /> Restore Configuration</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -20,13 +23,15 @@ export default {
|
||||||
selectedHost: Number,
|
selectedHost: Number,
|
||||||
selectedHostOption: String,
|
selectedHostOption: String,
|
||||||
isAddHost: Boolean,
|
isAddHost: Boolean,
|
||||||
|
isBackupMode: Boolean,
|
||||||
|
isRestoreMode: Boolean,
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
hostOptions() {
|
hostOptions() {
|
||||||
return {
|
return {
|
||||||
localProc: {
|
localProc: {
|
||||||
displayName: 'Local Procedures',
|
displayName: 'Local Actions',
|
||||||
iconClass: 'fas fa-puzzle-piece',
|
iconClass: 'fas fa-puzzle-piece',
|
||||||
},
|
},
|
||||||
remoteProc: {
|
remoteProc: {
|
||||||
|
|
29
src/options/Restore.vue
Normal file
29
src/options/Restore.vue
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<template>
|
||||||
|
<div class="page restore">
|
||||||
|
<h2>Load configuration from a previous backup</h2>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<textarea v-text="loading ? 'Loading...' : config" :disabled="loading" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import mixins from '../utils';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Restore',
|
||||||
|
mixins: [mixins],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
config: {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.config = this.loadConfig();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- vim:sw=2:ts=2:et: -->
|
|
@ -7,7 +7,7 @@
|
||||||
URL.
|
URL.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form ref="form" @submit.prevent="runAction">
|
<form ref="runForm" @submit.prevent="runAction">
|
||||||
<div class="row action-head">
|
<div class="row action-head">
|
||||||
<div class="action-name">
|
<div class="action-name">
|
||||||
<Autocomplete :items="Object.keys(actions)" :value="action.name" :disabled="loading" placeholder="Action" @change="onActionChange" />
|
<Autocomplete :items="Object.keys(actions)" :value="action.name" :disabled="loading" placeholder="Action" @change="onActionChange" />
|
||||||
|
@ -49,6 +49,27 @@
|
||||||
<button type="button" @click="clearAction" :disabled="loading"><i class="fas fa-times" /> Clear Form</button>
|
<button type="button" @click="clearAction" :disabled="loading"><i class="fas fa-times" /> Clear Form</button>
|
||||||
<button type="submit" :disabled="loading"><i class="fas fa-play" /> Run</button>
|
<button type="submit" :disabled="loading"><i class="fas fa-play" /> Run</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row buttons" v-if="!saveMode">
|
||||||
|
<button type="button" @click="saveMode = true" :disabled="loading || !(action.name && action.name.length && action.name in actions)">
|
||||||
|
<i class="fas fa-save" /> Save Action
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form class="save-form" ref="saveForm" @submit.prevent="storeAction" v-if="saveMode">
|
||||||
|
<div class="row">
|
||||||
|
<input type="text" name="displayName" placeholder="Action display name" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<input type="text" name="iconClass" placeholder="FontAwesome icon class (e.g. 'fas fa-play')" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row buttons">
|
||||||
|
<button type="submit" :disabled="loading"><i class="fas fa-save" /> Save Action</button>
|
||||||
|
<button type="button" @click="saveMode = false" :disabled="loading"><i class="fas fa-times" /> Cancel</button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="code response" v-text="actionResponse" v-if="actionResponse && (actionResponse.length || Object.keys(actionResponse).length)" />
|
<div class="code response" v-text="actionResponse" v-if="actionResponse && (actionResponse.length || Object.keys(actionResponse).length)" />
|
||||||
|
@ -72,6 +93,7 @@ export default {
|
||||||
return {
|
return {
|
||||||
plugins: {},
|
plugins: {},
|
||||||
pluginsLoading: false,
|
pluginsLoading: false,
|
||||||
|
saveMode: false,
|
||||||
actionResponse: null,
|
actionResponse: null,
|
||||||
actionError: null,
|
actionError: null,
|
||||||
action: {
|
action: {
|
||||||
|
@ -119,21 +141,23 @@ export default {
|
||||||
this.actionError = null;
|
this.actionError = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
async runAction() {
|
getActionArgs() {
|
||||||
this.loading = true;
|
return [...this.$refs.runForm.querySelectorAll('[data-type="arg"]')].map(el => {
|
||||||
|
|
||||||
const args = [...this.$refs.form.querySelectorAll('[data-type="arg"]')].map(el => {
|
|
||||||
return {
|
return {
|
||||||
name: el.name,
|
name: el.name,
|
||||||
value: el.value,
|
value: el.value,
|
||||||
};
|
};
|
||||||
}, {});
|
}, {});
|
||||||
|
},
|
||||||
|
|
||||||
|
async runAction() {
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.actionResponse = await this.run(
|
this.actionResponse = await this.run(
|
||||||
{
|
{
|
||||||
name: this.action.name,
|
name: this.action.name,
|
||||||
args: [...args, ...this.action.args],
|
args: this.getActionArgs(),
|
||||||
},
|
},
|
||||||
this.host
|
this.host
|
||||||
);
|
);
|
||||||
|
@ -172,6 +196,26 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async storeAction(event) {
|
||||||
|
const saveForm = event.target;
|
||||||
|
const displayName = saveForm.displayName.value.trim();
|
||||||
|
const iconClass = saveForm.iconClass.value.trim();
|
||||||
|
|
||||||
|
if (!displayName.length) {
|
||||||
|
this.notify('Please specify an action name', 'No action name provided');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = {
|
||||||
|
displayName: displayName,
|
||||||
|
iconClass: iconClass,
|
||||||
|
name: this.action.name,
|
||||||
|
args: this.getActionArgs(),
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.saveAction(action);
|
||||||
|
},
|
||||||
|
|
||||||
onActionChange(action) {
|
onActionChange(action) {
|
||||||
this.action.name = action;
|
this.action.name = action;
|
||||||
this.action.args = [];
|
this.action.args = [];
|
||||||
|
@ -213,7 +257,6 @@ export default {
|
||||||
|
|
||||||
form {
|
form {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 50em;
|
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -259,6 +302,16 @@ form {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.save-form {
|
||||||
|
margin-top: 2em;
|
||||||
|
|
||||||
|
input[type='text'] {
|
||||||
|
width: 60%;
|
||||||
|
min-width: 20em;
|
||||||
|
max-width: 35em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.code {
|
.code {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
|
|
51
src/utils.js
51
src/utils.js
|
@ -61,7 +61,7 @@ export default {
|
||||||
|
|
||||||
return msg.data.response.output;
|
return msg.data.response.output;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.notify('Request error', e.toString());
|
this.notify(e.toString(), 'Request error');
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -71,14 +71,57 @@ export default {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await browser.storage.local.get('hosts');
|
const response = await browser.storage.local.get('hosts');
|
||||||
this.hosts = JSON.parse(response.hosts);
|
return JSON.parse(response.hosts);
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveHosts() {
|
async saveHosts(hosts) {
|
||||||
await browser.storage.local.set({ hosts: JSON.stringify(this.hosts) });
|
this.loading = true;
|
||||||
|
try {
|
||||||
|
await browser.storage.local.set({ hosts: JSON.stringify(hosts) });
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadActions() {
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await browser.storage.local.get('actions');
|
||||||
|
return JSON.parse(response.actions);
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async saveAction(action) {
|
||||||
|
const actions = await this.loadActions();
|
||||||
|
if (action.displayName in actions) {
|
||||||
|
if (!confirm('An action with this name already exists. Do you want to overwrite it?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actions[action.displayName] = action;
|
||||||
|
this.loading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await browser.storage.local.set({ actions: JSON.stringify(actions) });
|
||||||
|
this.notify('You can find this action under the Local Actions menu', 'Action saved');
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadConfig() {
|
||||||
|
const [hosts, actions] = await Promise.all([this.loadHosts(), this.loadActions()]);
|
||||||
|
return {
|
||||||
|
hosts: hosts,
|
||||||
|
actions: actions,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
formToHost(form) {
|
formToHost(form) {
|
||||||
|
|
Loading…
Reference in a new issue