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"
|
||||
:selectedHostOption="selectedHostOption"
|
||||
:isAddHost="isAddHost"
|
||||
:isBackupMode="isBackupMode"
|
||||
:isRestoreMode="isRestoreMode"
|
||||
@select-host="selectHost"
|
||||
@select-host-option="selectHostOption"
|
||||
@select-add-host="selectAddHost"
|
||||
@select-backup-mode="selectBackupMode"
|
||||
@select-restore-mode="selectRestoreMode"
|
||||
/>
|
||||
|
||||
<div class="body">
|
||||
<NewHost @add="addHost" v-if="isAddHost" />
|
||||
<Backup v-else-if="isBackupMode" />
|
||||
<Restore v-else-if="isRestoreMode" />
|
||||
<LocalCommands v-else-if="selectedHost >= 0 && selectedHostOption === 'localProc'" />
|
||||
<RemoteCommands v-else-if="selectedHost >= 0 && selectedHostOption === 'remoteProc'" />
|
||||
<Run :host="hosts[selectedHost]" v-else-if="selectedHost >= 0 && selectedHostOption === 'run'" />
|
||||
|
@ -28,6 +34,8 @@ import NewHost from './NewHost';
|
|||
import EditHost from './EditHost';
|
||||
import LocalCommands from './LocalCommands';
|
||||
import RemoteCommands from './RemoteCommands';
|
||||
import Backup from './Backup';
|
||||
import Restore from './Restore';
|
||||
import Run from './Run';
|
||||
|
||||
export default {
|
||||
|
@ -39,6 +47,8 @@ export default {
|
|||
EditHost,
|
||||
LocalCommands,
|
||||
RemoteCommands,
|
||||
Backup,
|
||||
Restore,
|
||||
Run,
|
||||
},
|
||||
|
||||
|
@ -47,6 +57,8 @@ export default {
|
|||
selectedHost: -1,
|
||||
selectedHostOption: null,
|
||||
isAddHost: false,
|
||||
isBackupMode: false,
|
||||
isRestoreMode: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -64,6 +76,20 @@ export default {
|
|||
selectAddHost() {
|
||||
this.selectedHost = -1;
|
||||
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) {
|
||||
|
@ -83,7 +109,7 @@ export default {
|
|||
}
|
||||
|
||||
this.hosts.push(host);
|
||||
await this.saveHosts();
|
||||
await this.saveHosts(this.hosts);
|
||||
this.selectedHost = this.hosts.length - 1;
|
||||
this.isAddHost = false;
|
||||
} finally {
|
||||
|
@ -134,7 +160,10 @@ export default {
|
|||
},
|
||||
|
||||
created() {
|
||||
this.loadHosts();
|
||||
const self = this;
|
||||
this.loadHosts().then(hosts => {
|
||||
self.hosts = hosts;
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -72,23 +72,49 @@ export default {
|
|||
},
|
||||
|
||||
onKeyDown(event) {
|
||||
if (event.key === 'Enter') {
|
||||
this.showItems = false;
|
||||
if (!this.$refs.items) {
|
||||
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();
|
||||
}
|
||||
},
|
||||
|
||||
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) {
|
||||
if (event.key === 'Escape') {
|
||||
this.showItems = false;
|
||||
|
@ -100,19 +126,10 @@ export default {
|
|||
if (['ArrowUp', 'ArrowDown'].indexOf(event.key) >= 0) {
|
||||
switch (event.key) {
|
||||
case 'ArrowUp':
|
||||
if (this.selectedItem > 0) {
|
||||
this.selectedItem--;
|
||||
} else {
|
||||
this.selectedItem = this.filteredItems.length - 1;
|
||||
}
|
||||
this.selectPrev();
|
||||
break;
|
||||
|
||||
case 'ArrowDown':
|
||||
if (this.selectedItem >= this.filteredItems.length - 1) {
|
||||
this.selectedItem = this.filteredItems.length ? 0 : -1;
|
||||
} else {
|
||||
this.selectedItem++;
|
||||
}
|
||||
this.selectNext();
|
||||
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>
|
||||
</ul>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
|
@ -20,13 +23,15 @@ export default {
|
|||
selectedHost: Number,
|
||||
selectedHostOption: String,
|
||||
isAddHost: Boolean,
|
||||
isBackupMode: Boolean,
|
||||
isRestoreMode: Boolean,
|
||||
},
|
||||
|
||||
computed: {
|
||||
hostOptions() {
|
||||
return {
|
||||
localProc: {
|
||||
displayName: 'Local Procedures',
|
||||
displayName: 'Local Actions',
|
||||
iconClass: 'fas fa-puzzle-piece',
|
||||
},
|
||||
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.
|
||||
</div>
|
||||
|
||||
<form ref="form" @submit.prevent="runAction">
|
||||
<form ref="runForm" @submit.prevent="runAction">
|
||||
<div class="row action-head">
|
||||
<div class="action-name">
|
||||
<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="submit" :disabled="loading"><i class="fas fa-play" /> Run</button>
|
||||
</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>
|
||||
|
||||
<div class="code response" v-text="actionResponse" v-if="actionResponse && (actionResponse.length || Object.keys(actionResponse).length)" />
|
||||
|
@ -72,6 +93,7 @@ export default {
|
|||
return {
|
||||
plugins: {},
|
||||
pluginsLoading: false,
|
||||
saveMode: false,
|
||||
actionResponse: null,
|
||||
actionError: null,
|
||||
action: {
|
||||
|
@ -119,21 +141,23 @@ export default {
|
|||
this.actionError = null;
|
||||
},
|
||||
|
||||
async runAction() {
|
||||
this.loading = true;
|
||||
|
||||
const args = [...this.$refs.form.querySelectorAll('[data-type="arg"]')].map(el => {
|
||||
getActionArgs() {
|
||||
return [...this.$refs.runForm.querySelectorAll('[data-type="arg"]')].map(el => {
|
||||
return {
|
||||
name: el.name,
|
||||
value: el.value,
|
||||
};
|
||||
}, {});
|
||||
},
|
||||
|
||||
async runAction() {
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
this.actionResponse = await this.run(
|
||||
{
|
||||
name: this.action.name,
|
||||
args: [...args, ...this.action.args],
|
||||
args: this.getActionArgs(),
|
||||
},
|
||||
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) {
|
||||
this.action.name = action;
|
||||
this.action.args = [];
|
||||
|
@ -213,7 +257,6 @@ export default {
|
|||
|
||||
form {
|
||||
position: relative;
|
||||
max-width: 50em;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
|
@ -259,6 +302,16 @@ form {
|
|||
}
|
||||
}
|
||||
|
||||
.save-form {
|
||||
margin-top: 2em;
|
||||
|
||||
input[type='text'] {
|
||||
width: 60%;
|
||||
min-width: 20em;
|
||||
max-width: 35em;
|
||||
}
|
||||
}
|
||||
|
||||
.code {
|
||||
padding: 1em;
|
||||
white-space: pre-wrap;
|
||||
|
|
51
src/utils.js
51
src/utils.js
|
@ -61,7 +61,7 @@ export default {
|
|||
|
||||
return msg.data.response.output;
|
||||
} catch (e) {
|
||||
this.notify('Request error', e.toString());
|
||||
this.notify(e.toString(), 'Request error');
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
@ -71,14 +71,57 @@ export default {
|
|||
|
||||
try {
|
||||
const response = await browser.storage.local.get('hosts');
|
||||
this.hosts = JSON.parse(response.hosts);
|
||||
return JSON.parse(response.hosts);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async saveHosts() {
|
||||
await browser.storage.local.set({ hosts: JSON.stringify(this.hosts) });
|
||||
async saveHosts(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) {
|
||||
|
|
Loading…
Reference in a new issue