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,12 +1,12 @@
{
"name": "@scrypted/core",
"version": "0.0.58",
"version": "0.0.59",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/core",
"version": "0.0.58",
"version": "0.0.59",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/sdk": "file:../../sdk",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/core",
"version": "0.0.58",
"version": "0.0.59",
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -49,7 +49,7 @@ export class Automation extends ScryptedDeviceBase implements OnOff {
let device: any;
if (id === 'javascript') {
device = new Javascript(systemManager, eventSource, eventDetails, eventData);
device = new Javascript(systemManager, eventSource, eventDetails, eventData, this.log);
}
else {
device = systemManager.getDeviceById(id);

View File

@@ -1,23 +1,25 @@
import { EventDetails, ScryptedDevice, SystemManager } from "@scrypted/sdk";
import { Logger, EventDetails, ScryptedDevice, SystemManager } from "@scrypted/sdk";
export class Javascript {
systemManager: SystemManager;
eventSource: ScryptedDevice;
eventDetails: EventDetails;
eventData: any;
log: Logger;
constructor(systemManager: SystemManager, eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any) {
constructor(systemManager: SystemManager, eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any, log: Logger) {
this.systemManager = systemManager;
this.eventSource = eventSource;
this.eventDetails = eventDetails;
this.eventData = eventData;
this.log = log;
}
run(script: string) {
const f = eval(`(function script(systemManager, eventSource, eventDetails, eventData) {
const f = eval(`(function script(systemManager, eventSource, eventDetails, eventData, log) {
${script}
})`);
f(this.systemManager, this.eventSource, this.eventDetails, this.eventData);
f(this.systemManager, this.eventSource, this.eventDetails, this.eventData, this.log);
}
}

View File

@@ -12,7 +12,7 @@
<v-spacer></v-spacer>
<v-menu left bottom>
<template v-slot:activator="{ on }">
<v-btn v-on="on" text>{{$store.state.username}}</v-btn>
<v-btn v-on="on" text>{{ $store.state.username }}</v-btn>
</template>
<v-list>
<v-list-item class="font-weight-light" @click="reload">
@@ -30,10 +30,16 @@
<v-menu left bottom>
<template v-slot:activator="{ on }">
<v-btn icon v-on="on">
<v-badge :value="$store.state.scrypted.alerts.length" color="red" overlap>
<template v-slot:badge>{{ $store.state.scrypted.alerts.length }}</template>
<v-icon>notifications</v-icon>
<v-btn small icon v-on="on">
<v-badge
:value="$store.state.scrypted.alerts.length"
color="red"
overlap
>
<template v-slot:badge>{{
$store.state.scrypted.alerts.length
}}</template>
<v-icon small>notifications</v-icon>
</v-badge>
</v-btn>
</template>
@@ -46,36 +52,50 @@
@click="doAlert(alert)"
>
<v-list-item-icon>
<v-icon x-small style="color: #a9afbb;">{{ getAlertIcon(alert) }}</v-icon>
<v-icon x-small style="color: #a9afbb">{{
getAlertIcon(alert)
}}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="caption">{{ alert.title }}</v-list-item-title>
<v-list-item-subtitle class="caption">{{ alert.message }}</v-list-item-subtitle>
<v-list-item-subtitle class="caption">{{ friendlyTime(alert.timestamp) }}</v-list-item-subtitle>
<v-list-item-title class="caption">{{
alert.title
}}</v-list-item-title>
<v-list-item-subtitle class="caption">{{
alert.message
}}</v-list-item-subtitle>
<v-list-item-subtitle class="caption">{{
friendlyTime(alert.timestamp)
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-divider v-if="$store.state.scrypted.alerts.length"></v-divider>
<v-list-item v-if="!$store.state.scrypted.alerts.length" class="font-weight-light">
<v-list-item
v-if="!$store.state.scrypted.alerts.length"
class="font-weight-light"
>
<v-list-item-content>
<v-list-item-title class="caption">No notifications.</v-list-item-title>
<v-list-item-title class="caption"
>No notifications.</v-list-item-title
>
</v-list-item-content>
</v-list-item>
<v-list-item v-else class="font-weight-light" @click="clearAlerts">
<v-list-item-icon>
<v-icon x-small style="color: #a9afbb;">fa-trash</v-icon>
<v-icon x-small style="color: #a9afbb">fa-trash</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="caption">Clear All Alerts</v-list-item-title>
<v-list-item-title class="caption"
>Clear All Alerts</v-list-item-title
>
</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
<v-menu left bottom v-if="$store.state.menu">
<template v-slot:activator="{ on }">
<v-btn icon v-on="on">
<v-icon>more_vert</v-icon>
<v-btn small icon v-on="on">
<v-icon small>more_vert</v-icon>
</v-btn>
</template>
@@ -87,11 +107,17 @@
@click="menuItem.click"
>
<v-list-item-icon v-if="menuItem.icon">
<v-icon x-small style="color: #a9afbb;">{{ menuItem.icon }}</v-icon>
<v-icon x-small style="color: #a9afbb">{{
menuItem.icon
}}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title class="caption">{{ menuItem.title }}</v-list-item-title>
<v-list-item-subtitle class="caption">{{ menuItem.subtitle }}</v-list-item-subtitle>
<v-list-item-title class="caption">{{
menuItem.title
}}</v-list-item-title>
<v-list-item-subtitle class="caption">{{
menuItem.subtitle
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
@@ -121,39 +147,57 @@
persistent
max-width="600px"
>
<v-card dark color="purple">
<v-card-title>
<span class="headline">Scrypted Management Console</span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12>
<v-text-field v-model="username" color="white" label="User Name"></v-text-field>
<v-text-field v-model="password" color="white" type="password" label="Password"></v-text-field>
<v-checkbox
v-if="$store.state.hasLogin === true"
v-model="changePassword"
label="Change Password"
></v-checkbox>
<v-text-field
v-model="confirmPassword"
v-if="changePassword || $store.state.hasLogin === false"
color="white"
type="password"
label="Confirm Password"
:rules="passwordRules"
></v-text-field>
</v-flex>
</v-layout>
<div>{{ loginResult }}</div>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn text @click="doLogin">Log In</v-btn>
</v-card-actions>
</v-card>
<v-form @submit="doLogin">
<v-card dark color="purple">
<v-card-title>
<span class="headline">Scrypted Management Console</span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12>
<v-text-field
v-model="username"
color="white"
label="User Name"
></v-text-field>
<v-text-field
v-model="password"
color="white"
type="password"
label="Password"
></v-text-field>
<v-checkbox
v-if="$store.state.hasLogin === true"
v-model="changePassword"
label="Change Password"
></v-checkbox>
<v-text-field
v-model="newPassword"
v-if="changePassword"
color="white"
type="password"
label="New Password"
></v-text-field>
<v-text-field
v-model="confirmPassword"
v-if="changePassword || $store.state.hasLogin === false"
color="white"
type="password"
label="Confirm Password"
:rules="[(changePassword ? confirmPassword !== newPassword : confirmPassword !== password) ? 'Passwords do not match.' : true]"
></v-text-field>
</v-flex>
</v-layout>
<div>{{ loginResult }}</div>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn type="submit" text @click="doLogin">Log In</v-btn>
</v-card-actions>
</v-card>
</v-form>
</v-dialog>
<v-dialog
v-else-if="$store.state.isConnected === false"
@@ -174,7 +218,14 @@
</v-dialog>
<v-content elevation="-2">
<v-container grid-list-xs grid-list-xl grid-list-md grid-list-sm grid-list-lg fluid>
<v-container
grid-list-xs
grid-list-xl
grid-list-md
grid-list-sm
grid-list-lg
fluid
>
<v-fade-transition mode="out-in">
<router-view v-if="$store.state.isConnected"></router-view>
</v-fade-transition>
@@ -192,12 +243,12 @@ import { removeAlert, getAlertIcon } from "./components/helpers";
import router from "./router";
import Vue from "vue";
import store from './store';
import store from "./store";
import "./client";
const PushConnectionManager = window["pushconnect"].PushConnectionManager;
var pushConnectionPromise;
Vue.prototype.$pushconnect = function() {
Vue.prototype.$pushconnect = function () {
if (pushConnectionPromise) {
return pushConnectionPromise;
}
@@ -208,11 +259,11 @@ Vue.prototype.$pushconnect = function() {
{
urls: ["turn:n0.clockworkmod.com", "turn:n1.clockworkmod.com"],
username: "foo",
credential: "bar"
}
]
credential: "bar",
},
],
}
).then(rtcManager => {
).then((rtcManager) => {
// console.log('persistent gcm connection created', rtcManager != null);
// console.log(rtcManager.registrationId);
return rtcManager;
@@ -224,23 +275,23 @@ Vue.prototype.$pushconnect = function() {
export default {
name: "App",
components: {
Drawer
Drawer,
},
mounted() {
this.$vuetify.theme.dark = true;
this._timer = setInterval(
function() {
function () {
this.$data.now = Date.now();
}.bind(this),
1000
);
},
destroyed: function() {
destroyed: function () {
clearInterval(this._timer);
},
methods: {
reconnect() {
this.$connectScrypted().catch(e => (this.loginResult = e.toString()));
this.$connectScrypted().catch((e) => (this.loginResult = e.toString()));
},
reload() {
window.location.reload();
@@ -250,7 +301,7 @@ export default {
},
doCloudLogin() {
var encode = qs.stringify({
redirect_uri: "/endpoint/@scrypted/core/public/"
redirect_uri: "/endpoint/@scrypted/core/public/",
});
window.location = `https://home.scrypted.app/_punch/login?${encode}`;
@@ -258,36 +309,41 @@ export default {
doLogin() {
const body = {
username: this.username,
password: this.password
password: this.password,
};
if (this.changePassword || this.$store.state.hasLogin === false) {
if (this.password !== this.confirmPassword) {
if (this.$store.state.hasLogin === false && this.password !== this.confirmPassword) {
this.loginResult = 'Passwords do not match.';
return;
}
body.confirm_password = this.confirmPassword;
else if (this.changePassword && this.newPassword !== this.confirmPassword) {
this.loginResult = 'Passwords do not match.';
return;
}
body.change_password = this.confirmPassword;
}
this.loginResult = "";
axios
.post("/login", qs.stringify(body), {
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
"Content-Type": "application/x-www-form-urlencoded",
},
})
.then(response => {
.then((response) => {
if (response.data.error) {
this.loginResult = response.data.error;
return;
}
window.location.reload();
})
.catch(e => {
.catch((e) => {
this.loginResult = e.toString();
});
},
async clearAlerts() {
const alerts = await this.$scrypted.systemManager.getComponent('alerts')
await alerts.clearAlerts();
const alerts = await this.$scrypted.systemManager.getComponent("alerts");
await alerts.clearAlerts();
},
getAlertIcon,
removeAlert,
@@ -325,7 +381,7 @@ export default {
},
alertConvert(alertPath) {
return alertPath.replace("/web/", "/");
}
},
},
created() {
router.beforeEach((to, from, next) => {
@@ -345,6 +401,7 @@ export default {
username: null,
password: null,
confirmPassword: null,
newPassword: null,
loginResult: undefined,
passwordRules: [
() => {
@@ -352,8 +409,8 @@ export default {
return "Passwords do not match.";
}
return true;
}
]
},
],
};
},
};

View File

@@ -36,6 +36,7 @@
<v-card-title class="orange-gradient subtitle-1 font-weight-light">
{{ name || "No Device Name" }}
<v-layout
mr-1
row
justify-end
align-center
@@ -321,25 +322,26 @@
<v-flex xs12 md6 lg6>
<v-layout row wrap>
<v-flex xs12 v-for="iface in noCardInterfaces" :key="iface">
<component v-if="name != null"
<component
v-if="name != null"
:value="deviceState"
:device="device"
:is="iface"
></component>
</v-flex>
<v-flex xs12 v-for="iface in cardInterfaces" :key="iface">
<v-card v-if="name != null">
<v-card-title
class="red-gradient white--text subtitle-1 font-weight-light"
>{{ iface }}</v-card-title
>
<component
:value="deviceState"
:device="device"
:is="iface"
></component>
</v-flex>
<v-flex xs12 v-for="iface in cardInterfaces" :key="iface">
<v-card v-if="name != null">
<v-card-title
class="red-gradient white--text subtitle-1 font-weight-light"
>{{ iface }}</v-card-title
>
<component
:value="deviceState"
:device="device"
:is="iface"
></component>
</v-card>
</v-card>
</v-flex>
<v-flex v-if="showLogs" ref="logsEl">
@@ -386,6 +388,8 @@ import VideoCamera from "../interfaces/VideoCamera.vue";
import Thermometer from "../interfaces/sensors/Thermometer.vue";
import HumiditySensor from "../interfaces/sensors/HumiditySensor.vue";
import EntrySensor from "../interfaces/sensors/EntrySensor.vue";
import MotionSensor from "../interfaces/sensors/MotionSensor.vue";
import AudioSensor from "../interfaces/sensors/AudioSensor.vue";
import OccupancySensor from "../interfaces/sensors/OccupancySensor.vue";
import Settings from "../interfaces/Settings.vue";
import StartStop from "../interfaces/StartStop.vue";
@@ -408,6 +412,8 @@ import Vue from "vue";
const cardHeaderInterfaces = [
ScryptedInterface.OccupancySensor,
ScryptedInterface.EntrySensor,
ScryptedInterface.MotionSensor,
ScryptedInterface.AudioSensor,
ScryptedInterface.HumiditySensor,
ScryptedInterface.Thermometer,
ScryptedInterface.Battery,
@@ -489,6 +495,8 @@ export default {
Thermometer,
HumiditySensor,
EntrySensor,
MotionSensor,
AudioSensor,
OccupancySensor,
OauthClient,

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>

View File

@@ -1,7 +1,7 @@
<template>
<span>
<font-awesome-icon class="white--text mr-2 ml-2" size="sm" :icon="batteryIcon" color="#a9afbb" />
<span class="caption mr-2">{{ lazyValue.batteryLevel }}%</span>
<font-awesome-icon class="white--text mr-1 mr-1" size="sm" :icon="batteryIcon" color="#a9afbb" />
<span class="caption mr-1">{{ lazyValue.batteryLevel }}%</span>
</span>
</template>

View File

@@ -0,0 +1,27 @@
<template>
<v-tooltip left>
<template v-slot:activator="{ on }">
<v-icon
v-on="on"
v-if="lazyValue.audioDetected"
class="mr-1 mr-1"
small
>fa-volume-up</v-icon>
<v-icon
v-on="on"
v-else
class="mr-1 mr-1"
small
>fa-volume-mute</v-icon>
</template>
<span>{{ !lazyValue.audioDetected ? 'No ' : '' }}Audio Detected</span>
</v-tooltip>
</template>
<script>
import RPCInterface from "../RPCInterface.vue";
export default {
mixins: [RPCInterface]
};
</script>

View File

@@ -4,7 +4,7 @@
<font-awesome-icon
v-on="on"
v-if="lazyValue.entryOpen"
class="white--text mr-2 ml-2"
class="white--text mr-1 mr-1"
size="sm"
icon="door-open"
color="#a9afbb"
@@ -12,7 +12,7 @@
<font-awesome-icon
v-on="on"
v-else
class="white--text mr-2 ml-2"
class="white--text mr-1 mr-1"
size="sm"
icon="door-closed"
color="#a9afbb"

View File

@@ -1,7 +1,7 @@
<template>
<span>
<font-awesome-icon class="white--text mr-2 ml-2" size="sm" icon="tint" color="#a9afbb" />
<span class="caption mr-2">{{ Math.round(lazyValue.humidity) }}%</span>
<font-awesome-icon class="white--text mr-1 mr-1" size="sm" icon="tint" color="#a9afbb" />
<span class="caption mr-1">{{ Math.round(lazyValue.humidity) }}%</span>
</span>
</template>

View File

@@ -0,0 +1,27 @@
<template>
<v-tooltip left>
<template v-slot:activator="{ on }">
<v-icon
v-on="on"
v-if="lazyValue.motionDetected"
class="mr-1 mr-1"
small
>fa-user-circle</v-icon>
<v-icon
v-on="on"
v-else
class="mr-1 mr-1"
small
>far fa-circle</v-icon>
</template>
<span>{{ !lazyValue.motionDetected ? 'No ' : '' }}Motion Detected</span>
</v-tooltip>
</template>
<script>
import RPCInterface from "../RPCInterface.vue";
export default {
mixins: [RPCInterface]
};
</script>

View File

@@ -4,7 +4,7 @@
<font-awesome-icon
v-on="on"
v-if="lazyValue.occupied"
class="white--text mr-2 ml-2"
class="white--text mr-1 mr-1"
size="sm"
icon="user"
color="#a9afbb"
@@ -12,7 +12,7 @@
<font-awesome-icon
v-on="on"
v-else
class="white--text mr-2 ml-2"
class="white--text mr-1 mr-1"
size="sm"
icon="user-alt-slash"
color="#a9afbb"

View File

@@ -1,7 +1,7 @@
<template>
<span>
<font-awesome-icon class="white--text mr-2 ml-2" size="sm" icon="thermometer-three-quarters" color="#a9afbb" />
<span class="caption mr-2">{{ Math.round(lazyValue.temperature) }}°</span>
<font-awesome-icon class="white--text mr-1 mr-1" size="sm" icon="thermometer-three-quarters" color="#a9afbb" />
<span class="caption mr-1">{{ Math.round(lazyValue.temperature) }}°</span>
</span>
</template>