login fixes, more sensors

This commit is contained in:
Koushik Dutta
2021-09-02 22:00:37 -07:00
parent 34a8c1bd53
commit bb280779d5
19 changed files with 230 additions and 787 deletions

View File

@@ -1,349 +0,0 @@
<template>
<v-flex>
<v-card
v-if="managedDevices.devices.length"
raised
style="margin-bottom: 60px"
>
<v-card-title
class="green-gradient subtitle-1 text--white font-weight-light"
>
<font-awesome-icon size="sm" icon="database" />&nbsp;&nbsp;Managing Devices
</v-card-title>
<v-card-text>These devices were created by {{ name }}.</v-card-text>
<DeviceGroup :deviceGroup="managedDevices"></DeviceGroup>
</v-card>
<v-card raised >
<v-card-title
class="red-gradient subtitle-1 text--white font-weight-light"
>{{ script.npmPackage ? "Plugin Management" : "Edit Script" }}</v-card-title>
<v-form>
<v-container>
<v-layout>
<v-flex xs12>
<v-layout>
<v-flex xs12 v-if="!script.npmPackage">
<v-select
outlined
xs12
v-model="script.type"
label="Script Type"
:items="['Library', 'Event', 'Device']"
@input="onChange"
></v-select>
<v-card style="margin-bottom: 16px;" v-if="hasVars">
<v-card-title
class="small-header green-gradient white--text font-weight-light subtitle-2"
>{{ script.type || 'Library' }} Script</v-card-title>
<v-container>
<v-layout>
<v-flex>
<div v-if="script.type == 'Event'" class="caption">
Use the
<router-link
:to="`${getComponentViewPath('automation')}`"
>Automation component</router-link>&nbsp;to run this script when an event is triggered. The
"eventSource" and "eventData" local variables will contain information about the event.
</div>
<div v-if="!script.type || script.type == 'Library'" class="caption">
Library scripts are can be run using the "Test" button, or called from other scripts with custom arguments. Though Library scripts
can be run from
<router-link :to="`${getComponentViewPath('automation')}`">Automations,</router-link>&nbsp;an Event script is better suited for that, as Event Scripts expose extra
variables pertaining to the event.
</div>
<div v-if="script.type == 'Device'">
<div class="caption">
Device scripts enable the creation of custom devices within Scrypted. Choose the supported interfaces of your device,
then use the "Generate Device Code" button to get a default implementation.
</div>
<div class="caption">
DeviceProvider is a unique interface, in that it enables creation of "controllers" that may create one of more other devices. This can be used to add support for
third party hubs or discoverable devices. See the
<a
href="https://github.com/koush/scrypted-hue"
target="_blank"
>Hue</a> and
<a
href="https://github.com/koush/scrypted-lifx"
target="_blank"
>Lifx</a>
samples to get started.
<v-select
class="mt-2"
hint
xs12
multiple
chips
v-model="script.virtualDeviceInterfaces"
label="Interfaces"
:items="Object.keys(deviceProps.interfaces)"
@input="onChange"
></v-select>
<v-btn
small
color="info"
outlined
@click="generate"
>Generate Device Code</v-btn>
</div>
</div>
</v-flex>
</v-layout>
</v-container>
</v-card>
</v-flex>
</v-layout>
<v-card style="margin-bottom: 16px;" v-if="hasVars">
<v-card-title
class="small-header green-gradient white--text font-weight-light subtitle-2"
>Script Variables</v-card-title>
<v-container>
<v-layout>
<v-flex>
<ScriptVariablesPicker
v-model="script.vars"
:scriptType="script.type"
:actions="deviceProps.actions"
:addButton="!!!deviceProps.npmPackage"
@input="onChange"
></ScriptVariablesPicker>
</v-flex>
</v-layout>
</v-container>
</v-card>
<v-textarea
style="margin-top: 16px;"
v-if="!script.gistInSync && !script.npmPackage"
auto-grow
rows="10"
v-model="script.script"
outlined
label="Script"
@input="onChange"
></v-textarea>
<div v-else-if="!script.npmPackage" xs12 ref="gist" style="margin-top: 16px;"></div>
<div class="caption mt-2" style v-if="!script.npmPackage">
<a href="https://developer.scrypted.app" target="developer">Developer Reference</a>
</div>
<v-btn v-if="script.npmPackage" outlined color="blue" @click="reload" xs4>Reload</v-btn>
<v-btn v-else outlined color="blue" @click="test" xs4>Run Script</v-btn>
<v-btn outlined color="blue" @click="debug" xs4>Debug</v-btn>
<v-alert
style="margin-top: 16px;"
outlined
v-model="showCompilerResult"
dismissible
close-text="Close Alert"
type="success"
>
<div>
<pre class="black--text" style="white-space: pre-wrap;" v-html="compilerResult"></pre>
</div>
</v-alert>
</v-flex>
</v-layout>
</v-container>
</v-form>
<v-card-actions>
<v-btn text color="primary" @click="showStorage = !showStorage">Storage</v-btn>
<v-spacer></v-spacer>
<v-btn
v-if="script.npmPackage && !updateAvailable"
text
color="blue"
@click="openNpm"
xs4
>{{ script.npmPackage }}@{{ script.npmPackageVersion }}</v-btn>
<v-btn
v-else-if="script.npmPackage && updateAvailable"
color="orange"
@click="doInstall"
dark
>Install Update {{ updateAvailable }}</v-btn>
</v-card-actions>
</v-card>
<v-card raised v-if="showStorage" style="margin-top: 60px">
<v-card-title
class="green-gradient subtitle-1 text--white font-weight-light"
>Script Storage</v-card-title>
<v-form>
<v-container>
<v-layout>
<v-flex xs12>
<Storage v-model="script.configuration" @input="onChange"></Storage>
</v-flex>
</v-layout>
</v-container>
</v-form>
</v-card>
</v-flex>
</template>
<script>
import cloneDeep from "lodash/cloneDeep";
import DeviceGroup from "../../common/DeviceTable.vue";
import ScriptVariablesPicker from "./ScriptVariablesPicker.vue";
import axios from "axios";
import qs from "query-string";
import Storage from "../../common/Storage.vue";
import { getComponentWebPath, getComponentViewPath } from "../helpers";
import { checkUpdate, installNpm, getNpmPath } from "./plugin";
export default {
props: ["value", "id", "name", "deviceProps"],
components: {
DeviceGroup,
ScriptVariablesPicker,
Storage
},
data: function() {
return {
updateAvailable: false,
compilerResult: undefined,
script: Object.assign(cloneDeep(this.deviceProps.script), {
vars: cloneDeep(this.deviceProps.vars)
}),
showStorage: false,
scriptTypes: ["Library", "Device", "Event"].map(id => ({ id, text: id }))
};
},
mounted() {
this.doGist();
if (this.script.npmPackage) {
checkUpdate(this.script.npmPackage, this.script.npmPackageVersion).then(
updateAvailable => (this.updateAvailable = updateAvailable)
);
}
},
watch: {
id() {
this.doGist();
}
},
methods: {
getComponentViewPath,
doInstall() {
installNpm(this.script.npmPackage).then(() =>
this.$emit("refresh")
);
},
openNpm() {
window.open(getNpmPath(this.script.npmPackage), "npm");
},
openDeveloperReference(iface) {
window.open("https://developer.scrypted.app/#" + iface.toLowerCase());
},
generate() {
const body = this.script.virtualDeviceInterfaces
.map(iface => "interfaces=" + iface)
.join("&");
axios
.post(`${getComponentWebPath("script")}/generate`, body, {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
})
.then(response => {
this.script.script = response.data;
this.onChange();
});
},
onChange() {
if (!this.script.script) {
this.script.script = "";
}
this.$emit("input", this.script);
},
doGist() {
if (!this.deviceProps.gistEmbed) {
return;
}
const nativeWrite = document.write;
this.$refs.gist.innerHTML = "";
document.write = str => {
this.$refs.gist.innerHTML += str;
};
var tag = document.createElement("script");
tag.src = this.deviceProps.gistEmbed;
this.$refs.gist.appendChild(tag);
tag.onload = () => {
document.write = nativeWrite;
};
},
debug() {
axios
.post(
`${getComponentWebPath("script")}/debugTarget`,
qs.stringify({
thingId: this.script.id
})
)
.then(response => {
this.compilerResult = response.data;
});
},
reload() {
axios
.post(`${getComponentWebPath("script")}/reload/${this.script.id}`)
.then(response => {
this.compilerResult = response.data.length
? "Reload output:\n\n" + response.data
: this.script.npmPackage
? "Plugin reloaded."
: "Script reloaded.";
});
},
test() {
axios
.post(`${getComponentWebPath("script")}/test`, this.script)
.then(response => {
this.compilerResult = "Script output:\n\n" + response.data;
});
}
},
computed: {
hasVars() {
return (
!this.script.npmPackage ||
!this.script.npmPackageJson ||
(this.script.npmPackageJson.scrypted &&
this.script.npmPackageJson.scrypted.variables)
);
},
showCompilerResult: {
get() {
return !!this.compilerResult;
},
set(value) {
this.compilerResult = value ? this.compilerResult : "";
}
},
managedDevices() {
const devices = this.$store.state.scrypted.devices
.filter(
id =>
this.$store.state.systemState[id].metadata.value.ownerPlugin ===
this.id
)
.map(id => ({
id,
name: this.$store.state.systemState[id].name.value,
type: this.$store.state.systemState[id].type.value
}));
return {
devices
};
}
}
};
</script>

View File

@@ -1,14 +1,10 @@
<script>
import BasicComponent from "../BasicComponent.vue";
import PluginUpdate from "./PluginUpdate.vue";
import Stats from "./Stats.vue";
import PluginPid from "./PluginPid.vue";
export default {
mixins: [BasicComponent],
components: {
Stats,
},
methods: {
getOwnerColumn(device) {
return device.pluginId;
@@ -20,7 +16,6 @@ export default {
data() {
var self = this;
return {
// footer: "Stats",
cards: [
{
body: null,
@@ -38,22 +33,6 @@ export default {
"Integrate your existing smart home devices and services.",
title: "Install Plugin",
},
// {
// body: null,
// buttons: [
// {
// method: "POST",
// path: "new",
// title: "Create Script",
// click() {
// self.newDevice();
// },
// },
// ],
// description:
// "Write custom scripts to automate events or add new devices.",
// title: "Create New Script",
// },
],
resettable: true,
component: {

View File

@@ -1,61 +0,0 @@
<template>
<v-flex>
<v-card raised style="margin-bottom: 60px">
<v-card-title class="green-gradient subtitle-1 text--white font-weight-light">
<font-awesome-icon size="sm" icon="database" />
<span class="title font-weight-light">&nbsp;&nbsp;Managed Device</span>
</v-card-title>
<v-card-text></v-card-text>
<v-card-text>
<b>Native ID:</b>
{{ device.internalId }}
</v-card-text>
<v-card-actions>
<v-btn text color="primary" @click="showStorage = !showStorage">Storage</v-btn>
<v-spacer></v-spacer>
<v-btn text color="blue" :to="`/device/${ownerDevice.id}`">{{ ownerDevice.name }}</v-btn>
</v-card-actions>
</v-card>
<v-card v-if="showStorage" raised style="margin-bottom: 60px">
<v-card-title class="green-gradient subtitle-1 text--white font-weight-light">Script Storage</v-card-title>
<v-flex>
<Storage v-model="device.configuration" @input="onChange"></Storage>
</v-flex>
</v-card>
</v-flex>
</template>
<script>
import Storage from "../../common/Storage";
import cloneDeep from "lodash/cloneDeep";
export default {
props: ["value", "id", "name", "deviceProps"],
components: {
Storage
},
data: function() {
return {
device: cloneDeep(this.deviceProps.device),
showStorage: false
};
},
methods: {
onChange() {
this.$emit("input", this.device);
}
},
computed: {
ownerDevice() {
const id = this.id;
const ownerPlugin = this.$store.state.systemState[id].metadata.value
.ownerPlugin;
return {
id: ownerPlugin,
name: this.$store.state.systemState[ownerPlugin].name.value,
type: this.$store.state.systemState[ownerPlugin].type.value
};
}
}
};
</script>

View File

@@ -1,81 +0,0 @@
<template>
<v-layout row wrap>
<v-flex xs12 md5>
<v-text-field
outlined
v-model="lazyValue.variableName"
placeholder="variableName"
label="Variable Name"
@input="onInput"
></v-text-field>
</v-flex>
<v-flex xs12 md7>
<Select2
label="Variable"
v-model="lazyValue.variableValue"
:options="combinedActions"
:unselected="unselected"
@input="onInput"
></Select2>
</v-flex>
</v-layout>
</template>
<script>
import cloneDeep from "lodash/cloneDeep";
import Select2 from "../../common/Select2.vue";
import CustomValue from "../../common/CustomValue.vue";
function unassigned() {
return {
id: "unassigned",
text: "Assign Device to Variable"
};
}
export default {
mixins: [CustomValue],
props: {
scriptType: String,
actions: Array,
unselected: {
type: Object,
default: unassigned
}
},
computed: {
combinedActions: {
get: function() {
var actions = [];
if (this.scriptType == "Library") {
actions.push({
id: "library",
text: "Library Method Parameter"
});
}
actions = actions.concat(this.actions);
return actions;
}
}
},
components: {
Select2
},
methods: {
createLazyValue() {
return {
variableName: this.value.key,
variableValue:
cloneDeep(this.actions.find(e => e.id == this.value.value)) ||
unassigned()
};
},
createInputValue() {
return {
key: this.lazyValue.variableName,
value: this.lazyValue.variableValue.id
};
}
}
};
</script>

View File

@@ -1,40 +0,0 @@
<template>
<Grower
v-model="lazyValue"
:empty="unassigned"
@input="onInput"
addButton="Add Variable"
>
<template v-slot:default="slotProps">
<ScriptVariablePicker
:actions="actions"
:scriptType="scriptType"
v-model="slotProps.item"
@input="slotProps.onInput"
></ScriptVariablePicker>
</template>
</Grower>
</template>
<script>
import ScriptVariablePicker from "./ScriptVariablePicker.vue";
import CustomValue from "../../common/CustomValue.vue";
import Grower from "../../common/Grower.vue";
export default {
props: ["actions", "scriptType"],
mixins: [CustomValue],
components: {
Grower,
ScriptVariablePicker
},
computed: {
unassigned() {
return {
key: "",
value: "unassigned"
};
}
}
};
</script>

View File

@@ -1,126 +0,0 @@
<template>
<v-flex xs6>
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<v-btn
color="primary"
dark
v-bind="attrs"
v-on="on"
>Usage: {{ metrics[currentMetric].name }}</v-btn>
</template>
<v-list>
<v-list-item v-for="(item, index) in metrics" :key="index" @click="currentMetric = index">
<v-list-item-title>{{ item.name }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<VueApexCharts
v-if="chartData"
type="bar"
:options="chartData.options"
:series="chartData.series"
></VueApexCharts>
</v-flex>
</template>
<script>
import axios from "axios";
import VueApexCharts from "vue-apexcharts";
const metrics = [
{
name: "CPU Time",
key: "time",
},
{
name: "Memory",
key: "heap",
},
{
name: "HTTP",
key: "http",
},
{
name: "TCP",
key: "tcp",
},
{
name: "UDP",
key: "udp",
},
{
name: "Objects",
key: "object",
},
];
export default {
components: {
VueApexCharts,
},
data() {
return {
data: null,
currentMetric: 0,
metrics,
};
},
methods: {
getRandomInt() {
return Math.floor(Math.random() * (50 - 5 + 1)) + 5;
},
},
computed: {
chartData() {
if (!this.data) return;
const data = this.data;
const chartData = {
options: {
chart: {
id: "vuechart-example",
},
xaxis: {
categories: [],
tickAmount: 1,
labels: {
formatter: function (val) {
return val.toFixed(0);
},
},
},
plotOptions: {
bar: {
horizontal: true,
},
},
},
series: [
{
name: metrics[this.currentMetric].name,
data: [],
},
],
};
for (const id of Object.keys(data)) {
const device = this.$scrypted.systemManager.getDeviceById(id);
if (!device) continue;
chartData.options.xaxis.categories.push(device.name);
chartData.series[0].data.push(
data[id][metrics[this.currentMetric].key]
);
}
return chartData;
},
},
async mounted() {
const response = await axios.get("/web/component/script/stats");
this.data = response.data;
},
};
</script>