mirror of
https://github.com/koush/scrypted.git
synced 2026-02-03 14:13:28 +00:00
login fixes, more sensors
This commit is contained in:
4
plugins/core/package-lock.json
generated
4
plugins/core/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" /> 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> 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> 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>
|
||||
@@ -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: {
|
||||
|
||||
@@ -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"> 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
27
plugins/core/ui/src/interfaces/sensors/AudioSensor.vue
Normal file
27
plugins/core/ui/src/interfaces/sensors/AudioSensor.vue
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
27
plugins/core/ui/src/interfaces/sensors/MotionSensor.vue
Normal file
27
plugins/core/ui/src/interfaces/sensors/MotionSensor.vue
Normal 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>
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user