Migrated execute panel
This commit is contained in:
parent
94ad14f23f
commit
856eb720b0
15 changed files with 932 additions and 5 deletions
2
platypush/backend/http/dist/index.html
vendored
2
platypush/backend/http/dist/index.html
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/css/chunk-1f0508e6.68e8f438.css
vendored
Normal file
1
platypush/backend/http/dist/static/css/chunk-1f0508e6.68e8f438.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/app.dbd07bd6.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/app.dbd07bd6.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/app.dbd07bd6.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/app.dbd07bd6.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
2
platypush/backend/http/dist/static/js/chunk-1f0508e6.9512b2e6.js
vendored
Normal file
2
platypush/backend/http/dist/static/js/chunk-1f0508e6.9512b2e6.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/dist/static/js/chunk-1f0508e6.9512b2e6.js.map
vendored
Normal file
1
platypush/backend/http/dist/static/js/chunk-1f0508e6.9512b2e6.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -18,6 +18,9 @@
|
|||
"camera.pi": {
|
||||
"class": "fas fa-camera"
|
||||
},
|
||||
"execute": {
|
||||
"class": "fa fa-play"
|
||||
},
|
||||
"light.hue": {
|
||||
"class": "fas fa-lightbulb"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
function autocomplete(inp, arr, listener) {
|
||||
/*the autocomplete function takes two arguments,
|
||||
the text field element and an array of possible autocompleted values:*/
|
||||
let currentFocus;
|
||||
/*execute a function when someone writes in the text field:*/
|
||||
inp.addEventListener("input", function() {
|
||||
let a, b, i, val = this.value;
|
||||
/*close any already open lists of autocompleted values*/
|
||||
closeAllLists();
|
||||
if (!val) { return false;}
|
||||
currentFocus = -1;
|
||||
/*create a DIV element that will contain the items (values):*/
|
||||
a = document.createElement("DIV");
|
||||
a.setAttribute("id", this.id + "autocomplete-list");
|
||||
a.setAttribute("class", "autocomplete-items");
|
||||
/*append the DIV element as a child of the autocomplete container:*/
|
||||
this.parentNode.appendChild(a);
|
||||
/*for each item in the array...*/
|
||||
for (i = 0; i < arr.length; i++) {
|
||||
/*check if the item starts with the same letters as the text field value:*/
|
||||
if (arr[i].substr(0, val.length).toUpperCase() === val.toUpperCase()) {
|
||||
/*create a DIV element for each matching element:*/
|
||||
b = document.createElement("DIV");
|
||||
/*make the matching letters bold:*/
|
||||
b.innerHTML = "<strong>" + arr[i].substr(0, val.length) + "</strong>";
|
||||
b.innerHTML += arr[i].substr(val.length);
|
||||
/*insert a input field that will hold the current array item's value:*/
|
||||
b.innerHTML += "<input type='hidden' value='" + arr[i] + "'>";
|
||||
/*execute a function when someone clicks on the item value (DIV element):*/
|
||||
b.addEventListener("click", function(e) {
|
||||
/*insert the value for the autocomplete text field:*/
|
||||
inp.value = this.getElementsByTagName("input")[0].value;
|
||||
/*trigger event listener if any:*/
|
||||
if (listener) {
|
||||
listener(e, inp.value);
|
||||
}
|
||||
/*close the list of autocompleted values,
|
||||
(or any other open lists of autocompleted values:*/
|
||||
closeAllLists();
|
||||
});
|
||||
a.appendChild(b);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
inp.addEventListener("keydown", function(e) {
|
||||
if (e.keyCode === 9) {
|
||||
/*Reset the list if tab has been pressed*/
|
||||
closeAllLists();
|
||||
}
|
||||
});
|
||||
|
||||
/*execute a function presses a key on the keyboard:*/
|
||||
inp.addEventListener("keydown", function(e) {
|
||||
let x = document.getElementById(this.id + "autocomplete-list");
|
||||
if (x) x = x.getElementsByTagName("div");
|
||||
if (e.keyCode === 40) {
|
||||
/*If the arrow DOWN key is pressed,
|
||||
increase the currentFocus variable:*/
|
||||
currentFocus++;
|
||||
/*and and make the current item more visible:*/
|
||||
addActive(x);
|
||||
} else if (e.keyCode === 38) { //up
|
||||
/*If the arrow UP key is pressed,
|
||||
decrease the currentFocus variable:*/
|
||||
currentFocus--;
|
||||
/*and and make the current item more visible:*/
|
||||
addActive(x);
|
||||
} else if (e.keyCode === 13) {
|
||||
/*If the ENTER key is pressed, prevent the form from being submitted,*/
|
||||
if (currentFocus > -1 && x && x.length) {
|
||||
e.preventDefault();
|
||||
/*and simulate a click on the "active" item:*/
|
||||
x[currentFocus].click();
|
||||
/*and restore the focus on the input element:*/
|
||||
this.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
function addActive(x) {
|
||||
/*a function to classify an item as "active":*/
|
||||
if (!x) return false;
|
||||
/*start by removing the "active" class on all items:*/
|
||||
removeActive(x);
|
||||
if (currentFocus >= x.length) currentFocus = 0;
|
||||
if (currentFocus < 0) currentFocus = (x.length - 1);
|
||||
/*add class "autocomplete-active":*/
|
||||
x[currentFocus].classList.add("autocomplete-active");
|
||||
}
|
||||
function removeActive(x) {
|
||||
/*a function to remove the "active" class from all autocomplete items:*/
|
||||
for (let i = 0; i < x.length; i++) {
|
||||
x[i].classList.remove("autocomplete-active");
|
||||
}
|
||||
}
|
||||
function closeAllLists(elmnt) {
|
||||
/*close all autocomplete lists in the document,
|
||||
except the one passed as an argument:*/
|
||||
const x = document.getElementsByClassName("autocomplete-items");
|
||||
for (let i = 0; i < x.length; i++) {
|
||||
if (elmnt !== x[i] && elmnt !== inp) {
|
||||
x[i].parentNode.removeChild(x[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*execute a function when someone clicks in the document:*/
|
||||
document.addEventListener("click", function (e) {
|
||||
closeAllLists(e.target);
|
||||
});
|
||||
}
|
||||
|
||||
export default autocomplete;
|
|
@ -0,0 +1,758 @@
|
|||
<template>
|
||||
<div class="row plugin execute-container">
|
||||
<Loading v-if="loading" />
|
||||
<div class="command-container">
|
||||
<div class="title">Execute Action</div>
|
||||
<form class="action-form" ref="actionForm" autocomplete="off" @submit.prevent="executeAction">
|
||||
<div class="request-type-container">
|
||||
<input type="radio" id="action-structured-input"
|
||||
:checked="structuredInput" @change="onInputTypeChange(true)">
|
||||
<label for="action-structured-input">Structured request</label>
|
||||
<input type="radio" id="action-raw-input"
|
||||
:checked="!structuredInput" @change="onInputTypeChange(false)">
|
||||
<label for="action-raw-input">Raw request</label>
|
||||
</div>
|
||||
|
||||
<div class="request structured-request" :class="structuredInput ? '' : 'hidden'">
|
||||
<div class="autocomplete">
|
||||
<label>
|
||||
<input ref="actionName" type="text" class="action-name"
|
||||
placeholder="Action Name" :disabled="running" v-model="action.name"
|
||||
@change="actionChanged=true" @blur="updateAction">
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="run-btn btn-primary" :disabled="running" title="Run">
|
||||
<i class="fas fa-play" />
|
||||
</button>
|
||||
|
||||
<div class="doc-container" v-if="selectedDoc">
|
||||
<div class="title">
|
||||
Action documentation
|
||||
</div>
|
||||
|
||||
<div class="doc html" v-html="selectedDoc" v-if="htmlDoc" />
|
||||
<div class="doc raw" v-text="selectedDoc" v-else />
|
||||
</div>
|
||||
|
||||
<div class="options" v-if="action.name in actions && (Object.keys(action.args).length ||
|
||||
action.supportsExtraArgs)">
|
||||
<div class="params" ref="params"
|
||||
v-if="Object.keys(action.args).length || action.supportsExtraArgs">
|
||||
<div class="param" :key="name" v-for="name in Object.keys(action.args)">
|
||||
<label>
|
||||
<input type="text" class="action-param-value" :disabled="running"
|
||||
:placeholder="name" v-model="action.args[name].value"
|
||||
@focus="selectAttrDoc(name)"
|
||||
@blur="resetAttrDoc">
|
||||
</label>
|
||||
|
||||
<div class="attr-doc-container mobile" v-if="selectedAttrDoc && selectedAttr === name">
|
||||
<div class="title">
|
||||
Attribute: <div class="attr-name" v-text="selectedAttr" />
|
||||
</div>
|
||||
|
||||
<div class="doc html" v-html="selectedAttrDoc" v-if="htmlDoc" />
|
||||
<div class="doc raw" v-text="selectedAttrDoc" v-else />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="extra-params" ref="extraParams" v-if="Object.keys(action.extraArgs).length">
|
||||
<div class="param extra-param" :key="i" v-for="i in Object.keys(action.extraArgs)">
|
||||
<label class="col-5">
|
||||
<input type="text" class="action-extra-param-name" :disabled="running"
|
||||
placeholder="Name" v-model="action.extraArgs[i].name">
|
||||
</label>
|
||||
<label class="col-5">
|
||||
<input type="text" class="action-extra-param-value" :disabled="running"
|
||||
placeholder="Value" v-model="action.extraArgs[i].value">
|
||||
</label>
|
||||
<label class="col-2 buttons">
|
||||
<button type="button" class="action-extra-param-del" title="Remove parameter"
|
||||
@click="removeParameter(i)">
|
||||
<i class="fas fa-trash" />
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="add-param" v-if="action.supportsExtraArgs">
|
||||
<button type="button" title="Add a parameter" @click="addParameter">
|
||||
<i class="fas fa-plus" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="attr-doc-container widescreen" v-if="selectedAttrDoc">
|
||||
<div class="title">
|
||||
Attribute: <div class="attr-name" v-text="selectedAttr" />
|
||||
</div>
|
||||
|
||||
<div class="doc html" v-html="selectedAttrDoc" v-if="htmlDoc" />
|
||||
<div class="doc raw" v-text="selectedAttrDoc" v-else />
|
||||
</div>
|
||||
|
||||
<div class="output-container">
|
||||
<div class="title" v-text="error != null ? 'Error' : 'Output'" v-if="error != null || response != null" />
|
||||
<div class="response" v-html="response" v-if="response != null" />
|
||||
<div class="error" v-html="error" v-else-if="error != null" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="request raw-request" :class="structuredInput ? 'hidden' : ''">
|
||||
<div class="first-row">
|
||||
<label>
|
||||
<textarea v-model="rawRequest" placeholder="Raw JSON request" />
|
||||
</label>
|
||||
<button type="submit" :disabled="running" class="run-btn btn-primary" title="Run">
|
||||
<i class="fas fa-play" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="output-container" v-if="response != null || error != null">
|
||||
<div class="title" v-text="error != null ? 'Error' : 'Output'" />
|
||||
<div class="error" v-html="error" v-if="error != null" />
|
||||
<div class="response" v-html="response" v-else-if="response != null" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="procedures-container">
|
||||
<div class="title">Execute Procedure</div>
|
||||
<div class="procedure" :class="selectedProcedure.name === name ? 'selected' : ''"
|
||||
v-for="name in Object.keys(procedures).sort()" :key="name" @click="updateProcedure(name, $event)">
|
||||
<form ref="procedureForm" autocomplete="off" @submit.prevent="executeProcedure">
|
||||
<div class="head">
|
||||
<div class="name col-no-margin-11" v-text="name" />
|
||||
<div class="btn-container col-no-margin-1">
|
||||
<button type="submit" class="run-btn btn-default" :disabled="running" title="Run"
|
||||
@click.stop="$emit('submit')" v-if="selectedProcedure.name === name">
|
||||
<i class="fas fa-play" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="params" v-if="selectedProcedure.name === name">
|
||||
<div class="param"
|
||||
v-for="argname in Object.keys(selectedProcedure.args)"
|
||||
:key="argname">
|
||||
<label>
|
||||
<input type="text" class="action-param-value" @click="$event.stopPropagation()" :disabled="running"
|
||||
:placeholder="argname" v-model="selectedProcedure.args[argname]">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import autocomplete from "@/components/elements/Autocomplete"
|
||||
import Utils from "@/Utils"
|
||||
import Loading from "@/components/Loading";
|
||||
|
||||
export default {
|
||||
name: "Execute",
|
||||
components: {Loading},
|
||||
mixins: [Utils],
|
||||
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
running: false,
|
||||
structuredInput: true,
|
||||
actionChanged: false,
|
||||
selectedDoc: undefined,
|
||||
selectedAttr: undefined,
|
||||
selectedAttrDoc: undefined,
|
||||
selectedProcedure: {
|
||||
name: undefined,
|
||||
args: {},
|
||||
},
|
||||
|
||||
response: undefined,
|
||||
error: undefined,
|
||||
htmlDoc: false,
|
||||
rawRequest: undefined,
|
||||
actions: {},
|
||||
plugins: {},
|
||||
procedures: {},
|
||||
action: {
|
||||
name: undefined,
|
||||
args: {},
|
||||
extraArgs: [],
|
||||
supportsExtraArgs: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async refresh() {
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
this.procedures = await this.request('inspect.get_procedures')
|
||||
this.plugins = await this.request('inspect.get_all_plugins', {html_doc: false})
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
for (const plugin of Object.values(this.plugins)) {
|
||||
if (plugin.html_doc)
|
||||
this.htmlDoc = true
|
||||
|
||||
for (const action of Object.values(plugin.actions)) {
|
||||
action.name = plugin.name + '.' + action.name
|
||||
action.supportsExtraArgs = !!action.has_kwargs
|
||||
delete action.has_kwargs
|
||||
this.actions[action.name] = action
|
||||
}
|
||||
}
|
||||
|
||||
const self = this
|
||||
autocomplete(this.$refs.actionName, Object.keys(this.actions).sort(), (evt, value) => {
|
||||
this.action.name = value
|
||||
self.updateAction()
|
||||
})
|
||||
},
|
||||
|
||||
updateAction() {
|
||||
if (!(this.action.name in this.actions))
|
||||
this.selectedDoc = undefined
|
||||
|
||||
if (!this.actionChanged || !(this.action.name in this.actions))
|
||||
return
|
||||
|
||||
this.loading = true
|
||||
try {
|
||||
this.action = {
|
||||
...this.actions[this.action.name],
|
||||
args: Object.entries(this.actions[this.action.name].args).reduce((args, entry) => {
|
||||
args[entry[0]] = {
|
||||
...entry[1],
|
||||
value: entry[1].default,
|
||||
}
|
||||
|
||||
return args
|
||||
}, {}),
|
||||
extraArgs: [],
|
||||
}
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
this.selectedDoc = this.parseDoc(this.action.doc)
|
||||
this.actionChanged = false
|
||||
this.response = undefined
|
||||
this.error = undefined
|
||||
},
|
||||
|
||||
parseDoc(docString) {
|
||||
if (!docString?.length || this.htmlDoc)
|
||||
return docString
|
||||
|
||||
let lineNo = 0
|
||||
let trailingSpaces = 0
|
||||
|
||||
return docString.split('\n').reduce((doc, line) => {
|
||||
if (++lineNo === 2)
|
||||
trailingSpaces = line.match(/^(\s*)/)[1].length
|
||||
|
||||
if (line.trim().startsWith('.. code-block'))
|
||||
return doc
|
||||
|
||||
doc += line.slice(trailingSpaces).replaceAll('``', '') + '\n'
|
||||
return doc
|
||||
}, '')
|
||||
},
|
||||
|
||||
updateProcedure(name, event) {
|
||||
if (event.target.getAttribute('type') === 'submit') {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.selectedProcedure.name === name) {
|
||||
this.selectedProcedure = {
|
||||
name: undefined,
|
||||
args: {},
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!(name in this.procedures)) {
|
||||
console.warn('Procedure not found: ' + name)
|
||||
return
|
||||
}
|
||||
|
||||
this.selectedProcedure = {
|
||||
name: name,
|
||||
args: (this.procedures[name].args || []).reduce((args, arg) => {
|
||||
args[arg] = undefined
|
||||
return args
|
||||
}, {})
|
||||
}
|
||||
},
|
||||
|
||||
addParameter() {
|
||||
this.action.extraArgs.push({
|
||||
name: undefined,
|
||||
value: undefined,
|
||||
})
|
||||
},
|
||||
|
||||
removeParameter(i) {
|
||||
this.action.extraArgs.pop(i)
|
||||
},
|
||||
|
||||
selectAttrDoc(name) {
|
||||
this.response = undefined
|
||||
this.error = undefined
|
||||
this.selectedAttr = name
|
||||
this.selectedAttrDoc = this.parseDoc(this.action.args[name].doc)
|
||||
},
|
||||
|
||||
resetAttrDoc() {
|
||||
this.response = undefined
|
||||
this.error = undefined
|
||||
this.selectedAttr = undefined
|
||||
this.selectedAttrDoc = undefined
|
||||
},
|
||||
|
||||
onInputTypeChange(structuredInput) {
|
||||
this.structuredInput = structuredInput
|
||||
this.response = undefined
|
||||
this.error = undefined
|
||||
},
|
||||
|
||||
onResponse(response) {
|
||||
this.response = '<pre>' + JSON.stringify(response, null, 2) + '</pre>'
|
||||
this.error = undefined
|
||||
},
|
||||
|
||||
onError(error) {
|
||||
this.response = undefined
|
||||
this.error = error
|
||||
},
|
||||
|
||||
onDone() {
|
||||
this.running = false
|
||||
},
|
||||
|
||||
executeAction() {
|
||||
if (!this.action.name && !this.rawRequest || this.running)
|
||||
return
|
||||
|
||||
this.running = true
|
||||
if (this.structuredInput) {
|
||||
const args = {
|
||||
...Object.entries(this.action.args).reduce((args, param) => {
|
||||
if (param[1].value != null) {
|
||||
let value = param[1].value
|
||||
try {
|
||||
value = JSON.parse(value)
|
||||
} catch (e) {
|
||||
console.debug('Not a valid JSON value')
|
||||
console.debug(value)
|
||||
}
|
||||
|
||||
args[param[0]] = value
|
||||
}
|
||||
return args
|
||||
}, {}),
|
||||
|
||||
...this.action.extraArgs.reduce((args, param) => {
|
||||
let value = args[param.value]
|
||||
try {
|
||||
value = JSON.parse(value)
|
||||
} catch (e) {
|
||||
console.debug('Not a valid JSON value')
|
||||
console.debug(value)
|
||||
}
|
||||
|
||||
args[param.name] = value
|
||||
return args
|
||||
}, {})
|
||||
}
|
||||
|
||||
this.request(this.action.name, args).then(this.onResponse).catch(this.onError).finally(this.onDone)
|
||||
} else {
|
||||
let request = this.rawRequest
|
||||
try {
|
||||
request = JSON.parse(this.rawRequest)
|
||||
} catch (e) {
|
||||
this.notify({
|
||||
error: true,
|
||||
title: 'Invalid JSON request',
|
||||
text: e.toString(),
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.execute(request).then(this.onResponse).catch(this.onError).finally(this.onDone)
|
||||
}
|
||||
},
|
||||
|
||||
executeProcedure(event) {
|
||||
if (!this.selectedProcedure.name || this.running)
|
||||
return
|
||||
|
||||
event.stopPropagation()
|
||||
this.running = true
|
||||
const args = {
|
||||
...Object.entries(this.selectedProcedure.args).reduce((args, param) => {
|
||||
if (param[1] != null) {
|
||||
let value = param[1]
|
||||
try {
|
||||
value = JSON.parse(value)
|
||||
} catch (e) {
|
||||
console.debug('Not a valid JSON value')
|
||||
console.debug(value)
|
||||
}
|
||||
|
||||
args[param[0]] = value
|
||||
}
|
||||
return args
|
||||
}, {}),
|
||||
}
|
||||
|
||||
this.request('procedure.' + this.selectedProcedure.name, args)
|
||||
.then(this.onResponse).catch(this.onError).finally(this.onDone)
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.refresh()
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "vars";
|
||||
@import "~@/style/autocomplete.scss";
|
||||
|
||||
$params-desktop-width: 30em;
|
||||
$params-tablet-width: 20em;
|
||||
|
||||
.execute-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: $default-fg-2;
|
||||
font-weight: 400;
|
||||
border-bottom: $default-border-2;
|
||||
border-radius: 0 0 1em 1em;
|
||||
|
||||
form {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.action-form {
|
||||
padding: 1em .5em;
|
||||
}
|
||||
|
||||
.title {
|
||||
background: $title-bg;
|
||||
padding: .5em;
|
||||
border: $title-border;
|
||||
box-shadow: $title-shadow;
|
||||
font-size: 1.1em;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.request-type-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
|
||||
label {
|
||||
margin: 0 1em 0 .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.request {
|
||||
margin: 0 .5em;
|
||||
|
||||
form {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.autocomplete {
|
||||
width: 80%;
|
||||
max-width: 60em;
|
||||
}
|
||||
|
||||
.action-name {
|
||||
box-shadow: $action-name-shadow;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
[type=submit] {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
.options {
|
||||
display: flex;
|
||||
margin-top: .5em;
|
||||
margin-bottom: 1.5em;
|
||||
padding-top: .5em;
|
||||
|
||||
@include until($tablet) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.params {
|
||||
@include until($tablet) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include from($tablet) {
|
||||
width: $params-tablet-width;
|
||||
margin-right: 1.5em;
|
||||
}
|
||||
|
||||
@include from($desktop) {
|
||||
width: $params-desktop-width;
|
||||
}
|
||||
|
||||
.param {
|
||||
margin-bottom: .25em;
|
||||
@include until($tablet) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.action-param-value {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.add-param {
|
||||
width: 100%;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
background: $extra-params-btn-bg;
|
||||
border: $title-border;
|
||||
}
|
||||
}
|
||||
|
||||
.extra-param {
|
||||
display: flex;
|
||||
margin-bottom: .5em;
|
||||
|
||||
.action-extra-param-del {
|
||||
border: 0;
|
||||
text-align: right;
|
||||
padding: 0 .5em;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: .25em;
|
||||
|
||||
button {
|
||||
background: none;
|
||||
|
||||
&:hover {
|
||||
color: $default-hover-fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.doc-container,
|
||||
.output-container {
|
||||
margin-top: .5em;
|
||||
.doc {
|
||||
&.raw {
|
||||
white-space: pre;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.attr-doc-container {
|
||||
@include from($tablet) {
|
||||
width: calc(100% - #{$params-tablet-width} - 2em);
|
||||
}
|
||||
|
||||
@include from($desktop) {
|
||||
width: calc(100% - #{$params-desktop-width} - 2em);
|
||||
}
|
||||
|
||||
.doc {
|
||||
white-space: pre-line;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&.widescreen {
|
||||
@include until($tablet) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.mobile {
|
||||
width: 100%;
|
||||
@include from($tablet) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.doc-container,
|
||||
.attr-doc-container {
|
||||
.doc {
|
||||
padding: 1em !important;
|
||||
|
||||
&.raw {
|
||||
font-family: monospace;
|
||||
font-size: .8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.output-container, .doc-container, .attr-doc-container {
|
||||
max-height: 50vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
font-weight: normal;
|
||||
font-size: 1em;
|
||||
padding: .5em;
|
||||
background: $section-title-bg;
|
||||
border-radius: .5em;
|
||||
|
||||
.attr-name {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.response,
|
||||
.error,
|
||||
.doc {
|
||||
height: 100%;
|
||||
padding: .5em .5em 0 .5em;
|
||||
border-radius: 0 0 1em 1em;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.response {
|
||||
background: $response-bg;
|
||||
border: $response-border;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: $error-bg;
|
||||
border: $error-border;
|
||||
}
|
||||
|
||||
.doc {
|
||||
background: $doc-bg;
|
||||
border: $doc-border;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 10em;
|
||||
margin-bottom: .5em;
|
||||
padding: .5em;
|
||||
border: $default-border-2;
|
||||
border-radius: 1em;
|
||||
box-shadow: $border-shadow-bottom-right;
|
||||
outline: none;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid $default-hover-fg-2;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: 1px solid $selected-fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.raw-request {
|
||||
.first-row {
|
||||
@include until($tablet) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@include from($tablet) {
|
||||
width: 80%;
|
||||
max-width: 60em;
|
||||
}
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
button {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.procedures-container {
|
||||
.procedure {
|
||||
background: $background-color;
|
||||
border-bottom: $default-border-2;
|
||||
padding: 1.5em .5em;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: $hover-bg;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: $selected-bg;
|
||||
}
|
||||
|
||||
form {
|
||||
background: none;
|
||||
display: flex;
|
||||
margin-bottom: 0 !important;
|
||||
flex-direction: column;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
button {
|
||||
background: $procedure-submit-btn-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.run-btn {
|
||||
border-radius: 2em;
|
||||
padding: .5em .75em;
|
||||
|
||||
&:hover {
|
||||
opacity: .8;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,13 @@
|
|||
$title-bg: #eee;
|
||||
$title-border: 1px solid #ddd;
|
||||
$title-shadow: 0 3px 3px 0 rgba(187,187,187,0.75);
|
||||
$action-name-shadow: 1px 1px 1px 1px #ddd;
|
||||
$extra-params-btn-bg: #eee;
|
||||
$response-bg: #edfff2;
|
||||
$response-border: 1px dashed #98ff98;
|
||||
$error-bg: #ffbcbc;
|
||||
$error-border: 1px dashed #ff5353;
|
||||
$doc-bg: #e8feff;
|
||||
$doc-border: 1px dashed #84f9ff;
|
||||
$procedure-submit-btn-bg: #ebffeb;
|
||||
$section-title-bg: rgba(0, 0, 0, .04);
|
32
platypush/backend/http/webapp/src/style/autocomplete.scss
Normal file
32
platypush/backend/http/webapp/src/style/autocomplete.scss
Normal file
|
@ -0,0 +1,32 @@
|
|||
.autocomplete {
|
||||
/*the container must be positioned relative:*/
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.autocomplete-items {
|
||||
position: absolute;
|
||||
border: $default-border-2;
|
||||
border-bottom: none;
|
||||
border-top: none;
|
||||
z-index: 99;
|
||||
/*position the autocomplete items to be the same width as the container:*/
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
div {
|
||||
padding: 1em;
|
||||
cursor: pointer;
|
||||
border-bottom: $default-border-2;
|
||||
background-color: $background-color;
|
||||
|
||||
&:hover {
|
||||
background-color: $hover-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.autocomplete-active {
|
||||
background-color: $selected-bg !important;
|
||||
}
|
|
@ -85,6 +85,11 @@ export default {
|
|||
this.request('config.get_device_id'),
|
||||
])
|
||||
|
||||
this.initializeDefaultViews()
|
||||
},
|
||||
|
||||
initializeDefaultViews() {
|
||||
this.plugins.execute = {}
|
||||
this.plugins.switches = {}
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue