Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install modules
run: yarn install
run: yarn install --frozen-lockfile
- name: Build app
env:
VITE_APP_VERSION: ${{ steps.set_tag.outputs.tag }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
with:
node-version: '20.x'
- name: Install modules
run: yarn install
run: yarn install --frozen-lockfile
- name: Linting
run: |
yarn run lint
Expand Down
8 changes: 4 additions & 4 deletions cypress/integration/example.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// https://docs.cypress.io/api/introduction/api.html

describe("My First Test", () => {
it("visits the app root url", () => {
cy.visit("/");
cy.contains("h1", "You did it!");
describe('My First Test', () => {
it('visits the app root url', () => {
cy.visit('/');
cy.contains('h1', 'You did it!');
});
});
2 changes: 1 addition & 1 deletion cypress/support/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// ***********************************************************

// Import commands.js using ES2015 syntax:
import "./commands";
import './commands';

// Alternatively you can use CommonJS syntax:
// require('./commands')
7 changes: 5 additions & 2 deletions eslint.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { globalIgnores } from 'eslint/config'
import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
import * as pluginVue from 'eslint-plugin-vue'
import * as pluginVitest from '@vitest/eslint-plugin'
import skipFormatting from '@vue/eslint-config-prettier/skip-formatting'

// To allow more languages other than `ts` in `.vue` files, uncomment the following lines:
// import { configureVueProject } from '@vue/eslint-config-typescript'
Expand All @@ -24,5 +23,9 @@ export default defineConfigWithVueTs(
...pluginVitest.default.configs.recommended,
files: ['src/**/__tests__/*'],
},
skipFormatting,
{
rules: {
'quotes': ['error', 'single', { 'allowTemplateLiterals': true }],
},
},
)
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"build": "vue-tsc --noEmit -p tsconfig.app.json --composite false && vite build",
"test:unit": "vitest --environment jsdom",
"test:e2e": "start-server-and-test preview http://127.0.0.1:5050/ 'cypress open'",
"lint": "eslint . --fix",
"lint": "eslint .",
"dev": "vite",
"preview": "vite preview --port 5050",
"test:e2e:ci": "start-server-and-test preview http://127.0.0.1:5050/ 'cypress run'"
Expand All @@ -29,6 +29,7 @@
"socket.io-client": "^4.8.1",
"vue": "^3.5.0",
"vue-chartjs": "^4.1.1",
"vue-color": "^3.3.3",
"vue-fullscreen": "^3.1.2",
"vue-router": "^4.5.1",
"vue-socket.io": "^3.0.10",
Expand All @@ -46,7 +47,6 @@
"@vitejs/plugin-vue-jsx": "^5.0.0",
"@vitest/eslint-plugin": "^1.2.7",
"@vue/compiler-sfc": "^3.5.17",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.5.1",
"@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.7.0",
Expand All @@ -57,7 +57,6 @@
"jiti": "^2.4.2",
"jsdom": "^26.1.0",
"npm-run-all2": "^8.0.4",
"prettier": "^3.5.3",
"sass": "^1.89.2",
"start-server-and-test": "^2.0.12",
"typescript": "^5.8.3",
Expand Down
48 changes: 28 additions & 20 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
<script setup lang="ts">
import { useDevicesStore } from "./stores/devices.js";
import { useSocketIO } from "./plugins/vueSocketIOClient.js";
import type { Socket } from "socket.io-client";
import type Device from "./model/devices/Device";
import { useSettingsStore } from "./stores/settings.js";
import { useAutomationStore } from "./stores/automation.js";
import { storeToRefs } from "pinia";
import { useAppStore } from "./stores/app";
import { useHealthStore } from "./stores/health";
import { useBackendStore } from "@/stores/backend";
import ServerStatusOverlay from "@/components/ServerStatusOverlay.vue";
import { useDevicesStore } from './stores/devices.js';
import { useSocketIO } from './plugins/vueSocketIOClient.js';
import type Device from './model/devices/Device';
import { useSettingsStore } from './stores/settings.js';
import { useAutomationStore } from './stores/automation.js';
import { storeToRefs } from 'pinia';
import { useAppStore } from './stores/app';
import { useHealthStore } from './stores/health';
import { useBackendStore } from '@/stores/backend';
import { useDeviceNotificationsStore } from '@/stores/deviceNotifications';
import ServerStatusOverlay from '@/components/ServerStatusOverlay.vue';

const settingsStore = useSettingsStore();
const healthStore = useHealthStore();
const appStore = useAppStore();
const automationStore = useAutomationStore();
const devicesStore = useDevicesStore();
const backendStore = useBackendStore();
const deviceNotificationsStore = useDeviceNotificationsStore();

const { theme } = storeToRefs(settingsStore);

if (backendStore.backendUrl) {
const io = useSocketIO() as Socket;
const io = useSocketIO();

io.on("connect", () => {
if (backendStore.backendUrl) {
io?.on('connect', () => {
backendStore.setServerOnline(true);

// Init/update stores with initial data from server
Expand All @@ -32,9 +33,13 @@ if (backendStore.backendUrl) {
devicesStore.init();
healthStore.init(io);
Comment thread
heavyrubberslave marked this conversation as resolved.
});
io.on("disconnect", () => backendStore.setServerOnline(false));
io?.on('disconnect', () => {
backendStore.setServerOnline(false);
devicesStore.clear();
deviceNotificationsStore.clear();
});
Comment thread
heavyrubberslave marked this conversation as resolved.

io.on("deviceDisconnected", (device) => {
io?.on('deviceDisconnected', (device) => {
devicesStore.removeDevice(device);

appStore.displaySnackbar(
Expand All @@ -43,7 +48,7 @@ if (backendStore.backendUrl) {
}) disconnected`
);
});
io.on("deviceConnected", (device) => {
io?.on('deviceConnected', (device) => {
devicesStore.addDevice(device);

appStore.displaySnackbar(
Expand All @@ -52,17 +57,20 @@ if (backendStore.backendUrl) {
}) connected`
);
});
io.on("deviceRefreshed", (device) => {
io?.on('deviceRefreshed', (device) => {
devicesStore.updateDevice(device);
});
io.on("automationConsoleLog", (data: string) => {
io?.on('deviceNotification', (device, notification) => {
deviceNotificationsStore.dispatch(device, notification);
});
io?.on('automationConsoleLog', (data: string) => {
automationStore.logMessages.push(data);

if (automationStore.logMessages.length > 500) {
automationStore.logMessages.shift();
}
});
io.on("settingsChanged", async () => {
io?.on('settingsChanged', async () => {
await settingsStore.getServerSettings()
});
}
Expand Down
30 changes: 16 additions & 14 deletions src/components/DeviceInfo.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
<script setup lang="ts">
import type Device from "@/model/devices/Device";
import DeviceIcon from "./icons/DeviceIcon.vue";
import { computed, reactive } from "vue";
import {hasProperty} from "@/utils/utils";
import type Device from '@/model/devices/Device';
import DeviceIcon from './icons/DeviceIcon.vue';
import { computed } from 'vue';

interface Props {
device: Device;
}

const props = defineProps<Props>();
const device = reactive<Device>(props.device);

const lastRefreshed = computed<string>((): string => {
return device.lastRefresh
? new Date(device.lastRefresh).toISOString()
: "n/a";
return props.device.lastRefresh
? new Date(props.device.lastRefresh).toISOString()
: 'n/a';
});

const deviceTypeModel = computed<string>((): string => {
return `${device.type} ${
device.type === "slvCtrlPlus" ? ` (model: ${device.deviceModel})` : ""
return `${props.device.type} ${
props.device.type === 'slvCtrlPlus' ? ` (model: ${props.device.deviceModel})` : ''
}`;
});

Expand All @@ -38,7 +36,7 @@ function formatFwVersion(fwVersion: string|number): string {
}

function isStringOrNumber(val: unknown): val is string | number {
return typeof val === "string" || typeof val === "number";
return typeof val === 'string' || typeof val === 'number';
}
</script>

Expand All @@ -59,16 +57,20 @@ function isStringOrNumber(val: unknown): val is string | number {
<v-list-item-title>Type</v-list-item-title>
<v-list-item-subtitle>{{ deviceTypeModel }}</v-list-item-subtitle>
</v-list-item>
<v-list-item v-if="hasProperty<Device, 'fwVersion'>(device, 'fwVersion') && isStringOrNumber(device.fwVersion)">
<v-list-item v-if="'fwVersion' in props.device && isStringOrNumber(props.device.fwVersion)">
<v-list-item-title>Firmware</v-list-item-title>
<v-list-item-subtitle>{{
formatFwVersion(device.fwVersion)
formatFwVersion(props.device.fwVersion)
}}</v-list-item-subtitle>
</v-list-item>
<v-list-item v-if="'rssi' in props.device">
<v-list-item-title>Signal strength</v-list-item-title>
<v-list-item-subtitle>{{props.device.rssi}}</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>Connected since</v-list-item-title>
<v-list-item-subtitle>{{
new Date(device.connectedSince).toISOString()
new Date(props.device.connectedSince).toISOString()
}}</v-list-item-subtitle>
</v-list-item>
<v-list-item>
Expand Down
8 changes: 4 additions & 4 deletions src/components/ServerStatusOverlay.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script setup lang="ts">
import {useBackendStore} from "@/stores/backend";
import {storeToRefs} from "pinia";
import {computed, ref, watch} from "vue";
import {format} from "date-fns";
import {useBackendStore} from '@/stores/backend';
import {storeToRefs} from 'pinia';
import {computed, ref, watch} from 'vue';
import {format} from 'date-fns';

const backendStore = useBackendStore();
const { backendUrl, isServerOnline, wasServerEverOnline } = storeToRefs(backendStore);
Expand Down
14 changes: 7 additions & 7 deletions src/components/__tests__/HelloWorld.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { describe, it, expect } from "vitest";
import { describe, it, expect } from 'vitest';

import { mount } from "@vue/test-utils";
import HelloWorld from "../HelloWorld.vue";
import { mount } from '@vue/test-utils';
import HelloWorld from '../HelloWorld.vue';

describe("HelloWorld", () => {
it("renders properly", () => {
const wrapper = mount(HelloWorld, { props: { msg: "Hello Vitest" } });
expect(wrapper.text()).toContain("Hello Vitest");
describe('HelloWorld', () => {
it('renders properly', () => {
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } });
expect(wrapper.text()).toContain('Hello Vitest');
});
});
26 changes: 13 additions & 13 deletions src/components/automation/CreateForm.vue
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
<script setup lang="ts">
import { ref } from "vue";
import { useAutomationStore } from "@/stores/automation.js";
import { useAppStore } from "@/stores/app.js";
import { ref } from 'vue';
import { useAutomationStore } from '@/stores/automation.js';
import { useAppStore } from '@/stores/app.js';

const emit = defineEmits(["save", "cancel"]);
const emit = defineEmits(['save', 'cancel']);

const appStore = useAppStore();
const automationStore = useAutomationStore();

const newScriptName = ref("");
const newScriptName = ref('');
const isValid = ref(true);

const scriptNameRules = [
(value: string) => {
if (value) return true;
return "You must enter a script name.";
return 'You must enter a script name.';
},
(value: string) => {
if (value.length >= 3) return true;
return "The script name needs to be at least 3 characters long.";
return 'The script name needs to be at least 3 characters long.';
},
(value: string) => {
if (value.length < 64) return true;
return "The script name needs to no more than 64 characters long.";
if (value.length <= 64) return true;
return 'The script name needs to no more than 64 characters long.';
},
(value: string) => {
if (value.match(/^[a-z\d-_.]+$/)) return true;
return "Only lower case letters, numbers, hyphens, underscores and dots are allowed.";
return 'Only lower case letters, numbers, hyphens, underscores and dots are allowed.';
Comment thread
heavyrubberslave marked this conversation as resolved.
},
];

Expand All @@ -45,10 +45,10 @@ function createScript(): void {
automationStore.fetchScript(scriptNameWithExt);
})
.then(() => {
emit("save");
newScriptName.value = "";
emit('save');
newScriptName.value = '';
})
.catch((e: Error) => appStore.displaySnackbar(`${e.message}`, "red"));
.catch((e: Error) => appStore.displaySnackbar(`${e.message}`, 'red'));
}
</script>

Expand Down
6 changes: 3 additions & 3 deletions src/components/automation/LogViewer.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { computed, nextTick, ref, watch } from "vue";
import moment from "moment";
import { computed, nextTick, ref, watch } from 'vue';
import moment from 'moment';

interface Props {
logData: string[];
Expand All @@ -11,7 +11,7 @@ const emit = defineEmits(['close']);

const props = defineProps<Props>();

const logData = computed(() => `${props.logData.join("\n")}\n\n`);
const logData = computed(() => `${props.logData.join('\n')}\n\n`);
const runningSinceFormatted = computed(() => {
if (null === props.runningSince) {
return null;
Expand Down
Loading
Loading