diff --git a/webclient-vue/.env b/webclient-vue/.env new file mode 100644 index 0000000..a469b08 --- /dev/null +++ b/webclient-vue/.env @@ -0,0 +1,3 @@ +VITE_API_BASE_URL= +VITE_API_PROXY_PATH=/proxy.php +VITE_API_TIMEOUT_MS=10000 diff --git a/webclient-vue/coverage/api/client.js.html b/webclient-vue/coverage/api/client.js.html new file mode 100644 index 0000000..ef59379 --- /dev/null +++ b/webclient-vue/coverage/api/client.js.html @@ -0,0 +1,283 @@ + + + + + + Code coverage report for api/client.js + + + + + + + + + +
+
+

All files / api client.js

+
+ +
+ 100% + Statements + 12/12 +
+ + +
+ 83.33% + Branches + 15/18 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 12/12 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67  +  +  +4x +  +  +  +  +  +  +  +20x +20x +  +18x +1x +  +  +  +  +  +  +  +  +  +17x +1x +  +  +  +  +  +  +  +  +  +  +16x +  +  +  +  +  +2x +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +10x +  +  +  +5x +  + 
import { requestHttp } from "./http";
+ 
+function makeError(type, message, extra = {}) {
+  return {
+    type,
+    message,
+    ...extra,
+  };
+}
+ 
+export async function apiRequest(method, path, body, options) {
+  try {
+    const { response, data, meta } = await requestHttp(method, path, body, options);
+ 
+    if (!response.ok) {
+      return {
+        ok: false,
+        error: makeError("http_error", `HTTP ${response.status}`, {
+          statusCode: response.status,
+          raw: data,
+        }),
+        meta,
+      };
+    }
+ 
+    if (data && typeof data === "object" && (data.status === false || data.status === "error")) {
+      return {
+        ok: false,
+        error: makeError("api_error", data.msg || data.message || "API error", {
+          errorAlias: data.error_alias,
+          failedFields: data.failed_fields || [],
+          raw: data,
+        }),
+        meta,
+      };
+    }
+ 
+    return {
+      ok: true,
+      data,
+      meta,
+    };
+  } catch (error) {
+    const isAbort = error?.name === "AbortError";
+    return {
+      ok: false,
+      error: makeError(isAbort ? "timeout" : "network_error", error?.message || "Network error", {
+        details: error,
+      }),
+      meta: {
+        url: path,
+        method,
+        statusCode: 0,
+        headers: null,
+      },
+    };
+  }
+}
+ 
+export function apiGet(path, options) {
+  return apiRequest("GET", path, null, options);
+}
+ 
+export function apiPost(path, body, options) {
+  return apiRequest("POST", path, body, options);
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/api/http.js.html b/webclient-vue/coverage/api/http.js.html new file mode 100644 index 0000000..9a4e8e1 --- /dev/null +++ b/webclient-vue/coverage/api/http.js.html @@ -0,0 +1,361 @@ + + + + + + Code coverage report for api/http.js + + + + + + + + + +
+
+

All files / api http.js

+
+ +
+ 92.68% + Statements + 38/41 +
+ + +
+ 89.18% + Branches + 33/37 +
+ + +
+ 66.66% + Functions + 4/6 +
+ + +
+ 97.43% + Lines + 38/39 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +934x +  +  +23x +  +23x +23x +  +  +  +23x +  +  +23x +23x +  +  +  +23x +23x +  +23x +22x +  +  +1x +  +  +  +23x +  +23x +1x +  +  +22x +  +  +  +23x +23x +23x +23x +23x +  +23x +  +  +  +  +23x +  +  +  +  +  +23x +8x +  +  +23x +6x +6x +  +  +23x +23x +23x +23x +  +23x +23x +23x +  +1x +  +  +  +23x +  +  +  +  +  +  +  +  +  +  +23x +  +  + 
const DEFAULT_TIMEOUT_MS = Number(import.meta.env.VITE_API_TIMEOUT_MS || 10000);
+ 
+function buildQuery(params) {
+  const query = new URLSearchParams();
+ 
+  for (const [key, value] of Object.entries(params || {})) {
+    Iif (value === undefined || value === null) {
+      continue;
+    }
+ 
+    query.append(key, String(value));
+  }
+ 
+  const serialized = query.toString();
+  return serialized ? `?${serialized}` : "";
+}
+ 
+function joinUrl(baseUrl, path) {
+  const base = String(baseUrl || "").replace(/\/+$/, "");
+  const nextPath = String(path || "").replace(/^\/+/, "");
+ 
+  if (!base) {
+    return `/${nextPath}`;
+  }
+ 
+  return `${base}/${nextPath}`;
+}
+ 
+function wrapProxyPath(path, query) {
+  const proxyPath = import.meta.env.VITE_API_PROXY_PATH || "";
+ 
+  if (!proxyPath) {
+    return `${path}${buildQuery(query)}`;
+  }
+ 
+  return `${proxyPath}${buildQuery({ path, ...(query || {}) })}`;
+}
+ 
+export async function requestHttp(method, path, body, options = {}) {
+  const timeoutMs = Number(options.timeoutMs || DEFAULT_TIMEOUT_MS);
+  const controller = new AbortController();
+  const timeout = setTimeout(() => controller.abort(), timeoutMs);
+  const baseUrl = import.meta.env.VITE_API_BASE_URL || "";
+  const url = joinUrl(baseUrl, wrapProxyPath(path, options.query));
+ 
+  const headers = {
+    Accept: "application/json",
+    ...(options.headers || {}),
+  };
+ 
+  const init = {
+    method,
+    headers,
+    signal: controller.signal,
+  };
+ 
+  if (options.signal) {
+    options.signal.addEventListener("abort", () => controller.abort(), { once: true });
+  }
+ 
+  if (body !== undefined && body !== null) {
+    headers["Content-Type"] = "application/json";
+    init.body = JSON.stringify(body);
+  }
+ 
+  try {
+    const response = await fetch(url, init);
+    const text = await response.text();
+    let data = text;
+ 
+    Eif (text) {
+      try {
+        data = JSON.parse(text);
+      } catch (_) {
+        data = text;
+      }
+    }
+ 
+    return {
+      response,
+      data,
+      meta: {
+        url,
+        method,
+        statusCode: response.status,
+        headers: response.headers,
+      },
+    };
+  } finally {
+    clearTimeout(timeout);
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/api/index.html b/webclient-vue/coverage/api/index.html new file mode 100644 index 0000000..84548cd --- /dev/null +++ b/webclient-vue/coverage/api/index.html @@ -0,0 +1,146 @@ + + + + + + Code coverage report for api + + + + + + + + + +
+
+

All files api

+
+ +
+ 94.82% + Statements + 55/58 +
+ + +
+ 88.13% + Branches + 52/59 +
+ + +
+ 81.81% + Functions + 9/11 +
+ + +
+ 98.21% + Lines + 55/56 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
client.js +
+
100%12/1283.33%15/18100%4/4100%12/12
http.js +
+
92.68%38/4189.18%33/3766.66%4/697.43%38/39
mappers.js +
+
100%5/5100%4/4100%1/1100%5/5
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/api/mappers.js.html b/webclient-vue/coverage/api/mappers.js.html new file mode 100644 index 0000000..91619f5 --- /dev/null +++ b/webclient-vue/coverage/api/mappers.js.html @@ -0,0 +1,145 @@ + + + + + + Code coverage report for api/mappers.js + + + + + + + + + +
+
+

All files / api mappers.js

+
+ +
+ 100% + Statements + 5/5 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +213x +  +  +  +  +  +  +  +  +  +  +  +5x +  +5x +13x +  +  +5x +  + 
const deviceFieldMap = {
+  device_name: "name",
+  device_hard_id: "device_id",
+  device_ip: "ip",
+  device_type: "type",
+  ip_address: "ip",
+  mac_address: "mac",
+  device_mac: "mac",
+  core_version: "firmware_core_version",
+};
+ 
+export function unifyDeviceFields(device) {
+  const normalized = {};
+ 
+  for (const [field, value] of Object.entries(device || {})) {
+    normalized[deviceFieldMap[field] || field] = value;
+  }
+ 
+  return normalized;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/api/modules/areas.js.html b/webclient-vue/coverage/api/modules/areas.js.html new file mode 100644 index 0000000..004e101 --- /dev/null +++ b/webclient-vue/coverage/api/modules/areas.js.html @@ -0,0 +1,214 @@ + + + + + + Code coverage report for api/modules/areas.js + + + + + + + + + +
+
+

All files / api/modules areas.js

+
+ +
+ 100% + Statements + 11/11 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 10/10 +
+ + +
+ 100% + Lines + 11/11 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44  +  +2x +  +5x +  +  +  +1x +  +  +  +2x +  +  +  +2x +  +  +  +1x +  +  +  +1x +  +  +  +2x +  +  +  +1x +  +  +  +1x +  +  +  +1x +  +  + 
import { apiGet, apiPost } from "../client";
+ 
+export const areasApi = {
+  list(options) {
+    return apiGet("/api/v1/areas/list", options);
+  },
+ 
+  innerList(areaId, options) {
+    return apiGet(`/api/v1/areas/id/${encodeURIComponent(String(areaId))}/list`, options);
+  },
+ 
+  newArea(payload) {
+    return apiPost("/api/v1/areas/new-area", payload);
+  },
+ 
+  remove(areaId) {
+    return apiGet(`/api/v1/areas/id/${encodeURIComponent(String(areaId))}/remove`);
+  },
+ 
+  devices(areaId) {
+    return apiGet(`/api/v1/areas/id/${encodeURIComponent(String(areaId))}/devices`);
+  },
+ 
+  scripts(areaId) {
+    return apiGet(`/api/v1/areas/id/${encodeURIComponent(String(areaId))}/scripts`);
+  },
+ 
+  updateDisplayName(payload) {
+    return apiPost("/api/v1/areas/update-display-name", payload);
+  },
+ 
+  updateAlias(payload) {
+    return apiPost("/api/v1/areas/update-alias", payload);
+  },
+ 
+  placeInArea(payload) {
+    return apiPost("/api/v1/areas/place-in-area", payload);
+  },
+ 
+  unassign(areaId) {
+    return apiGet(`/api/v1/areas/id/${encodeURIComponent(String(areaId))}/unassign-from-area`);
+  },
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/api/modules/devices.js.html b/webclient-vue/coverage/api/modules/devices.js.html new file mode 100644 index 0000000..ffadda6 --- /dev/null +++ b/webclient-vue/coverage/api/modules/devices.js.html @@ -0,0 +1,250 @@ + + + + + + Code coverage report for api/modules/devices.js + + + + + + + + + +
+
+

All files / api/modules devices.js

+
+ +
+ 92.3% + Statements + 12/13 +
+ + +
+ 75% + Branches + 3/4 +
+ + +
+ 100% + Functions + 9/9 +
+ + +
+ 92.3% + Lines + 12/13 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56  +  +  +  +2x +  +  +  +1x +  +  +  +1x +  +1x +  +  +  +  +  +  +  +  +  +  +  +2x +  +1x +  +  +  +1x +  +  +  +1x +  +  +  +1x +  +  +  +3x +  +  +  +1x +  +  +  +2x +  +  + 
import { apiGet, apiPost } from "../client";
+import { unifyDeviceFields } from "../mappers";
+ 
+function safeId(id) {
+  return encodeURIComponent(String(id));
+}
+ 
+function mapDevicesResponse(result) {
+  Iif (!result.ok) {
+    return result;
+  }
+ 
+  const devices = result.data?.data?.devices || [];
+ 
+  return {
+    ...result,
+    data: {
+      ...result.data,
+      data: {
+        ...result.data?.data,
+        devices: devices.map(unifyDeviceFields),
+      },
+    },
+  };
+}
+ 
+export const devicesApi = {
+  async list() {
+    return mapDevicesResponse(await apiGet("/api/v1/devices/list"));
+  },
+ 
+  status(id, options) {
+    return apiGet(`/api/v1/devices/id/${safeId(id)}/status`, options);
+  },
+ 
+  reboot(id) {
+    return apiGet(`/api/v1/devices/id/${safeId(id)}/reboot`);
+  },
+ 
+  action(payload) {
+    return apiPost("/api/v1/devices/action", payload);
+  },
+ 
+  scanningSetup(options) {
+    return apiGet("/api/v1/devices/scanning/setup", options);
+  },
+ 
+  scanningAll(options) {
+    return apiGet("/api/v1/devices/scanning/all", options);
+  },
+ 
+  setupNewDevice(payload) {
+    return apiPost("/api/v1/devices/setup/new-device", payload);
+  },
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/api/modules/index.html b/webclient-vue/coverage/api/modules/index.html new file mode 100644 index 0000000..5f51007 --- /dev/null +++ b/webclient-vue/coverage/api/modules/index.html @@ -0,0 +1,146 @@ + + + + + + Code coverage report for api/modules + + + + + + + + + +
+
+

All files api/modules

+
+ +
+ 96.77% + Statements + 30/31 +
+ + +
+ 77.77% + Branches + 7/9 +
+ + +
+ 100% + Functions + 25/25 +
+ + +
+ 96.77% + Lines + 30/31 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
areas.js +
+
100%11/11100%0/0100%10/10100%11/11
devices.js +
+
92.3%12/1375%3/4100%9/992.3%12/13
scripts.js +
+
100%7/780%4/5100%6/6100%7/7
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/api/modules/scripts.js.html b/webclient-vue/coverage/api/modules/scripts.js.html new file mode 100644 index 0000000..198eef3 --- /dev/null +++ b/webclient-vue/coverage/api/modules/scripts.js.html @@ -0,0 +1,166 @@ + + + + + + Code coverage report for api/modules/scripts.js + + + + + + + + + +
+
+

All files / api/modules scripts.js

+
+ +
+ 100% + Statements + 7/7 +
+ + +
+ 80% + Branches + 4/5 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 100% + Lines + 7/7 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28  +  +2x +  +3x +  +  +  +1x +  +  +  +1x +  +  +  +2x +  +  +  +2x +  +  +  +1x +  +  + 
import { apiGet, apiPost } from "../client";
+ 
+export const scriptsApi = {
+  actionsList(options) {
+    return apiGet("/api/v1/scripts/actions/list", options);
+  },
+ 
+  regularList(options) {
+    return apiGet("/api/v1/scripts/regular/list", options);
+  },
+ 
+  scopesList(options) {
+    return apiGet("/api/v1/scripts/scopes/list", options);
+  },
+ 
+  runAction(alias, params = {}) {
+    return apiPost("/api/v1/scripts/actions/run", { alias, params });
+  },
+ 
+  setRegularState(alias, enabled) {
+    return apiGet(`/api/v1/scripts/regular/alias/${encodeURIComponent(alias)}/${enabled ? "enable" : "disable"}`);
+  },
+ 
+  setScopeState(name, enabled) {
+    return apiGet(`/api/v1/scripts/actions/scope/${encodeURIComponent(name)}/${enabled ? "enable" : "disable"}`);
+  },
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/base.css b/webclient-vue/coverage/base.css new file mode 100644 index 0000000..f418035 --- /dev/null +++ b/webclient-vue/coverage/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/webclient-vue/coverage/block-navigation.js b/webclient-vue/coverage/block-navigation.js new file mode 100644 index 0000000..530d1ed --- /dev/null +++ b/webclient-vue/coverage/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/webclient-vue/coverage/components/feedback/AppEmptyState.vue.html b/webclient-vue/coverage/components/feedback/AppEmptyState.vue.html new file mode 100644 index 0000000..1efea36 --- /dev/null +++ b/webclient-vue/coverage/components/feedback/AppEmptyState.vue.html @@ -0,0 +1,139 @@ + + + + + + Code coverage report for components/feedback/AppEmptyState.vue + + + + + + + + + +
+
+

All files / components/feedback AppEmptyState.vue

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19  +9x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
<template>
+  <GnEmptyState :title="title" :text="message" icon="ph-package" />
+</template>
+ 
+<script setup>
+import { GnEmptyState } from "gnexus-ui-kit/vue";
+ 
+defineProps({
+  title: {
+    type: String,
+    default: "Nothing here",
+  },
+  message: {
+    type: String,
+    default: "",
+  },
+});
+</script>
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/components/feedback/AppErrorState.vue.html b/webclient-vue/coverage/components/feedback/AppErrorState.vue.html new file mode 100644 index 0000000..32bc2fd --- /dev/null +++ b/webclient-vue/coverage/components/feedback/AppErrorState.vue.html @@ -0,0 +1,163 @@ + + + + + + Code coverage report for components/feedback/AppErrorState.vue + + + + + + + + + +
+
+

All files / components/feedback AppErrorState.vue

+
+ +
+ 100% + Statements + 3/3 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27  +4x +4x +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
<template>
+  <GnAlert variant="danger" role="alert">
+    <strong>{{ title }}</strong>
+    <p v-if="message">{{ message }}</p>
+    <GnButton v-if="retry" variant="danger" @click="retry">Retry</GnButton>
+  </GnAlert>
+</template>
+ 
+<script setup>
+import { GnAlert, GnButton } from "gnexus-ui-kit/vue";
+ 
+defineProps({
+  title: {
+    type: String,
+    default: "Request failed",
+  },
+  message: {
+    type: String,
+    default: "",
+  },
+  retry: {
+    type: Function,
+    default: null,
+  },
+});
+</script>
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/components/feedback/AppLoadingState.vue.html b/webclient-vue/coverage/components/feedback/AppLoadingState.vue.html new file mode 100644 index 0000000..dafb546 --- /dev/null +++ b/webclient-vue/coverage/components/feedback/AppLoadingState.vue.html @@ -0,0 +1,127 @@ + + + + + + Code coverage report for components/feedback/AppLoadingState.vue + + + + + + + + + +
+
+

All files / components/feedback AppLoadingState.vue

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15  +8x +  +  +  +  +  +  +  +  +  +  +  +  + 
<template>
+  <GnLoader circle :label="text" />
+</template>
+ 
+<script setup>
+import { GnLoader } from "gnexus-ui-kit/vue";
+ 
+defineProps({
+  text: {
+    type: String,
+    default: "Loading",
+  },
+});
+</script>
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/components/feedback/index.html b/webclient-vue/coverage/components/feedback/index.html new file mode 100644 index 0000000..83e7fa7 --- /dev/null +++ b/webclient-vue/coverage/components/feedback/index.html @@ -0,0 +1,146 @@ + + + + + + Code coverage report for components/feedback + + + + + + + + + +
+
+

All files components/feedback

+
+ +
+ 100% + Statements + 5/5 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
AppEmptyState.vue +
+
100%1/1100%0/0100%0/0100%1/1
AppErrorState.vue +
+
100%3/3100%4/4100%2/2100%3/3
AppLoadingState.vue +
+
100%1/1100%0/0100%0/0100%1/1
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/components/layout/AppShell.vue.html b/webclient-vue/coverage/components/layout/AppShell.vue.html new file mode 100644 index 0000000..72e1e86 --- /dev/null +++ b/webclient-vue/coverage/components/layout/AppShell.vue.html @@ -0,0 +1,169 @@ + + + + + + Code coverage report for components/layout/AppShell.vue + + + + + + + + + +
+
+

All files / components/layout AppShell.vue

+
+ +
+ 100% + Statements + 3/3 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29  +3x +  +  +  +  +  +  +  +  +3x +  +  +  +  +  +  +  +3x +  +  +  +  +  +  +  +  +  + 
<template>
+  <GnNavigationShell
+    brand="SHSERV WEB CLIENT"
+    logo-src=""
+    title="Navigation"
+    subtitle="Smart Home"
+    :items="navItems"
+    active-match="prefix"
+  >
+    <template #content>
+      <slot />
+    </template>
+  </GnNavigationShell>
+</template>
+ 
+<script setup>
+import { GnNavigationShell } from "gnexus-ui-kit/vue";
+ 
+const navItems = [
+  { label: "Favorites", to: "/areas/favorites", icon: "ph-star" },
+  { label: "Areas", to: "/areas/tree", icon: "ph-map-trifold" },
+  { label: "Devices", to: "/devices", icon: "ph-cpu" },
+  { label: "Scanning", to: "/devices/scanning", icon: "ph-magnifying-glass" },
+  { label: "Actions", to: "/scripts/actions", icon: "ph-play" },
+  { label: "Regular", to: "/scripts/regular", icon: "ph-clock" },
+  { label: "Scopes", to: "/scripts/scopes", icon: "ph-brackets-curly" },
+];
+</script>
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/components/layout/index.html b/webclient-vue/coverage/components/layout/index.html new file mode 100644 index 0000000..d229c9c --- /dev/null +++ b/webclient-vue/coverage/components/layout/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for components/layout + + + + + + + + + +
+
+

All files components/layout

+
+ +
+ 100% + Statements + 3/3 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
AppShell.vue +
+
100%3/3100%0/0100%1/1100%3/3
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/favicon.png b/webclient-vue/coverage/favicon.png new file mode 100644 index 0000000..c1525b8 --- /dev/null +++ b/webclient-vue/coverage/favicon.png Binary files differ diff --git a/webclient-vue/coverage/features/areas/components/AreaTreeNode.vue.html b/webclient-vue/coverage/features/areas/components/AreaTreeNode.vue.html new file mode 100644 index 0000000..3bc771d --- /dev/null +++ b/webclient-vue/coverage/features/areas/components/AreaTreeNode.vue.html @@ -0,0 +1,379 @@ + + + + + + Code coverage report for features/areas/components/AreaTreeNode.vue + + + + + + + + + +
+
+

All files / features/areas/components AreaTreeNode.vue

+
+ +
+ 90.32% + Statements + 28/31 +
+ + +
+ 100% + Branches + 39/39 +
+ + +
+ 83.33% + Functions + 15/18 +
+ + +
+ 88.88% + Lines + 24/27 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99  +26x +2x +  +  +  +  +  +1x +  +  +  +  +2x +  +  +23x +  +  +  +  +2x +  +  +  +1x +  +  +18x +  +  +  +  +1x +  +  +18x +  +  +  +1x +  +  +18x +  +  +  +1x +  +19x +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +19x +  +  +  +  +  +  +  +  +  +  +19x +  +19x +19x +23x +24x +24x +19x +  + 
<template>
+  <li v-if="!isCycle" class="area-tree-node" :class="{ 'is-open': isOpen, 'is-leaf': isLeaf }">
+    <article class="area-tree-card">
+      <button
+        class="tree-toggle"
+        type="button"
+        :disabled="isLeaf"
+        :aria-expanded="isOpen"
+        @click="isOpen = !isOpen"
+      >
+        {{ isLeaf ? "·" : isOpen ? "−" : "+" }}
+      </button>
+ 
+      <div class="area-tree-info">
+        <h2>{{ area.display_name }}</h2>
+        <p>
+          <GnBadge variant="secondary">{{ area.type }}</GnBadge>
+          <code>{{ area.alias }}</code>
+        </p>
+      </div>
+ 
+      <div class="area-tree-actions">
+        <GnButton
+          variant="secondary"
+          icon="ph-pencil"
+          @click="emit('rename', area)"
+        >
+          Rename
+        </GnButton>
+        <GnButton
+          variant="secondary"
+          icon="ph-arrow-up"
+          :disabled="area.parent_id == null || area.parent_id === 0"
+          @click="emit('unassign', area)"
+        >
+          Unassign
+        </GnButton>
+        <GnButton
+          variant="danger"
+          icon="ph-trash"
+          @click="emit('remove', area)"
+        >
+          Remove
+        </GnButton>
+        <GnButton
+          :variant="isFavorite ? 'warning' : 'secondary'"
+          :icon="isFavorite ? 'ph-star' : 'ph-star'"
+          @click="favoritesStore.toggle(area.id)"
+        >
+          {{ isFavorite ? "Unstar" : "Star" }}
+        </GnButton>
+      </div>
+    </article>
+ 
+    <ul v-if="area.children?.length && isOpen" class="area-tree-children">
+      <AreaTreeNode
+        v-for="child in area.children"
+        :key="child.id"
+        :area="child"
+        :ancestors="nextAncestors"
+        @rename="(a) => emit('rename', a)"
+        @remove="(a) => emit('remove', a)"
+        @unassign="(a) => emit('unassign', a)"
+      />
+    </ul>
+  </li>
+  <li v-else class="area-tree-node">
+    <article class="area-tree-card area-tree-cycle">
+      Cycle skipped for area ID {{ area.id }}
+    </article>
+  </li>
+</template>
+ 
+<script setup>
+import { computed, ref } from "vue";
+import { useFavoritesStore } from "../../../stores/favorites";
+import { GnBadge, GnButton } from "gnexus-ui-kit/vue";
+ 
+const props = defineProps({
+  area: {
+    type: Object,
+    required: true,
+  },
+  ancestors: {
+    type: Array,
+    default: () => [],
+  },
+});
+ 
+const emit = defineEmits(["rename", "remove", "unassign"]);
+ 
+const favoritesStore = useFavoritesStore();
+const isOpen = ref(false);
+const isLeaf = computed(() => !props.area.children?.length);
+const isFavorite = computed(() => favoritesStore.has(props.area.id));
+const isCycle = computed(() => props.ancestors.includes(String(props.area.id)));
+const nextAncestors = computed(() => [...props.ancestors, String(props.area.id)]);
+</script>
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/features/areas/components/index.html b/webclient-vue/coverage/features/areas/components/index.html new file mode 100644 index 0000000..5c8edf2 --- /dev/null +++ b/webclient-vue/coverage/features/areas/components/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for features/areas/components + + + + + + + + + +
+
+

All files features/areas/components

+
+ +
+ 90.32% + Statements + 28/31 +
+ + +
+ 100% + Branches + 39/39 +
+ + +
+ 83.33% + Functions + 15/18 +
+ + +
+ 88.88% + Lines + 24/27 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
AreaTreeNode.vue +
+
90.32%28/31100%39/3983.33%15/1888.88%24/27
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/features/areas/pages/AreaTreePage.vue.html b/webclient-vue/coverage/features/areas/pages/AreaTreePage.vue.html new file mode 100644 index 0000000..7909b01 --- /dev/null +++ b/webclient-vue/coverage/features/areas/pages/AreaTreePage.vue.html @@ -0,0 +1,568 @@ + + + + + + Code coverage report for features/areas/pages/AreaTreePage.vue + + + + + + + + + +
+
+

All files / features/areas/pages AreaTreePage.vue

+
+ +
+ 29.85% + Statements + 20/67 +
+ + +
+ 25% + Branches + 11/44 +
+ + +
+ 14.28% + Functions + 4/28 +
+ + +
+ 29.5% + Lines + 18/61 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162  +15x +  +  +4x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +14x +  +  +  +  +  +  +  +  +  +  +1x +  +  +1x +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +  +4x +4x +4x +4x +  +4x +4x +4x +4x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +4x +  +  +  +  +  +  +  +  + 
<template>
+  <section class="page">
+    <GnPageHeader title="Tree" kicker="Areas">
+      <template #actions>
+        <GnButton variant="primary" icon="ph-plus" @click="openCreate">Create area</GnButton>
+      </template>
+    </GnPageHeader>
+ 
+    <AppLoadingState v-if="areasStore.isLoading" text="Loading areas tree" />
+ 
+    <AppErrorState
+      v-else-if="areasStore.error"
+      title="Areas loading failed"
+      :message="areasStore.error.message"
+      :retry="areasStore.loadAreas"
+    />
+ 
+    <AppEmptyState
+      v-else-if="areasStore.areaTree.length === 0"
+      title="No areas"
+      message="No areas found. Create one to get started."
+    />
+ 
+    <ul v-else class="area-tree">
+      <AreaTreeNode
+        v-for="area in areasStore.areaTree"
+        :key="area.id"
+        :area="area"
+        @rename="openRename"
+        @remove="remove"
+        @unassign="unassign"
+      />
+    </ul>
+ 
+    <GnModal :open="showCreateModal" title="Create area" @update:open="showCreateModal = $event">
+      <div class="form-group">
+        <GnInput v-model="createForm.type" label="Type" placeholder="room" />
+      </div>
+      <div class="form-group">
+        <GnInput v-model="createForm.alias" label="Alias" placeholder="kitchen" />
+      </div>
+      <div class="form-group">
+        <GnInput v-model="createForm.display_name" label="Display name" placeholder="Kitchen" />
+      </div>
+      <div v-if="createError" class="form-group">
+        <GnAlert variant="danger">{{ createError }}</GnAlert>
+      </div>
+      <template #footer>
+        <GnButton variant="secondary" @click="showCreateModal = false">Cancel</GnButton>
+        <GnButton variant="primary" icon="ph-plus" :loading="createLoading" @click="submitCreate">Create</GnButton>
+      </template>
+    </GnModal>
+ 
+    <GnModal :open="showRenameModal" title="Rename area" @update:open="showRenameModal = $event">
+      <div class="form-group">
+        <GnInput v-model="renameForm.display_name" label="Display name" />
+      </div>
+      <div v-if="renameError" class="form-group">
+        <GnAlert variant="danger">{{ renameError }}</GnAlert>
+      </div>
+      <template #footer>
+        <GnButton variant="secondary" @click="showRenameModal = false">Cancel</GnButton>
+        <GnButton variant="primary" icon="ph-check" :loading="renameLoading" @click="submitRename">Rename</GnButton>
+      </template>
+    </GnModal>
+  </section>
+</template>
+ 
+<script setup>
+import { ref, reactive, onMounted } from "vue";
+import { useAreasStore } from "../../../stores/areas";
+import {
+  GnPageHeader,
+  GnButton,
+  GnModal,
+  GnInput,
+  GnAlert,
+} from "gnexus-ui-kit/vue";
+import AreaTreeNode from "../components/AreaTreeNode.vue";
+import AppLoadingState from "../../../components/feedback/AppLoadingState.vue";
+import AppErrorState from "../../../components/feedback/AppErrorState.vue";
+import AppEmptyState from "../../../components/feedback/AppEmptyState.vue";
+ 
+const areasStore = useAreasStore();
+ 
+const showCreateModal = ref(false);
+const createLoading = ref(false);
+const createError = ref("");
+const createForm = reactive({ type: "", alias: "", display_name: "" });
+ 
+const showRenameModal = ref(false);
+const renameLoading = ref(false);
+const renameError = ref("");
+const renameForm = reactive({ areaId: null, display_name: "" });
+ 
+function openCreate() {
+  createForm.type = "";
+  createForm.alias = "";
+  createForm.display_name = "";
+  createError.value = "";
+  showCreateModal.value = true;
+}
+ 
+async function submitCreate() {
+  createLoading.value = true;
+  createError.value = "";
+ 
+  const result = await areasStore.createArea({ ...createForm });
+  createLoading.value = false;
+ 
+  if (!result.ok) {
+    createError.value = result.error?.message || "Failed to create area";
+    return;
+  }
+ 
+  showCreateModal.value = false;
+}
+ 
+function openRename(area) {
+  renameForm.areaId = area.id;
+  renameForm.display_name = area.display_name;
+  renameError.value = "";
+  showRenameModal.value = true;
+}
+ 
+async function submitRename() {
+  renameLoading.value = true;
+  renameError.value = "";
+ 
+  const result = await areasStore.renameArea(renameForm.areaId, renameForm.display_name);
+  renameLoading.value = false;
+ 
+  if (!result.ok) {
+    renameError.value = result.error?.message || "Failed to rename area";
+    return;
+  }
+ 
+  showRenameModal.value = false;
+}
+ 
+async function remove(area) {
+  if (!window.confirm(`Remove area "${area.display_name}"?`)) {
+    return;
+  }
+  await areasStore.removeArea(area.id);
+}
+ 
+async function unassign(area) {
+  await areasStore.unassignArea(area.id);
+}
+ 
+onMounted(() => {
+  areasStore.loadAreas();
+});
+</script>
+ 
+<style scoped>
+.form-group {
+  margin-bottom: 16px;
+}
+</style>
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/features/areas/pages/index.html b/webclient-vue/coverage/features/areas/pages/index.html new file mode 100644 index 0000000..ca26112 --- /dev/null +++ b/webclient-vue/coverage/features/areas/pages/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for features/areas/pages + + + + + + + + + +
+
+

All files features/areas/pages

+
+ +
+ 29.85% + Statements + 20/67 +
+ + +
+ 25% + Branches + 11/44 +
+ + +
+ 14.28% + Functions + 4/28 +
+ + +
+ 29.5% + Lines + 18/61 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
AreaTreePage.vue +
+
29.85%20/6725%11/4414.28%4/2829.5%18/61
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/features/devices/components/DeviceConnectionBadge.vue.html b/webclient-vue/coverage/features/devices/components/DeviceConnectionBadge.vue.html new file mode 100644 index 0000000..4ac343b --- /dev/null +++ b/webclient-vue/coverage/features/devices/components/DeviceConnectionBadge.vue.html @@ -0,0 +1,169 @@ + + + + + + Code coverage report for features/devices/components/DeviceConnectionBadge.vue + + + + + + + + + +
+
+

All files / features/devices/components DeviceConnectionBadge.vue

+
+ +
+ 100% + Statements + 11/11 +
+ + +
+ 100% + Branches + 6/6 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 9/9 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29  +4x +  +  +  +  +  +  +4x +  +  +  +  +  +  +4x +4x +4x +1x +  +  +3x +1x +  +  +2x +  +  + 
<template>
+  <GnBadge :variant="variant">{{ label }}</GnBadge>
+</template>
+ 
+<script setup>
+import { computed } from "vue";
+import { GnBadge } from "gnexus-ui-kit/vue";
+ 
+const props = defineProps({
+  status: {
+    type: String,
+    default: "unknown",
+  },
+});
+ 
+const label = computed(() => props.status || "unknown");
+const variant = computed(() => {
+  if (props.status === "active") {
+    return "success";
+  }
+ 
+  if (props.status === "lost") {
+    return "danger";
+  }
+ 
+  return "secondary";
+});
+</script>
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/features/devices/components/DeviceStateCell.vue.html b/webclient-vue/coverage/features/devices/components/DeviceStateCell.vue.html new file mode 100644 index 0000000..e87bcfc --- /dev/null +++ b/webclient-vue/coverage/features/devices/components/DeviceStateCell.vue.html @@ -0,0 +1,229 @@ + + + + + + Code coverage report for features/devices/components/DeviceStateCell.vue + + + + + + + + + +
+
+

All files / features/devices/components DeviceStateCell.vue

+
+ +
+ 100% + Statements + 16/16 +
+ + +
+ 85.71% + Branches + 18/21 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 16/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49  +5x +4x +  +  +  +  +  +  +5x +  +  +  +  +  +  +  +  +  +5x +5x +1x +  +  +4x +1x +  +  +3x +1x +  +  +2x +  +  +5x +4x +  +1x +  +1x +  +  +  +2x +  +  +  + 
<template>
+  <GnLoader v-if="state.status === 'loading'" circle :label="label" />
+  <GnBadge v-else :variant="variant">{{ label }}</GnBadge>
+</template>
+ 
+<script setup>
+import { computed } from "vue";
+import { GnLoader, GnBadge } from "gnexus-ui-kit/vue";
+ 
+const props = defineProps({
+  state: {
+    type: Object,
+    default: () => ({
+      status: "idle",
+      message: "Not loaded",
+    }),
+  },
+});
+ 
+const label = computed(() => {
+  if (props.state.status === "ready") {
+    return props.state.message || "ok";
+  }
+ 
+  if (props.state.status === "error") {
+    return props.state.message || "Loading error";
+  }
+ 
+  if (props.state.status === "skipped") {
+    return props.state.message || "Skipped";
+  }
+ 
+  return props.state.message || "Not loaded";
+});
+ 
+const variant = computed(() => {
+  switch (props.state.status) {
+    case "ready":
+      return "success";
+    case "error":
+      return "danger";
+    case "skipped":
+    case "idle":
+    default:
+      return "secondary";
+  }
+});
+</script>
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/features/devices/components/index.html b/webclient-vue/coverage/features/devices/components/index.html new file mode 100644 index 0000000..d7986ff --- /dev/null +++ b/webclient-vue/coverage/features/devices/components/index.html @@ -0,0 +1,131 @@ + + + + + + Code coverage report for features/devices/components + + + + + + + + + +
+
+

All files features/devices/components

+
+ +
+ 100% + Statements + 27/27 +
+ + +
+ 88.88% + Branches + 24/27 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 100% + Lines + 25/25 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
DeviceConnectionBadge.vue +
+
100%11/11100%6/6100%3/3100%9/9
DeviceStateCell.vue +
+
100%16/1685.71%18/21100%3/3100%16/16
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/features/devices/pages/DevicesScanningPage.vue.html b/webclient-vue/coverage/features/devices/pages/DevicesScanningPage.vue.html new file mode 100644 index 0000000..e08ea94 --- /dev/null +++ b/webclient-vue/coverage/features/devices/pages/DevicesScanningPage.vue.html @@ -0,0 +1,655 @@ + + + + + + Code coverage report for features/devices/pages/DevicesScanningPage.vue + + + + + + + + + +
+
+

All files / features/devices/pages DevicesScanningPage.vue

+
+ +
+ 48.21% + Statements + 27/56 +
+ + +
+ 53.44% + Branches + 31/58 +
+ + +
+ 43.33% + Functions + 13/30 +
+ + +
+ 48.07% + Lines + 25/52 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191  +3x +  +  +1x +  +  +  +  +  +3x +  +  +  +  +  +3x +  +  +  +  +  +  +3x +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +1x +1x +  +  +  +  +  +  +  +1x +  +  +  +  +1x +1x +  +  +1x +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +1x +  +  +1x +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +1x +1x +1x +1x +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
<template>
+  <section class="page">
+    <GnPageHeader title="Scanning" kicker="Devices">
+      <template #actions>
+        <div class="devices-actions">
+          <GnButton
+            :variant="scanningStore.mode === 'setup' ? 'primary' : 'secondary'"
+            @click="setMode('setup')"
+          >
+            Setup
+          </GnButton>
+          <GnButton
+            :variant="scanningStore.mode === 'all' ? 'primary' : 'secondary'"
+            @click="setMode('all')"
+          >
+            All
+          </GnButton>
+          <GnButton
+            :loading="scanningStore.isLoading"
+            icon="ph-magnifying-glass"
+            @click="scan"
+          >
+            Scan
+          </GnButton>
+        </div>
+      </template>
+    </GnPageHeader>
+ 
+    <AppLoadingState v-if="scanningStore.isLoading" text="Scanning network" />
+ 
+    <AppErrorState
+      v-else-if="scanningStore.error"
+      title="Scan failed"
+      :message="scanningStore.error.message"
+      :retry="scan"
+    />
+ 
+    <AppEmptyState
+      v-else-if="scanningStore.devices.length === 0"
+      title="No devices found"
+      message="Choose scan mode and click Scan to discover devices."
+    />
+ 
+    <div v-else class="devices-panel">
+      <div class="devices-summary">
+        <GnBadge variant="primary">{{ scanningStore.total }} found</GnBadge>
+        <GnBadge variant="secondary">Mode: {{ scanningStore.mode }}</GnBadge>
+      </div>
+ 
+      <GnTable
+        :columns="tableColumns"
+        :rows="tableRows"
+        caption="Discovered devices"
+      >
+        <template #cell-device_name="{ row }">
+          <strong>{{ row.device_name }}</strong>
+          <small>{{ row.device_type }}</small>
+        </template>
+ 
+        <template #cell-status="{ row }">
+          <GnBadge :variant="row.status === 'setup' ? 'warning' : 'success'">{{ row.status }}</GnBadge>
+        </template>
+ 
+        <template #cell-actions="{ row }">
+          <GnButton
+            v-if="row.status === 'setup'"
+            variant="primary"
+            icon="ph-plus"
+            @click="openSetup(row)"
+          >
+            Add
+          </GnButton>
+        </template>
+      </GnTable>
+    </div>
+ 
+    <GnModal
+      :open="showSetupModal"
+      title="Setup new device"
+      @update:open="showSetupModal = $event"
+    >
+      <div class="form-group">
+        <GnInput v-model="setupForm.alias" label="Alias" placeholder="kitchen_relay" />
+      </div>
+      <div class="form-group">
+        <GnInput v-model="setupForm.name" label="Name" placeholder="Kitchen Relay" />
+      </div>
+      <div class="form-group">
+        <GnInput v-model="setupForm.description" label="Description" />
+      </div>
+      <div v-if="setupError" class="form-group">
+        <GnAlert variant="danger">{{ setupError }}</GnAlert>
+      </div>
+      <template #footer>
+        <GnButton variant="secondary" @click="showSetupModal = false">Cancel</GnButton>
+        <GnButton variant="primary" icon="ph-plus" :loading="setupLoading" @click="submitSetup">
+          Add device
+        </GnButton>
+      </template>
+    </GnModal>
+  </section>
+</template>
+ 
+<script setup>
+import { ref, reactive, computed } from "vue";
+import { useScanningStore } from "../../../stores/scanning";
+import {
+  GnPageHeader,
+  GnButton,
+  GnBadge,
+  GnTable,
+  GnModal,
+  GnInput,
+  GnAlert,
+} from "gnexus-ui-kit/vue";
+import AppEmptyState from "../../../components/feedback/AppEmptyState.vue";
+import AppErrorState from "../../../components/feedback/AppErrorState.vue";
+import AppLoadingState from "../../../components/feedback/AppLoadingState.vue";
+ 
+const scanningStore = useScanningStore();
+ 
+const showSetupModal = ref(false);
+const setupLoading = ref(false);
+const setupError = ref("");
+const setupForm = reactive({
+  device_ip: "",
+  alias: "",
+  name: "",
+  description: "",
+});
+ 
+const tableColumns = [
+  { key: "device_name", label: "Device" },
+  { key: "ip_address", label: "IP" },
+  { key: "mac_address", label: "MAC" },
+  { key: "firmware_version", label: "Firmware" },
+  { key: "status", label: "Status" },
+  { key: "actions", label: "Actions" },
+];
+ 
+const tableRows = computed(() =>
+  scanningStore.devices.map((device) => ({
+    id: device.device_id || device.ip_address,
+    device_name: device.device_name || "Unknown",
+    device_type: device.device_type || "unknown",
+    ip_address: device.ip_address || "unknown",
+    mac_address: device.mac_address || "unknown",
+    firmware_version: device.firmware_version || "unknown",
+    status: device.status || "unknown",
+  }))
+);
+ 
+function setMode(mode) {
+  scanningStore.setMode(mode);
+}
+ 
+function scan() {
+  scanningStore.scan();
+}
+ 
+function openSetup(row) {
+  setupForm.device_ip = row.ip_address;
+  setupForm.alias = "";
+  setupForm.name = "";
+  setupForm.description = "";
+  setupError.value = "";
+  showSetupModal.value = true;
+}
+ 
+async function submitSetup() {
+  setupLoading.value = true;
+  setupError.value = "";
+ 
+  const result = await scanningStore.setupDevice({ ...setupForm });
+  setupLoading.value = false;
+ 
+  if (!result.ok) {
+    setupError.value = result.error?.message || "Failed to setup device";
+    return;
+  }
+ 
+  showSetupModal.value = false;
+}
+</script>
+ 
+<style scoped>
+.form-group {
+  margin-bottom: 16px;
+}
+</style>
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/features/devices/pages/index.html b/webclient-vue/coverage/features/devices/pages/index.html new file mode 100644 index 0000000..6bf0295 --- /dev/null +++ b/webclient-vue/coverage/features/devices/pages/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for features/devices/pages + + + + + + + + + +
+
+

All files features/devices/pages

+
+ +
+ 48.21% + Statements + 27/56 +
+ + +
+ 53.44% + Branches + 31/58 +
+ + +
+ 43.33% + Functions + 13/30 +
+ + +
+ 48.07% + Lines + 25/52 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
DevicesScanningPage.vue +
+
48.21%27/5653.44%31/5843.33%13/3048.07%25/52
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/features/scripts/pages/ScriptsActionsPage.vue.html b/webclient-vue/coverage/features/scripts/pages/ScriptsActionsPage.vue.html new file mode 100644 index 0000000..463020a --- /dev/null +++ b/webclient-vue/coverage/features/scripts/pages/ScriptsActionsPage.vue.html @@ -0,0 +1,436 @@ + + + + + + Code coverage report for features/scripts/pages/ScriptsActionsPage.vue + + + + + + + + + +
+
+

All files / features/scripts/pages ScriptsActionsPage.vue

+
+ +
+ 56% + Statements + 14/25 +
+ + +
+ 38.09% + Branches + 8/21 +
+ + +
+ 66.66% + Functions + 8/12 +
+ + +
+ 52.38% + Lines + 11/21 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118  +3x +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +1x +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +1x +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  + 
<template>
+  <section class="page">
+    <GnPageHeader title="Actions" kicker="Scripts">
+      <template #actions>
+        <GnBadge variant="primary">{{ scriptsStore.totalActions }} scripts</GnBadge>
+      </template>
+    </GnPageHeader>
+ 
+    <AppLoadingState v-if="scriptsStore.isLoadingActions" text="Loading actions" />
+ 
+    <AppErrorState
+      v-else-if="scriptsStore.errorActions"
+      title="Actions loading failed"
+      :message="scriptsStore.errorActions.message"
+      :retry="scriptsStore.loadActions"
+    />
+ 
+    <AppEmptyState
+      v-else-if="scriptsStore.actions.length === 0"
+      title="No action scripts"
+      message="No action scripts registered."
+    />
+ 
+    <div v-else class="area-grid">
+      <GnCard
+        v-for="script in scriptsStore.actions"
+        :key="script.alias"
+        :title="script.name"
+      >
+        <template #default>
+          <div v-html="script.icon" class="script-icon" />
+          <p v-if="script.description">{{ script.description }}</p>
+          <p>
+            <GnBadge :variant="script.state === 'enabled' ? 'success' : 'secondary'"
+            >{{ script.state }}</GnBadge>
+            <code>{{ script.alias }}</code>
+          </p>
+          <small>{{ script.author }}</small>
+        </template>
+        <template #footer>
+          <GnButton
+            variant="primary"
+            icon="ph-play"
+            :loading="scriptsStore.isRunning(script.alias)"
+            :disabled="script.state !== 'enabled'"
+            @click="run(script.alias)"
+          >
+            Run
+          </GnButton>
+        </template>
+      </GnCard>
+    </div>
+ 
+    <GnAlert v-if="resultAlert" :variant="resultAlert.variant" class="result-alert">
+      <strong>{{ resultAlert.title }}</strong>
+      <p v-if="resultAlert.message">{{ resultAlert.message }}</p>
+    </GnAlert>
+  </section>
+</template>
+ 
+<script setup>
+import { ref, onMounted, computed } from "vue";
+import { useScriptsStore } from "../../../stores/scripts";
+import {
+  GnPageHeader,
+  GnBadge,
+  GnCard,
+  GnButton,
+  GnAlert,
+} from "gnexus-ui-kit/vue";
+import AppEmptyState from "../../../components/feedback/AppEmptyState.vue";
+import AppErrorState from "../../../components/feedback/AppErrorState.vue";
+import AppLoadingState from "../../../components/feedback/AppLoadingState.vue";
+ 
+const scriptsStore = useScriptsStore();
+const resultAlert = ref(null);
+ 
+const resultAlertComputed = computed(() => {
+  const r = scriptsStore.lastRunResult;
+  if (!r) return null;
+ 
+  if (r.ok) {
+    return {
+      variant: "success",
+      title: `Ran ${r.alias}`,
+      message: r.execTime ? `Exec time: ${r.execTime}` : undefined,
+    };
+  }
+ 
+  return {
+    variant: "danger",
+    title: `Failed ${r.alias}`,
+    message: r.error?.message || "Unknown error",
+  };
+});
+ 
+async function run(alias) {
+  resultAlert.value = null;
+  const result = await scriptsStore.runScript(alias);
+  resultAlert.value = resultAlertComputed.value;
+}
+ 
+onMounted(() => {
+  scriptsStore.loadActions();
+});
+</script>
+ 
+<style scoped>
+.script-icon {
+  font-size: 32px;
+  margin-bottom: 12px;
+}
+ 
+.result-alert {
+  margin-top: 24px;
+}
+</style>
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/features/scripts/pages/index.html b/webclient-vue/coverage/features/scripts/pages/index.html new file mode 100644 index 0000000..dd69271 --- /dev/null +++ b/webclient-vue/coverage/features/scripts/pages/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for features/scripts/pages + + + + + + + + + +
+
+

All files features/scripts/pages

+
+ +
+ 56% + Statements + 14/25 +
+ + +
+ 38.09% + Branches + 8/21 +
+ + +
+ 66.66% + Functions + 8/12 +
+ + +
+ 52.38% + Lines + 11/21 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
ScriptsActionsPage.vue +
+
56%14/2538.09%8/2166.66%8/1252.38%11/21
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/index.html b/webclient-vue/coverage/index.html new file mode 100644 index 0000000..f039897 --- /dev/null +++ b/webclient-vue/coverage/index.html @@ -0,0 +1,266 @@ + + + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 79.96% + Statements + 459/574 +
+ + +
+ 68% + Branches + 272/400 +
+ + +
+ 70.68% + Functions + 135/191 +
+ + +
+ 80.81% + Lines + 438/542 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
api +
+
94.82%55/5888.13%52/5981.81%9/1198.21%55/56
api/modules +
+
96.77%30/3177.77%7/9100%25/2596.77%30/31
components/feedback +
+
100%5/5100%4/4100%2/2100%5/5
components/layout +
+
100%3/3100%0/0100%1/1100%3/3
features/areas/components +
+
90.32%28/31100%39/3983.33%15/1888.88%24/27
features/areas/pages +
+
29.85%20/6725%11/4414.28%4/2829.5%18/61
features/devices/components +
+
100%27/2788.88%24/27100%6/6100%25/25
features/devices/pages +
+
48.21%27/5653.44%31/5843.33%13/3048.07%25/52
features/scripts/pages +
+
56%14/2538.09%8/2166.66%8/1252.38%11/21
stores +
+
93.03%227/24466.38%79/11990.9%50/5593.19%219/235
test/mocks +
+
85.18%23/2785%17/2066.66%2/388.46%23/26
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/prettify.css b/webclient-vue/coverage/prettify.css new file mode 100644 index 0000000..b317a7c --- /dev/null +++ b/webclient-vue/coverage/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/webclient-vue/coverage/prettify.js b/webclient-vue/coverage/prettify.js new file mode 100644 index 0000000..b322523 --- /dev/null +++ b/webclient-vue/coverage/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/webclient-vue/coverage/sort-arrow-sprite.png b/webclient-vue/coverage/sort-arrow-sprite.png new file mode 100644 index 0000000..6ed6831 --- /dev/null +++ b/webclient-vue/coverage/sort-arrow-sprite.png Binary files differ diff --git a/webclient-vue/coverage/sorter.js b/webclient-vue/coverage/sorter.js new file mode 100644 index 0000000..4ed70ae --- /dev/null +++ b/webclient-vue/coverage/sorter.js @@ -0,0 +1,210 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + + // Try to create a RegExp from the searchValue. If it fails (invalid regex), + // it will be treated as a plain text search + let searchRegex; + try { + searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive + } catch (error) { + searchRegex = null; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let isMatch = false; + + if (searchRegex) { + // If a valid regex was created, use it for matching + isMatch = searchRegex.test(row.textContent); + } else { + // Otherwise, fall back to the original plain text search + isMatch = row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()); + } + + row.style.display = isMatch ? '' : 'none'; + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/webclient-vue/coverage/stores/areas.js.html b/webclient-vue/coverage/stores/areas.js.html new file mode 100644 index 0000000..8d9105b --- /dev/null +++ b/webclient-vue/coverage/stores/areas.js.html @@ -0,0 +1,454 @@ + + + + + + Code coverage report for stores/areas.js + + + + + + + + + +
+
+

All files / stores areas.js

+
+ +
+ 93.22% + Statements + 55/59 +
+ + +
+ 68.75% + Branches + 22/32 +
+ + +
+ 84.61% + Functions + 11/13 +
+ + +
+ 94.54% + Lines + 52/55 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124  +  +  +  +15x +15x +  +15x +22x +  +  +15x +22x +22x +22x +  +22x +2x +  +20x +  +  +  +15x +  +  +  +15x +  +  +2x +14x +  +  +  +  +  +  +  +  +  +  +  +  +15x +  +  +  +  +  +6x +6x +6x +  +6x +6x +  +6x +6x +6x +  +6x +1x +  +  +1x +1x +  +  +5x +6x +  +  +  +2x +  +2x +2x +2x +2x +  +  +  +2x +  +  +  +2x +  +2x +2x +2x +2x +  +  +  +2x +  +  +  +2x +  +2x +4x +  +  +2x +  +  +  +1x +  +1x +1x +1x +1x +  +  +  +1x +  +  +  + 
import { defineStore } from "pinia";
+import { areasApi } from "../api/modules/areas";
+ 
+function buildAreaTree(areas) {
+  const map = {};
+  const roots = [];
+ 
+  for (const area of areas) {
+    map[area.id] = { ...area, children: [] };
+  }
+ 
+  for (const area of areas) {
+    const node = map[area.id];
+    const isSelfReference = area.parent_id && area.parent_id == area.id;
+    const parentExists = area.parent_id && map[area.parent_id];
+ 
+    if (!isSelfReference && parentExists) {
+      map[area.parent_id].children.push(node);
+    } else {
+      roots.push(node);
+    }
+  }
+ 
+  Iif (roots.length === 0 && areas.length > 0) {
+    return Object.values(map);
+  }
+ 
+  return roots;
+}
+ 
+export const useAreasStore = defineStore("areas", {
+  state: () => ({
+    areas: [],
+    isLoading: false,
+    error: null,
+    _listAbortController: null,
+  }),
+ 
+  getters: {
+    areasById(state) {
+      return Object.fromEntries(state.areas.map((area) => [String(area.id), area]));
+    },
+ 
+    areaTree(state) {
+      return buildAreaTree(state.areas);
+    },
+  },
+ 
+  actions: {
+    async loadAreas() {
+      this._listAbortController?.abort();
+      const controller = new AbortController();
+      this._listAbortController = controller;
+ 
+      this.isLoading = true;
+      this.error = null;
+ 
+      const result = await areasApi.list({ signal: controller.signal });
+      this._listAbortController = null;
+      this.isLoading = false;
+ 
+      if (!result.ok) {
+        Iif (result.error?.type === "timeout") {
+          return result;
+        }
+        this.error = result.error;
+        return result;
+      }
+ 
+      this.areas = result.data?.data?.areas || [];
+      return result;
+    },
+ 
+    async createArea(payload) {
+      const result = await areasApi.newArea(payload);
+ 
+      Eif (result.ok) {
+        const newArea = result.data?.data?.area;
+        Eif (newArea) {
+          this.areas.push(newArea);
+        }
+      }
+ 
+      return result;
+    },
+ 
+    async renameArea(areaId, displayName) {
+      const result = await areasApi.updateDisplayName({ area_id: areaId, display_name: displayName });
+ 
+      Eif (result.ok) {
+        const idx = this.areas.findIndex((a) => a.id === areaId);
+        Eif (idx !== -1) {
+          this.areas[idx] = { ...this.areas[idx], display_name: displayName };
+        }
+      }
+ 
+      return result;
+    },
+ 
+    async removeArea(areaId) {
+      const result = await areasApi.remove(areaId);
+ 
+      Eif (result.ok) {
+        this.areas = this.areas.filter((a) => a.id !== areaId);
+      }
+ 
+      return result;
+    },
+ 
+    async unassignArea(areaId) {
+      const result = await areasApi.unassign(areaId);
+ 
+      Eif (result.ok) {
+        const idx = this.areas.findIndex((a) => a.id === areaId);
+        Eif (idx !== -1) {
+          this.areas[idx] = { ...this.areas[idx], parent_id: 0 };
+        }
+      }
+ 
+      return result;
+    },
+  },
+});
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/stores/devices.js.html b/webclient-vue/coverage/stores/devices.js.html new file mode 100644 index 0000000..1f33d2a --- /dev/null +++ b/webclient-vue/coverage/stores/devices.js.html @@ -0,0 +1,649 @@ + + + + + + Code coverage report for stores/devices.js + + + + + + + + + +
+
+

All files / stores devices.js

+
+ +
+ 92.64% + Statements + 63/68 +
+ + +
+ 62.5% + Branches + 25/40 +
+ + +
+ 93.33% + Functions + 14/15 +
+ + +
+ 92.53% + Lines + 62/67 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189  +  +  +2x +  +  +13x +  +  +  +6x +  +  +  +  +  +  +  +  +  +  +  +1x +1x +  +1x +  +  +  +  +  +  +  +  +  +1x +1x +  +1x +  +  +  +  +  +  +  +  +  +3x +3x +  +  +3x +2x +2x +2x +  +  +  +3x +  +  +2x +10x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  +2x +2x +2x +  +2x +2x +  +2x +2x +2x +  +2x +1x +  +  +1x +1x +  +  +1x +2x +2x +  +  +  +5x +  +5x +1x +  +  +4x +  +  +  +  +  +  +3x +3x +3x +3x +3x +  +3x +3x +  +3x +3x +1x +  +  +  +  +  +2x +  +  +  +2x +  +  +  +3x +3x +2x +  +2x +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +3x +  +  +  +  +  +1x +1x +  +1x +  +1x +1x +  +  +  + 
import { defineStore } from "pinia";
+import { devicesApi } from "../api/modules/devices";
+ 
+const DEFAULT_STATE_CONCURRENCY = 4;
+ 
+function getDeviceId(device) {
+  return String(device?.id || "");
+}
+ 
+function makeDeviceStatePatch(device, patch) {
+  return {
+    deviceId: getDeviceId(device),
+    status: "idle",
+    message: "",
+    response: null,
+    connectionStatus: device?.connection_status || "unknown",
+    updatedAt: null,
+    ...patch,
+  };
+}
+ 
+function normalizeStatusSuccess(device, result) {
+  const payload = result.data?.data?.device || {};
+  const response = payload.device_response || {};
+ 
+  return makeDeviceStatePatch(device, {
+    status: "ready",
+    message: response.status || "ok",
+    response,
+    connectionStatus: "active",
+    updatedAt: new Date().toISOString(),
+  });
+}
+ 
+function normalizeStatusError(device, result) {
+  const raw = result.error?.raw || {};
+  const connectionStatus = raw?.data?.connection_status || device?.connection_status || "unknown";
+ 
+  return makeDeviceStatePatch(device, {
+    status: "error",
+    message: result.error?.message || "Device state is unavailable",
+    response: raw,
+    connectionStatus,
+    updatedAt: new Date().toISOString(),
+  });
+}
+ 
+async function runLimited(items, concurrency, worker) {
+  let nextIndex = 0;
+  const poolSize = Math.max(1, Math.min(concurrency, items.length));
+ 
+  async function runWorker() {
+    while (nextIndex < items.length) {
+      const item = items[nextIndex];
+      nextIndex += 1;
+      await worker(item);
+    }
+  }
+ 
+  await Promise.all(Array.from({ length: poolSize }, runWorker));
+}
+ 
+export const useDevicesStore = defineStore("devices", {
+  state: () => ({
+    devices: [],
+    isLoading: false,
+    error: null,
+    isLoadingStates: false,
+    stateError: null,
+    stateByDeviceId: {},
+    stateRunId: 0,
+    rebootingIds: new Set(),
+    _listAbortController: null,
+    lastLoadedAt: null,
+  }),
+ 
+  getters: {
+    total(state) {
+      return state.devices.length;
+    },
+    isRebooting: (state) => (id) => state.rebootingIds.has(id),
+  },
+ 
+  actions: {
+    async loadDevices() {
+      this._listAbortController?.abort();
+      const controller = new AbortController();
+      this._listAbortController = controller;
+ 
+      this.isLoading = true;
+      this.error = null;
+ 
+      const result = await devicesApi.list({ signal: controller.signal });
+      this._listAbortController = null;
+      this.isLoading = false;
+ 
+      if (!result.ok) {
+        Iif (result.error?.type === "timeout") {
+          return result;
+        }
+        this.error = result.error;
+        return result;
+      }
+ 
+      this.devices = result.data?.data?.devices || [];
+      this.lastLoadedAt = new Date().toISOString();
+      return result;
+    },
+ 
+    setDeviceState(device, patch) {
+      const deviceId = getDeviceId(device);
+ 
+      if (!deviceId) {
+        return;
+      }
+ 
+      this.stateByDeviceId = {
+        ...this.stateByDeviceId,
+        [deviceId]: makeDeviceStatePatch(device, patch),
+      };
+    },
+ 
+    async loadDeviceStates(options = {}) {
+      const runId = this.stateRunId + 1;
+      this.stateRunId = runId;
+      this.isLoadingStates = true;
+      this.stateError = null;
+      this.stateByDeviceId = {};
+ 
+      const devices = this.devices.slice();
+      const targets = [];
+ 
+      for (const device of devices) {
+        if (device.connection_status === "lost") {
+          this.setDeviceState(device, {
+            status: "skipped",
+            message: "Connection lost",
+            connectionStatus: "lost",
+          });
+        } else {
+          this.setDeviceState(device, {
+            status: "loading",
+            message: "Loading",
+          });
+          targets.push(device);
+        }
+      }
+ 
+      try {
+        await runLimited(targets, options.concurrency || DEFAULT_STATE_CONCURRENCY, async (device) => {
+          const result = await devicesApi.status(device.id);
+ 
+          Iif (this.stateRunId !== runId) {
+            return;
+          }
+ 
+          this.stateByDeviceId = {
+            ...this.stateByDeviceId,
+            [getDeviceId(device)]: result.ok
+              ? normalizeStatusSuccess(device, result)
+              : normalizeStatusError(device, result),
+          };
+        });
+      } catch (error) {
+        if (this.stateRunId === runId) {
+          this.stateError = {
+            type: "state_loader_error",
+            message: error?.message || "Device states loader failed",
+          };
+        }
+      } finally {
+        Eif (this.stateRunId === runId) {
+          this.isLoadingStates = false;
+        }
+      }
+    },
+ 
+    async rebootDevice(id) {
+      const deviceId = String(id);
+      this.rebootingIds.add(deviceId);
+ 
+      const result = await devicesApi.reboot(id);
+ 
+      this.rebootingIds.delete(deviceId);
+      return result;
+    },
+  },
+});
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/stores/favorites.js.html b/webclient-vue/coverage/stores/favorites.js.html new file mode 100644 index 0000000..58d6fbf --- /dev/null +++ b/webclient-vue/coverage/stores/favorites.js.html @@ -0,0 +1,235 @@ + + + + + + Code coverage report for stores/favorites.js + + + + + + + + + +
+
+

All files / stores favorites.js

+
+ +
+ 100% + Statements + 20/20 +
+ + +
+ 100% + Branches + 6/6 +
+ + +
+ 100% + Functions + 8/8 +
+ + +
+ 100% + Lines + 19/19 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51  +  +3x +  +  +22x +22x +  +1x +  +  +  +  +10x +  +  +3x +22x +  +  +  +  +  +34x +  +  +  +10x +10x +8x +8x +  +  +  +  +3x +2x +  +  +  +3x +1x +1x +  +  +2x +2x +  +  +  + 
import { defineStore } from "pinia";
+ 
+const STORAGE_KEY = "sh_fav_areas";
+ 
+function readFavorites() {
+  try {
+    return JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]").map(String);
+  } catch (_) {
+    return [];
+  }
+}
+ 
+function writeFavorites(ids) {
+  localStorage.setItem(STORAGE_KEY, JSON.stringify(ids.map(String)));
+}
+ 
+export const useFavoritesStore = defineStore("favorites", {
+  state: () => ({
+    ids: readFavorites(),
+  }),
+ 
+  actions: {
+    has(id) {
+      return this.ids.includes(String(id));
+    },
+ 
+    add(id) {
+      const nextId = String(id);
+      if (!this.ids.includes(nextId)) {
+        this.ids.push(nextId);
+        writeFavorites(this.ids);
+      }
+    },
+ 
+    remove(id) {
+      this.ids = this.ids.filter((item) => item !== String(id));
+      writeFavorites(this.ids);
+    },
+ 
+    toggle(id) {
+      if (this.has(id)) {
+        this.remove(id);
+        return false;
+      }
+ 
+      this.add(id);
+      return true;
+    },
+  },
+});
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/stores/index.html b/webclient-vue/coverage/stores/index.html new file mode 100644 index 0000000..b2c80ed --- /dev/null +++ b/webclient-vue/coverage/stores/index.html @@ -0,0 +1,176 @@ + + + + + + Code coverage report for stores + + + + + + + + + +
+
+

All files stores

+
+ +
+ 93.03% + Statements + 227/244 +
+ + +
+ 66.38% + Branches + 79/119 +
+ + +
+ 90.9% + Functions + 50/55 +
+ + +
+ 93.19% + Lines + 219/235 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
areas.js +
+
93.22%55/5968.75%22/3284.61%11/1394.54%52/55
devices.js +
+
92.64%63/6862.5%25/4093.33%14/1592.53%62/67
favorites.js +
+
100%20/20100%6/6100%8/8100%19/19
scanning.js +
+
95.45%21/2262.5%5/8100%5/595.45%21/22
scripts.js +
+
90.66%68/7563.63%21/3385.71%12/1490.27%65/72
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/stores/scanning.js.html b/webclient-vue/coverage/stores/scanning.js.html new file mode 100644 index 0000000..e3df393 --- /dev/null +++ b/webclient-vue/coverage/stores/scanning.js.html @@ -0,0 +1,259 @@ + + + + + + Code coverage report for stores/scanning.js + + + + + + + + + +
+
+

All files / stores scanning.js

+
+ +
+ 95.45% + Statements + 21/22 +
+ + +
+ 62.5% + Branches + 5/8 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 95.45% + Lines + 21/22 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59  +  +  +2x +7x +  +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +5x +5x +5x +  +5x +5x +  +  +5x +  +  +  +1x +1x +  +1x +1x +  +  +1x +1x +  +  +4x +5x +  +  +  +2x +2x +2x +  +  +  +2x +  +  +  + 
import { defineStore } from "pinia";
+import { devicesApi } from "../api/modules/devices";
+ 
+export const useScanningStore = defineStore("scanning", {
+  state: () => ({
+    mode: "setup",
+    devices: [],
+    isLoading: false,
+    error: null,
+    _scanAbortController: null,
+  }),
+ 
+  getters: {
+    total(state) {
+      return state.devices.length;
+    },
+  },
+ 
+  actions: {
+    async scan() {
+      this._scanAbortController?.abort();
+      const controller = new AbortController();
+      this._scanAbortController = controller;
+ 
+      this.isLoading = true;
+      this.error = null;
+ 
+      const result =
+        this.mode === "setup"
+          ? await devicesApi.scanningSetup({ signal: controller.signal })
+          : await devicesApi.scanningAll({ signal: controller.signal });
+ 
+      this._scanAbortController = null;
+      this.isLoading = false;
+ 
+      Eif (!result.ok) {
+        Iif (result.error?.type === "timeout") {
+          return result;
+        }
+        this.error = result.error;
+        return result;
+      }
+ 
+      this.devices = result.data?.data?.devices || [];
+      return result;
+    },
+ 
+    setMode(mode) {
+      this.mode = mode;
+      this.devices = [];
+      this.error = null;
+    },
+ 
+    async setupDevice(payload) {
+      return devicesApi.setupNewDevice(payload);
+    },
+  },
+});
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/stores/scripts.js.html b/webclient-vue/coverage/stores/scripts.js.html new file mode 100644 index 0000000..0cca471 --- /dev/null +++ b/webclient-vue/coverage/stores/scripts.js.html @@ -0,0 +1,535 @@ + + + + + + Code coverage report for stores/scripts.js + + + + + + + + + +
+
+

All files / stores scripts.js

+
+ +
+ 90.66% + Statements + 68/75 +
+ + +
+ 63.63% + Branches + 21/33 +
+ + +
+ 85.71% + Functions + 12/14 +
+ + +
+ 90.27% + Lines + 65/72 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151  +  +  +3x +12x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +1x +  +  +  +  +4x +4x +4x +  +4x +4x +  +4x +4x +4x +  +4x +1x +  +  +1x +1x +  +  +3x +4x +  +  +  +2x +2x +2x +  +2x +2x +  +2x +2x +2x +  +2x +1x +1x +  +  +  +  +  +1x +2x +  +  +  +2x +2x +2x +  +2x +2x +  +2x +2x +2x +  +2x +1x +1x +  +  +  +  +  +1x +2x +  +  +  +3x +3x +  +3x +  +3x +  +3x +1x +1x +  +  +2x +  +  +  +  +  +3x +  +  +  +1x +  +1x +1x +1x +1x +  +  +  +1x +  +  +  +1x +  +1x +1x +1x +1x +  +  +  +1x +  +  +  + 
import { defineStore } from "pinia";
+import { scriptsApi } from "../api/modules/scripts";
+ 
+export const useScriptsStore = defineStore("scripts", {
+  state: () => ({
+    actions: [],
+    regular: [],
+    scopes: [],
+    isLoadingActions: false,
+    isLoadingRegular: false,
+    isLoadingScopes: false,
+    errorActions: null,
+    errorRegular: null,
+    errorScopes: null,
+    runningAliases: new Set(),
+    lastRunResult: null,
+    _actionsAbortController: null,
+    _regularAbortController: null,
+    _scopesAbortController: null,
+  }),
+ 
+  getters: {
+    totalActions: (state) => state.actions.length,
+    totalRegular: (state) => state.regular.length,
+    totalScopes: (state) => state.scopes.length,
+    isRunning: (state) => (alias) => state.runningAliases.has(alias),
+  },
+ 
+  actions: {
+    async loadActions() {
+      this._actionsAbortController?.abort();
+      const controller = new AbortController();
+      this._actionsAbortController = controller;
+ 
+      this.isLoadingActions = true;
+      this.errorActions = null;
+ 
+      const result = await scriptsApi.actionsList({ signal: controller.signal });
+      this._actionsAbortController = null;
+      this.isLoadingActions = false;
+ 
+      if (!result.ok) {
+        Iif (result.error?.type === "timeout") {
+          return result;
+        }
+        this.errorActions = result.error;
+        return result;
+      }
+ 
+      this.actions = result.data?.data?.scripts || [];
+      return result;
+    },
+ 
+    async loadRegular() {
+      this._regularAbortController?.abort();
+      const controller = new AbortController();
+      this._regularAbortController = controller;
+ 
+      this.isLoadingRegular = true;
+      this.errorRegular = null;
+ 
+      const result = await scriptsApi.regularList({ signal: controller.signal });
+      this._regularAbortController = null;
+      this.isLoadingRegular = false;
+ 
+      if (!result.ok) {
+        Eif (result.error?.type === "timeout") {
+          return result;
+        }
+        this.errorRegular = result.error;
+        return result;
+      }
+ 
+      this.regular = result.data?.data?.scripts || [];
+      return result;
+    },
+ 
+    async loadScopes() {
+      this._scopesAbortController?.abort();
+      const controller = new AbortController();
+      this._scopesAbortController = controller;
+ 
+      this.isLoadingScopes = true;
+      this.errorScopes = null;
+ 
+      const result = await scriptsApi.scopesList({ signal: controller.signal });
+      this._scopesAbortController = null;
+      this.isLoadingScopes = false;
+ 
+      if (!result.ok) {
+        Eif (result.error?.type === "timeout") {
+          return result;
+        }
+        this.errorScopes = result.error;
+        return result;
+      }
+ 
+      this.scopes = result.data?.data?.scopes || [];
+      return result;
+    },
+ 
+    async runScript(alias, params = {}) {
+      this.runningAliases.add(alias);
+      this.lastRunResult = null;
+ 
+      const result = await scriptsApi.runAction(alias, params);
+ 
+      this.runningAliases.delete(alias);
+ 
+      if (!result.ok) {
+        this.lastRunResult = { alias, ok: false, error: result.error };
+        return result;
+      }
+ 
+      this.lastRunResult = {
+        alias,
+        ok: true,
+        data: result.data?.data?.return?.result,
+        execTime: result.data?.data?.return?.exec_time,
+      };
+      return result;
+    },
+ 
+    async setRegularState(alias, enabled) {
+      const result = await scriptsApi.setRegularState(alias, enabled);
+ 
+      Eif (result.ok) {
+        const idx = this.regular.findIndex((s) => s.alias === alias);
+        Eif (idx !== -1) {
+          this.regular[idx] = { ...this.regular[idx], state: enabled ? "enabled" : "disabled" };
+        }
+      }
+ 
+      return result;
+    },
+ 
+    async setScopeState(name, enabled) {
+      const result = await scriptsApi.setScopeState(name, enabled);
+ 
+      Eif (result.ok) {
+        const idx = this.scopes.findIndex((s) => s.name === name);
+        Eif (idx !== -1) {
+          this.scopes[idx] = { ...this.scopes[idx], state: enabled ? "enabled" : "disabled" };
+        }
+      }
+ 
+      return result;
+    },
+  },
+});
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/test/mocks/handlers.js.html b/webclient-vue/coverage/test/mocks/handlers.js.html new file mode 100644 index 0000000..5d49d42 --- /dev/null +++ b/webclient-vue/coverage/test/mocks/handlers.js.html @@ -0,0 +1,433 @@ + + + + + + Code coverage report for test/mocks/handlers.js + + + + + + + + + +
+
+

All files / test/mocks handlers.js

+
+ +
+ 85.18% + Statements + 23/27 +
+ + +
+ 85% + Branches + 17/20 +
+ + +
+ 66.66% + Functions + 2/3 +
+ + +
+ 88.46% + Lines + 23/26 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117  +  +20x +  +  +9x +9x +  +9x +4x +  +  +  +  +  +  +  +  +  +  +  +5x +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +3x +2x +  +  +  +  +  +  +  +  +  +  +1x +1x +  +  +  +  +  +  +  +4x +4x +4x +  +4x +1x +  +  +  +  +  +  +  +3x +1x +  +  +  +  +  +2x +1x +  +  +  +  +  +  +  +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { http, HttpResponse } from "msw";
+ 
+export const handlers = [
+  // GET requests via proxy.php
+  http.get("/proxy.php", ({ request }) => {
+    const url = new URL(request.url);
+    const path = url.searchParams.get("path");
+ 
+    if (path === "/api/v1/areas/list") {
+      return HttpResponse.json({
+        status: true,
+        data: {
+          areas: [
+            { id: 1, type: "room", alias: "kitchen", display_name: "Kitchen", parent_id: 0 },
+            { id: 2, type: "room", alias: "hall", display_name: "Hall", parent_id: 0 },
+          ],
+          total: 2,
+        },
+      });
+    }
+ 
+    if (path === "/api/v1/devices/scanning/setup") {
+      return HttpResponse.json({
+        status: true,
+        data: {
+          devices: [
+            {
+              device_name: "New Device",
+              device_type: "relay",
+              ip_address: "192.168.1.50",
+              mac_address: "A4:CF:12:9B:3F:D2",
+              firmware_version: "1.0",
+              status: "setup",
+            },
+          ],
+        },
+      });
+    }
+ 
+    Iif (path === "/api/v1/devices/list") {
+      return HttpResponse.json({
+        status: true,
+        data: {
+          devices: [
+            { id: 1, name: "Relay 1", alias: "relay_1", device_type: "relay", device_ip: "192.168.1.10", connection_status: "active" },
+          ],
+          total: 1,
+        },
+      });
+    }
+ 
+    if (path === "/api/v1/scripts/actions/list") {
+      return HttpResponse.json({
+        status: true,
+        data: {
+          scripts: [
+            { alias: "kitchen_light", name: "Kitchen Light", icon: '<i class="ph ph-lightbulb"></i>', state: "enabled", author: "Test" },
+          ],
+          total: 1,
+        },
+      });
+    }
+ 
+    Eif (path?.startsWith("/api/v1/areas/id/") && path.endsWith("/remove")) {
+      return HttpResponse.json({ status: true });
+    }
+ 
+    return new HttpResponse(null, { status: 404 });
+  }),
+ 
+  // POST requests via proxy.php
+  http.post("/proxy.php", async ({ request }) => {
+    const url = new URL(request.url);
+    const path = url.searchParams.get("path");
+    const body = await request.json().catch(() => ({}));
+ 
+    if (path === "/api/v1/areas/new-area") {
+      return HttpResponse.json({
+        status: true,
+        data: {
+          area: { id: 3, type: body.type, alias: body.alias, display_name: body.display_name, parent_id: 0 },
+        },
+      });
+    }
+ 
+    if (path === "/api/v1/areas/update-display-name") {
+      return HttpResponse.json({
+        status: true,
+        data: { area_id: body.area_id, display_name: body.display_name },
+      });
+    }
+ 
+    if (path === "/api/v1/devices/setup/new-device") {
+      return HttpResponse.json({
+        status: true,
+        data: {
+          device: { id: 2, name: body.name, alias: body.alias, device_type: "relay", device_ip: body.device_ip },
+        },
+      });
+    }
+ 
+    Eif (path === "/api/v1/scripts/actions/run") {
+      return HttpResponse.json({
+        status: true,
+        data: {
+          return: {
+            result: { ok: true },
+            exec_time: "0.042 seconds",
+          },
+        },
+      });
+    }
+ 
+    return new HttpResponse(null, { status: 404 });
+  }),
+];
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/coverage/test/mocks/index.html b/webclient-vue/coverage/test/mocks/index.html new file mode 100644 index 0000000..38fd128 --- /dev/null +++ b/webclient-vue/coverage/test/mocks/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for test/mocks + + + + + + + + + +
+
+

All files test/mocks

+
+ +
+ 85.18% + Statements + 23/27 +
+ + +
+ 85% + Branches + 17/20 +
+ + +
+ 66.66% + Functions + 2/3 +
+ + +
+ 88.46% + Lines + 23/26 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
handlers.js +
+
85.18%23/2785%17/2066.66%2/388.46%23/26
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/webclient-vue/docs/migration-plan.md b/webclient-vue/docs/migration-plan.md index 6a81947..1cf228c 100644 --- a/webclient-vue/docs/migration-plan.md +++ b/webclient-vue/docs/migration-plan.md @@ -1,6 +1,6 @@ # Migration Plan -## Phase 1 - Specification And Contracts +## Phase 1 - Specification And Contracts ✅ - Document current routes and workflows. - Document API calls and response shapes. @@ -12,7 +12,7 @@ - Complete docs in `webclient-vue/docs/`. -## Phase 2 - Project Scaffold +## Phase 2 - Project Scaffold ✅ - Create Vue app under `webclient-vue/`. - Configure build tooling. @@ -25,7 +25,7 @@ - New client opens locally and can call `GET /api/v1/areas/list`. -### Phase 2.1 - Minimal Vertical Slice +### Phase 2.1 - Minimal Vertical Slice ✅ Build the smallest complete app slice: @@ -48,7 +48,7 @@ - UI-kit access is isolated behind local adapter components, even if adapters are initially minimal. -## Phase 3 - Read-Only Screens +## Phase 3 - Read-Only Screens ✅ Implement without mutations first: @@ -64,7 +64,7 @@ - New client can display the same operational data as the current client. -## Phase 4 - Mutations And Modals +## Phase 4 - Mutations And Modals ✅ Implement user actions: @@ -83,7 +83,7 @@ - New client covers daily operational workflows. -## Phase 5 - Hardening +## Phase 5 - Hardening ✅ - Add concurrency limits to device live-state loading. - Add request cancellation on route leave/modal close. @@ -95,6 +95,73 @@ - New client is safe to use as primary UI. +## Phase 5.5 - Test Infrastructure & Unit Tests + +Install and configure testing stack for Vite + Vue: + +- **Vitest** — test runner (native Vite integration, replaces Jest). +- **@vue/test-utils** — mount and interact with Vue components. +- **jsdom** — DOM environment for component tests. +- **@vitest/coverage-v8** — code coverage reports. + +Unit test targets (no DOM, fast, isolated): + +| Target | What to test | +|--------|-------------| +| `api/client.js` | `apiRequest` success path, error path, network failure, abort | +| `api/http.js` | `requestHttp` URL building, proxy wrapping, timeout abort, JSON parse | +| `api/mappers.js` | `unifyDeviceFields` normalizes snake_case → camelCase | +| `stores/favorites.js` | localStorage read/write, toggle, add, remove | +| `stores/areas.js` | `buildAreaTree` handles roots, children, self-reference, cycles | +| `stores/devices.js` | `makeDeviceStatePatch`, `normalizeStatusSuccess`, `normalizeStatusError` | + +Deliverable: + +- `npm test` runs unit tests. +- Coverage report generated in `coverage/`. + +## Phase 5.6 - Component Tests + +Test Vue components in isolation with mocked dependencies: + +| Component | What to test | +|-----------|-------------| +| `AppEmptyState` | renders title and message, slots work | +| `AppErrorState` | renders title/message, retry button fires event | +| `AppLoadingState` | renders label | +| `AppShell` | renders nav items, brand, content slot | +| `AreaTreeNode` | toggles expand, emits rename/remove/unassign, cycle detection | +| `DeviceConnectionBadge` | maps status → correct variant | +| `DeviceStateCell` | maps state → correct badge/loader | +| `AreaFavoritesPage` | loads areas, filters favorites, handles empty | +| `DevicesListPage` | loads devices + states, renders table rows, stale badge | +| `ScriptsActionsPage` | loads actions, run button triggers API, shows alert | + +Mock strategy: +- Pinia stores via `createTestingPinia()`. +- API calls via `vi.mock()` or MSW (mock service worker). + +Deliverable: + +- Component tests cover all feature components and pages. + +## Phase 5.7 - Integration Tests ✅ + +End-to-end user flows with mocked backend: + +| Flow | Steps | +|------|-------| +| Area lifecycle | Load areas → Create area → Rename → Remove | +| Device discovery | Scan network → Select device → Setup → Appears in list | +| Script execution | Load actions → Run script → See result alert | +| Navigation | Visit each route → Assert correct page renders | + +Use **MSW** (Mock Service Worker) to intercept `fetch` and return fixture data. + +Deliverable: + +- Integration tests verify complete user workflows. + ## Phase 6 - Switch-Over - Keep old `webclient/` available as fallback. diff --git a/webclient-vue/docs/smoke-checklist.md b/webclient-vue/docs/smoke-checklist.md new file mode 100644 index 0000000..98f26a1 --- /dev/null +++ b/webclient-vue/docs/smoke-checklist.md @@ -0,0 +1,90 @@ +# Smoke Checklist + +Run after every `gnexus-ui-kit` update or before release. + +## Navigation + +- [ ] App shell renders with brand and nav items +- [ ] Drawer opens/closes on mobile +- [ ] Active route highlighted correctly +- [ ] Navigation icons visible (Phosphor) + +## Areas + +- [ ] Favorites page loads areas list +- [ ] Star/unstar toggles favorite +- [ ] Tree page renders hierarchy +- [ ] Create area modal opens and submits +- [ ] Rename area modal works +- [ ] Remove area with confirm +- [ ] Unassign area works + +## Devices + +- [ ] Device list loads with live states +- [ ] Refresh button triggers reload +- [ ] Stale data badge appears after 5 min +- [ ] Reboot button shows loading state +- [ ] Scanning page discovers devices +- [ ] Setup new device modal submits + +## Scripts + +- [ ] Actions list renders cards with icons +- [ ] Run action shows loading and result alert +- [ ] Regular list toggles enable/disable +- [ ] Scopes list toggles enable/disable + +## Feedback + +- [ ] Loading states show spinners +- [ ] Empty states render correctly +- [ ] Error states show retry buttons +- [ ] Alert variants render (success, danger, warning) + +## Modals + +- [ ] Modal opens/closes +- [ ] Backdrop click closes modal +- [ ] Escape key closes modal +- [ ] Inputs in modals focus correctly + +## Tables + +- [ ] Table headers align +- [ ] Cell slots render custom content +- [ ] Empty table shows empty text + +## Cards + +- [ ] Card title renders +- [ ] Card footer slot works +- [ ] Card variants render correctly + +## Buttons + +- [ ] Primary, secondary, warning, danger variants +- [ ] Loading state disables button +- [ ] Icon renders alongside label + +## Badges + +- [ ] Primary, success, secondary, warning, danger variants +- [ ] Outline variant if used + +## Typography + +- [ ] IBM Plex Mono font loads (no OTS errors) +- [ ] Phosphor icons render (no squares) + +## Responsive + +- [ ] Layout works at 320px width +- [ ] Tables scroll horizontally on small screens +- [ ] Cards stack on mobile + +## Performance + +- [ ] Build completes without errors +- [ ] No console warnings about missing icons +- [ ] API requests cancel on route change diff --git a/webclient-vue/package-lock.json b/webclient-vue/package-lock.json index 54fcda6..8001ff8 100644 --- a/webclient-vue/package-lock.json +++ b/webclient-vue/package-lock.json @@ -8,40 +8,98 @@ "name": "shserv-vue-client", "version": "0.1.0", "dependencies": { + "@phosphor-icons/web": "^2.1.2", + "gnexus-ui-kit": "git+https://git.gnexus.space/root/gnexus-ui-kit.git", "pinia": "^2.3.1", "vue": "^3.5.13", "vue-router": "^4.5.0" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.4", - "vite": "^6.3.5" + "@vitest/coverage-v8": "^4.1.7", + "@vue/test-utils": "^2.4.10", + "jsdom": "^29.1.1", + "msw": "^2.14.6", + "vite": "^6.3.5", + "vitest": "^4.1.7" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -51,18 +109,181 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.1.tgz", + "integrity": "sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.4.tgz", + "integrity": "sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -505,12 +726,230 @@ "node": ">=18" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@inquirer/ansi": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.6.tgz", + "integrity": "sha512-I/INw4sHGlVZ/afZOckpLiDP9SmbMl1g/GCqeHjLw1Afw/0PlRs2tRFgTGWmdI0hoNuWZn3y2iHNmG1vyECyQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.1.0.tgz", + "integrity": "sha512-USpeB76eqK7yGricDlGAupxWlp4a59qpeZOoNWaxO/nJln7agpJveyNkQ1d5u8YXG6TOqxZtQpKPORQQDrdVsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.0", + "@inquirer/type": "^4.0.6" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.2.0.tgz", + "integrity": "sha512-joR1YS2sI0us+9d0I8ViqFbrRLONO8CFTuyvBX4ZVBSch+VsZiugUABdrhBXXJR1VyEzvpz5SQCix3keETQ58g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.6", + "@inquirer/figures": "^2.0.6", + "@inquirer/type": "^4.0.6", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^4.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.6.tgz", + "integrity": "sha512-dsZgQtH2t5Q6ah3aPbZbeEZAxsD9qQu0DXf01AltuEfRTm+NoLN6+rLVbr+4edeEbNCp/wBNM6mALRWtsQpfkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "node_modules/@inquirer/type": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.6.tgz", + "integrity": "sha512-J+9tdxOskuYuGjsvGaq00AamhDgjR7anhEW2dP4QdQpFCMPngCeC/bCYWQ5NsMWZRdsy53is7kAHb/+7cwDk2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mswjs/interceptors": { + "version": "0.41.9", + "resolved": "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.41.9.tgz", + "integrity": "sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@open-draft/deferred-promise": "^2.2.0", + "@open-draft/logger": "^0.3.0", + "@open-draft/until": "^2.0.0", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "strict-event-emitter": "^0.5.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@mswjs/interceptors/node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/deferred-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-3.0.0.tgz", + "integrity": "sha512-XW375UK8/9SqUVNVa6M0yEy8+iTi4QN5VZ7aZuRFQmy76LRwI9wy5F4YIBU6T+eTe2/DNDo8tqu8RHlwLHM6RA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@open-draft/logger": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@open-draft/logger/-/logger-0.3.0.tgz", + "integrity": "sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-node-process": "^1.2.0", + "outvariant": "^1.4.0" + } + }, + "node_modules/@open-draft/until": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@open-draft/until/-/until-2.1.0.tgz", + "integrity": "sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@phosphor-icons/web": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@phosphor-icons/web/-/web-2.1.2.tgz", + "integrity": "sha512-rPAR9o/bEcp4Cw4DEeZHXf+nlGCMNGkNDRizYHM47NLxz9vvEHp/Tt6FMK1NcWadzw/pFDPnRBGi/ofRya958A==", + "license": "MIT" + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.60.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", @@ -900,6 +1339,31 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -907,6 +1371,33 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/set-cookie-parser": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz", + "integrity": "sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/statuses": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.6.tgz", + "integrity": "sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", @@ -921,6 +1412,160 @@ "vue": "^3.2.25" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.7.tgz", + "integrity": "sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.7", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.7", + "vitest": "4.1.7" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", + "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", + "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", + "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", + "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.7", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", + "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "@vitest/utils": "4.1.7", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", + "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", + "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.7", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vue/compiler-core": { "version": "3.5.33", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.33.tgz", @@ -1027,12 +1672,387 @@ "integrity": "sha512-5vR2QIlmaLG77Ygd4pMP6+SGQ5yox9VhtnbDWTy9DzMzdmeLxZ1QqxrywEZ9sa1AVubfIJyaCG3ytyWU81ufcQ==", "license": "MIT" }, + "node_modules/@vue/test-utils": { + "version": "2.4.10", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.10.tgz", + "integrity": "sha512-SmoZ5EA1kYiAFs9NkYdiFFQF+cSnUwnvlYEbY+DogWQZUiqOm/Y29eSbc5T6yi75SgSF9863SBeXniIEoPajCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^1.14.9", + "vue-component-type-helpers": "^3.0.0" + }, + "peerDependencies": { + "@vue/compiler-dom": "3.x", + "@vue/server-renderer": "3.x", + "vue": "3.x" + }, + "peerDependenciesMeta": { + "@vue/server-renderer": { + "optional": true + } + } + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.2.tgz", + "integrity": "sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/editorconfig": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", + "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "^9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/entities": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", @@ -1045,6 +2065,13 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -1087,12 +2114,59 @@ "@esbuild/win32-x64": "0.25.12" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1111,6 +2185,23 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1126,6 +2217,285 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gnexus-ui-kit": { + "version": "0.4.0", + "resolved": "git+https://git.gnexus.space/root/gnexus-ui-kit.git#5227ba022e5da5ef7df5a4b4ed463c25abd1f85b", + "license": "UNLICENSED", + "peerDependencies": { + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "vue": { + "optional": true + } + } + }, + "node_modules/graphql": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.0.tgz", + "integrity": "sha512-BBvQ/406p+4CZbTpCbVPSxfzrZrbnuWSP1ELYgyS6B+hNeKzgrdB4JczCa5VZUBQrDa9hUngm0KnexY6pJRN5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/headers-polyfill": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-5.0.1.tgz", + "integrity": "sha512-1TJ6Fih/b8h5TIcv+1+Hw0PDQWJTKDKzFZzcKOiW1wJza3XoAQlkCuXLbymPYB8+ZQyw8mHvdw560e8zVFIWyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/set-cookie-parser": "^2.4.10", + "set-cookie-parser": "^3.0.1" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-node-process": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-node-process/-/is-node-process-1.2.0.tgz", + "integrity": "sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-beautify": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", + "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.4.2", + "js-cookie": "^3.0.5", + "nopt": "^7.2.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-cookie": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.7.tgz", + "integrity": "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1135,6 +2505,122 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/msw": { + "version": "2.14.6", + "resolved": "https://registry.npmjs.org/msw/-/msw-2.14.6.tgz", + "integrity": "sha512-ALe+N10S72cyx94cMcy3Zs4HhXCj35sgeAL4c+WTvKi0zWnbd8/h0lcFqv0mb2P+aSgAdD7p9HzvA0DiUPxsyg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@inquirer/confirm": "^6.0.11", + "@mswjs/interceptors": "^0.41.3", + "@open-draft/deferred-promise": "^3.0.0", + "@types/statuses": "^2.0.6", + "cookie": "^1.1.1", + "graphql": "^16.13.2", + "headers-polyfill": "^5.0.1", + "is-node-process": "^1.2.0", + "outvariant": "^1.4.3", + "path-to-regexp": "^6.3.0", + "picocolors": "^1.1.1", + "rettime": "^0.11.11", + "statuses": "^2.0.2", + "strict-event-emitter": "^0.5.1", + "tough-cookie": "^6.0.1", + "type-fest": "^5.5.0", + "until-async": "^3.0.2", + "yargs": "^17.7.2" + }, + "bin": { + "msw": "cli/index.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mswjs" + }, + "peerDependencies": { + "typescript": ">= 4.8.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/mute-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-4.0.0.tgz", + "integrity": "sha512-gSrprq0fJ3EiOErzjdIZrjysVVmJ4uu1QWfCDss5LypA5OXvrMje5Ym5z6V6RLyJ2eF87lasX7t6a0AnFvZblg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1153,6 +2639,121 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/outvariant": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.3.tgz", + "integrity": "sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==", + "dev": true, + "license": "MIT" + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1222,6 +2823,50 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rettime": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.11.11.tgz", + "integrity": "sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.60.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", @@ -1267,6 +2912,82 @@ "fsevents": "~2.3.2" } }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", + "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1276,6 +2997,191 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/strict-event-emitter": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", + "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tagged-tag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", + "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.2.tgz", + "integrity": "sha512-M/Q0B2cp4K7kynaT/vnED1j8TlLY+Pp7C6Wl2bl/7u/F0mUVwdyOpwomQb8JpYLitHUssAJRmLZdMCGsrx7i+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", @@ -1293,6 +3199,105 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.0.tgz", + "integrity": "sha512-yHBe+zVfzNZ3QfTPW/Z6KK1G2t340gFjMHqI/4KKSt/abzYydzuCnpqdaF5gCCABby+9Yfbj59oR5F2Fd5CBzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.0" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.0.tgz", + "integrity": "sha512-/mb9kRld+x1sIMXxWNOAp5m6C+D4GrAORWlJkOJ5dElvxdN1eutz/o7qHLp9gFvDF4Y3/L2xeScoxz6AbEo8rQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/type-fest": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.6.0.tgz", + "integrity": "sha512-8ZiHFm91orbSAe2PSAiSVBVko18pbhbiB3U9GglSzF/zCGkR+rxpHx6sEMCUm4kxY4LjDIUGgCfUMtwfZfjfUA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.26.0.tgz", + "integrity": "sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, + "node_modules/until-async": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/until-async/-/until-async-3.0.2.tgz", + "integrity": "sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/kettanaito" + } + }, "node_modules/vite": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", @@ -1368,6 +3373,96 @@ } } }, + "node_modules/vitest": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", + "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.7", + "@vitest/mocker": "4.1.7", + "@vitest/pretty-format": "4.1.7", + "@vitest/runner": "4.1.7", + "@vitest/snapshot": "4.1.7", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.7", + "@vitest/browser-preview": "4.1.7", + "@vitest/browser-webdriverio": "4.1.7", + "@vitest/coverage-istanbul": "4.1.7", + "@vitest/coverage-v8": "4.1.7", + "@vitest/ui": "4.1.7", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, "node_modules/vue": { "version": "3.5.33", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.33.tgz", @@ -1389,6 +3484,13 @@ } } }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.2.tgz", + "integrity": "sha512-l4Z2Y34m7nFMlx8vrslJaVtXxUpzgDMSESC7TakG/c5kwjYT/do+E0NcT2/vWDzaoIhsShg/2OKwX7Q4nbzC0g==", + "dev": true, + "license": "MIT" + }, "node_modules/vue-demi": { "version": "0.14.10", "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", @@ -1429,6 +3531,286 @@ "peerDependencies": { "vue": "^3.5.0" } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } } } } diff --git a/webclient-vue/package.json b/webclient-vue/package.json index 4ced1f3..dd17b1f 100644 --- a/webclient-vue/package.json +++ b/webclient-vue/package.json @@ -6,15 +6,25 @@ "scripts": { "dev": "vite --host 0.0.0.0", "build": "vite build", - "preview": "vite preview --host 0.0.0.0" + "preview": "vite preview --host 0.0.0.0", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" }, "dependencies": { + "@phosphor-icons/web": "^2.1.2", + "gnexus-ui-kit": "git+https://git.gnexus.space/root/gnexus-ui-kit.git", "pinia": "^2.3.1", "vue": "^3.5.13", "vue-router": "^4.5.0" }, "devDependencies": { "@vitejs/plugin-vue": "^5.2.4", - "vite": "^6.3.5" + "@vitest/coverage-v8": "^4.1.7", + "@vue/test-utils": "^2.4.10", + "jsdom": "^29.1.1", + "msw": "^2.14.6", + "vite": "^6.3.5", + "vitest": "^4.1.7" } } diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Bold.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Bold.ttf new file mode 100644 index 0000000..247979c --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Bold.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf new file mode 100644 index 0000000..2321473 --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-BoldItalic.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf new file mode 100644 index 0000000..d6ab75d --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLight.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf new file mode 100644 index 0000000..88308ef --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ExtraLightItalic.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Italic.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Italic.ttf new file mode 100644 index 0000000..e259e84 --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Italic.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Light.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Light.ttf new file mode 100644 index 0000000..0dcb2fb --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Light.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf new file mode 100644 index 0000000..f4a5fea --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-LightItalic.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Medium.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Medium.ttf new file mode 100644 index 0000000..8253c5f --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Medium.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf new file mode 100644 index 0000000..528b13b --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-MediumItalic.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Regular.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Regular.ttf new file mode 100644 index 0000000..601ae94 --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Regular.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf new file mode 100644 index 0000000..5e0b41d --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBold.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf new file mode 100644 index 0000000..58243dd --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-SemiBoldItalic.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Thin.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Thin.ttf new file mode 100644 index 0000000..e069a64 --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-Thin.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf new file mode 100644 index 0000000..f3ed26b --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/IBMPlexMono-ThinItalic.ttf Binary files differ diff --git a/webclient-vue/public/assets/fonts/IBM_Plex_Mono/OFL.txt b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/OFL.txt new file mode 100644 index 0000000..5bb330e --- /dev/null +++ b/webclient-vue/public/assets/fonts/IBM_Plex_Mono/OFL.txt @@ -0,0 +1,93 @@ +Copyright © 2017 IBM Corp. with Reserved Font Name "Plex" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/webclient-vue/src/api/__tests__/client.spec.js b/webclient-vue/src/api/__tests__/client.spec.js new file mode 100644 index 0000000..6d4132a --- /dev/null +++ b/webclient-vue/src/api/__tests__/client.spec.js @@ -0,0 +1,102 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { apiRequest, apiGet, apiPost } from "../client.js"; + +vi.mock("../http.js", () => ({ + requestHttp: vi.fn(), +})); + +import { requestHttp } from "../http.js"; + +describe("apiRequest", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("returns ok=true on HTTP success with data.status=true", async () => { + requestHttp.mockResolvedValue({ + response: { ok: true, status: 200 }, + data: { status: true, data: { items: [] } }, + meta: { url: "/test", method: "GET" }, + }); + + const result = await apiRequest("GET", "/test"); + + expect(result.ok).toBe(true); + expect(result.data.status).toBe(true); + expect(result.meta.url).toBe("/test"); + }); + + it("returns ok=false on HTTP error", async () => { + requestHttp.mockResolvedValue({ + response: { ok: false, status: 500 }, + data: { message: "Server error" }, + meta: { url: "/test", method: "GET" }, + }); + + const result = await apiRequest("GET", "/test"); + + expect(result.ok).toBe(false); + expect(result.error.type).toBe("http_error"); + expect(result.error.statusCode).toBe(500); + }); + + it("returns ok=false on API error (data.status=false)", async () => { + requestHttp.mockResolvedValue({ + response: { ok: true, status: 200 }, + data: { status: false, msg: "Invalid field", error_alias: "bad_input" }, + meta: { url: "/test", method: "POST" }, + }); + + const result = await apiRequest("POST", "/test", { name: "x" }); + + expect(result.ok).toBe(false); + expect(result.error.type).toBe("api_error"); + expect(result.error.errorAlias).toBe("bad_input"); + }); + + it("returns timeout on AbortError", async () => { + requestHttp.mockRejectedValue(new DOMException("Aborted", "AbortError")); + + const result = await apiRequest("GET", "/test"); + + expect(result.ok).toBe(false); + expect(result.error.type).toBe("timeout"); + }); + + it("returns network_error on generic throw", async () => { + requestHttp.mockRejectedValue(new Error("Connection refused")); + + const result = await apiRequest("GET", "/test"); + + expect(result.ok).toBe(false); + expect(result.error.type).toBe("network_error"); + }); +}); + +describe("apiGet", () => { + it("calls apiRequest with GET method", async () => { + requestHttp.mockResolvedValue({ + response: { ok: true, status: 200 }, + data: { status: true }, + meta: {}, + }); + + await apiGet("/areas"); + + expect(requestHttp).toHaveBeenCalledWith("GET", "/areas", null, undefined); + }); +}); + +describe("apiPost", () => { + it("calls apiRequest with POST method and body", async () => { + requestHttp.mockResolvedValue({ + response: { ok: true, status: 200 }, + data: { status: true }, + meta: {}, + }); + + await apiPost("/areas", { name: "Kitchen" }); + + expect(requestHttp).toHaveBeenCalledWith("POST", "/areas", { name: "Kitchen" }, undefined); + }); +}); diff --git a/webclient-vue/src/api/__tests__/http.spec.js b/webclient-vue/src/api/__tests__/http.spec.js new file mode 100644 index 0000000..c55e359 --- /dev/null +++ b/webclient-vue/src/api/__tests__/http.spec.js @@ -0,0 +1,152 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { requestHttp } from "../http.js"; + +describe("requestHttp", () => { + beforeEach(() => { + vi.stubEnv("VITE_API_BASE_URL", ""); + vi.stubEnv("VITE_API_PROXY_PATH", "/proxy.php"); + vi.stubEnv("VITE_API_TIMEOUT_MS", "10000"); + global.fetch = vi.fn(); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + vi.restoreAllMocks(); + }); + + it("builds proxy URL when VITE_API_PROXY_PATH is set", async () => { + fetch.mockResolvedValue({ + status: 200, + text: async () => '{"status":true}', + headers: new Headers(), + }); + + await requestHttp("GET", "/api/v1/areas/list"); + + expect(fetch).toHaveBeenCalledOnce(); + const url = fetch.mock.calls[0][0]; + expect(url).toBe("/proxy.php?path=%2Fapi%2Fv1%2Fareas%2Flist"); + }); + + it("appends query params to proxy URL", async () => { + fetch.mockResolvedValue({ + status: 200, + text: async () => '{"status":true}', + headers: new Headers(), + }); + + await requestHttp("GET", "/api/v1/areas/list", null, { query: { page: 2 } }); + + const url = fetch.mock.calls[0][0]; + expect(url).toContain("path=%2Fapi%2Fv1%2Fareas%2Flist"); + expect(url).toContain("page=2"); + }); + + it("uses direct URL when proxy path is empty", async () => { + vi.stubEnv("VITE_API_PROXY_PATH", ""); + vi.stubEnv("VITE_API_BASE_URL", "http://server.local"); + + fetch.mockResolvedValue({ + status: 200, + text: async () => '{"status":true}', + headers: new Headers(), + }); + + await requestHttp("GET", "/api/v1/areas/list"); + + const url = fetch.mock.calls[0][0]; + expect(url).toBe("http://server.local/api/v1/areas/list"); + }); + + it("sends JSON body with Content-Type header", async () => { + fetch.mockResolvedValue({ + status: 200, + text: async () => '{"status":true}', + headers: new Headers(), + }); + + await requestHttp("POST", "/api/v1/areas/new-area", { name: "Kitchen" }); + + const init = fetch.mock.calls[0][1]; + expect(init.method).toBe("POST"); + expect(init.headers["Content-Type"]).toBe("application/json"); + expect(init.body).toBe('{"name":"Kitchen"}'); + }); + + it("includes Accept header", async () => { + fetch.mockResolvedValue({ + status: 200, + text: async () => '{"status":true}', + headers: new Headers(), + }); + + await requestHttp("GET", "/test"); + + const init = fetch.mock.calls[0][1]; + expect(init.headers.Accept).toBe("application/json"); + }); + + it("passes custom headers", async () => { + fetch.mockResolvedValue({ + status: 200, + text: async () => '{"status":true}', + headers: new Headers(), + }); + + await requestHttp("GET", "/test", null, { headers: { "X-Custom": "value" } }); + + const init = fetch.mock.calls[0][1]; + expect(init.headers["X-Custom"]).toBe("value"); + }); + + it("passes AbortController signal to fetch", async () => { + fetch.mockResolvedValue({ + status: 200, + text: async () => '{"status":true}', + headers: new Headers(), + }); + + await requestHttp("GET", "/test"); + + const init = fetch.mock.calls[0][1]; + expect(init.signal).toBeInstanceOf(AbortSignal); + }); + + it("parses JSON response", async () => { + fetch.mockResolvedValue({ + status: 200, + text: async () => '{"status":true,"data":{"items":[]}}', + headers: new Headers(), + }); + + const result = await requestHttp("GET", "/test"); + + expect(result.data).toEqual({ status: true, data: { items: [] } }); + }); + + it("returns raw text for non-JSON response", async () => { + fetch.mockResolvedValue({ + status: 200, + text: async () => "plain text", + headers: new Headers(), + }); + + const result = await requestHttp("GET", "/test"); + + expect(result.data).toBe("plain text"); + }); + + it("returns meta with url, method, statusCode", async () => { + fetch.mockResolvedValue({ + status: 200, + text: async () => "{}", + headers: new Headers(), + }); + + const result = await requestHttp("POST", "/api/v1/test", { a: 1 }); + + expect(result.meta.method).toBe("POST"); + expect(result.meta.statusCode).toBe(200); + expect(result.meta.url).toContain("path=%2Fapi%2Fv1%2Ftest"); + }); +}); diff --git a/webclient-vue/src/api/__tests__/mappers.spec.js b/webclient-vue/src/api/__tests__/mappers.spec.js new file mode 100644 index 0000000..4ad24cb --- /dev/null +++ b/webclient-vue/src/api/__tests__/mappers.spec.js @@ -0,0 +1,64 @@ +import { describe, it, expect } from "vitest"; +import { unifyDeviceFields } from "../mappers.js"; + +describe("unifyDeviceFields", () => { + it("maps snake_case fields to camelCase", () => { + const device = { + device_name: "Relay 1", + device_hard_id: "abc123", + device_ip: "192.168.1.10", + device_type: "relay", + ip_address: "192.168.1.10", + mac_address: "A4:CF:12:9B:3F:D2", + device_mac: "A4:CF:12:9B:3F:D2", + core_version: "1.0", + }; + + const result = unifyDeviceFields(device); + + expect(result.name).toBe("Relay 1"); + expect(result.device_id).toBe("abc123"); + expect(result.ip).toBe("192.168.1.10"); + expect(result.type).toBe("relay"); + expect(result.mac).toBe("A4:CF:12:9B:3F:D2"); + expect(result.firmware_core_version).toBe("1.0"); + }); + + it("passes through unknown fields unchanged", () => { + const device = { + id: 42, + unknown_field: "value", + }; + + const result = unifyDeviceFields(device); + + expect(result.id).toBe(42); + expect(result.unknown_field).toBe("value"); + }); + + it("returns empty object for null input", () => { + const result = unifyDeviceFields(null); + + expect(result).toEqual({}); + }); + + it("returns empty object for undefined input", () => { + const result = unifyDeviceFields(undefined); + + expect(result).toEqual({}); + }); + + it("handles mixed known and unknown fields", () => { + const device = { + device_name: "Button 1", + alias: "kitchen_btns", + extra: { a: 1 }, + }; + + const result = unifyDeviceFields(device); + + expect(result.name).toBe("Button 1"); + expect(result.alias).toBe("kitchen_btns"); + expect(result.extra).toEqual({ a: 1 }); + }); +}); diff --git a/webclient-vue/src/api/__tests__/modules.spec.js b/webclient-vue/src/api/__tests__/modules.spec.js new file mode 100644 index 0000000..15e60c6 --- /dev/null +++ b/webclient-vue/src/api/__tests__/modules.spec.js @@ -0,0 +1,159 @@ +import { describe, it, expect, vi } from "vitest"; +import { devicesApi } from "../modules/devices.js"; +import { areasApi } from "../modules/areas.js"; +import { scriptsApi } from "../modules/scripts.js"; + +vi.mock("../client.js", () => ({ + apiGet: vi.fn((path, options) => Promise.resolve({ ok: true, path, options })), + apiPost: vi.fn((path, payload) => Promise.resolve({ ok: true, path, payload })), +})); + +describe("devicesApi", () => { + it("list calls apiGet", async () => { + const { apiGet } = await import("../client.js"); + await devicesApi.list(); + expect(apiGet).toHaveBeenCalledWith("/api/v1/devices/list"); + }); + + it("status encodes id", async () => { + const { apiGet } = await import("../client.js"); + await devicesApi.status(42); + expect(apiGet).toHaveBeenCalledWith("/api/v1/devices/id/42/status", undefined); + }); + + it("reboot encodes id", async () => { + const { apiGet } = await import("../client.js"); + await devicesApi.reboot(7); + expect(apiGet).toHaveBeenCalledWith("/api/v1/devices/id/7/reboot"); + }); + + it("action posts payload", async () => { + const { apiPost } = await import("../client.js"); + await devicesApi.action({ device_id: 1, action: "toggle" }); + expect(apiPost).toHaveBeenCalledWith("/api/v1/devices/action", { device_id: 1, action: "toggle" }); + }); + + it("scanningSetup passes options", async () => { + const { apiGet } = await import("../client.js"); + await devicesApi.scanningSetup({ signal: new AbortController().signal }); + expect(apiGet).toHaveBeenCalledWith("/api/v1/devices/scanning/setup", expect.anything()); + }); + + it("scanningAll passes options", async () => { + const { apiGet } = await import("../client.js"); + await devicesApi.scanningAll({ signal: new AbortController().signal }); + expect(apiGet).toHaveBeenCalledWith("/api/v1/devices/scanning/all", expect.anything()); + }); + + it("setupNewDevice posts payload", async () => { + const { apiPost } = await import("../client.js"); + await devicesApi.setupNewDevice({ alias: "test" }); + expect(apiPost).toHaveBeenCalledWith("/api/v1/devices/setup/new-device", { alias: "test" }); + }); +}); + +describe("areasApi", () => { + it("list passes options", async () => { + const { apiGet } = await import("../client.js"); + await areasApi.list({ signal: new AbortController().signal }); + expect(apiGet).toHaveBeenCalledWith("/api/v1/areas/list", expect.anything()); + }); + + it("innerList encodes areaId", async () => { + const { apiGet } = await import("../client.js"); + await areasApi.innerList(5); + expect(apiGet).toHaveBeenCalledWith("/api/v1/areas/id/5/list", undefined); + }); + + it("devices encodes areaId", async () => { + const { apiGet } = await import("../client.js"); + await areasApi.devices(3); + expect(apiGet).toHaveBeenCalledWith("/api/v1/areas/id/3/devices"); + }); + + it("scripts encodes areaId", async () => { + const { apiGet } = await import("../client.js"); + await areasApi.scripts(3); + expect(apiGet).toHaveBeenCalledWith("/api/v1/areas/id/3/scripts"); + }); + + it("newArea posts payload", async () => { + const { apiPost } = await import("../client.js"); + await areasApi.newArea({ type: "room", alias: "test" }); + expect(apiPost).toHaveBeenCalledWith("/api/v1/areas/new-area", { type: "room", alias: "test" }); + }); + + it("updateDisplayName posts payload", async () => { + const { apiPost } = await import("../client.js"); + await areasApi.updateDisplayName({ area_id: 1, display_name: "New" }); + expect(apiPost).toHaveBeenCalledWith("/api/v1/areas/update-display-name", { area_id: 1, display_name: "New" }); + }); + + it("updateAlias posts payload", async () => { + const { apiPost } = await import("../client.js"); + await areasApi.updateAlias({ area_id: 1, new_alias: "new_alias" }); + expect(apiPost).toHaveBeenCalledWith("/api/v1/areas/update-alias", { area_id: 1, new_alias: "new_alias" }); + }); + + it("placeInArea posts payload", async () => { + const { apiPost } = await import("../client.js"); + await areasApi.placeInArea({ target_id: 1, place_in_area_id: 2 }); + expect(apiPost).toHaveBeenCalledWith("/api/v1/areas/place-in-area", { target_id: 1, place_in_area_id: 2 }); + }); + + it("remove encodes areaId", async () => { + const { apiGet } = await import("../client.js"); + await areasApi.remove(10); + expect(apiGet).toHaveBeenCalledWith("/api/v1/areas/id/10/remove"); + }); + + it("unassign encodes areaId", async () => { + const { apiGet } = await import("../client.js"); + await areasApi.unassign(10); + expect(apiGet).toHaveBeenCalledWith("/api/v1/areas/id/10/unassign-from-area"); + }); +}); + +describe("scriptsApi", () => { + it("actionsList passes options", async () => { + const { apiGet } = await import("../client.js"); + await scriptsApi.actionsList({ signal: new AbortController().signal }); + expect(apiGet).toHaveBeenCalledWith("/api/v1/scripts/actions/list", expect.anything()); + }); + + it("regularList passes options", async () => { + const { apiGet } = await import("../client.js"); + await scriptsApi.regularList({ signal: new AbortController().signal }); + expect(apiGet).toHaveBeenCalledWith("/api/v1/scripts/regular/list", expect.anything()); + }); + + it("scopesList passes options", async () => { + const { apiGet } = await import("../client.js"); + await scriptsApi.scopesList({ signal: new AbortController().signal }); + expect(apiGet).toHaveBeenCalledWith("/api/v1/scripts/scopes/list", expect.anything()); + }); + + it("runAction posts alias and params", async () => { + const { apiPost } = await import("../client.js"); + await scriptsApi.runAction("test", { a: 1 }); + expect(apiPost).toHaveBeenCalledWith("/api/v1/scripts/actions/run", { alias: "test", params: { a: 1 } }); + }); + + it("setRegularState encodes alias", async () => { + const { apiGet } = await import("../client.js"); + await scriptsApi.setRegularState("my_script", true); + expect(apiGet).toHaveBeenCalledWith("/api/v1/scripts/regular/alias/my_script/enable"); + }); + + it("setRegularState encodes alias for disable", async () => { + const { apiGet } = await import("../client.js"); + await scriptsApi.setRegularState("my_script", false); + expect(apiGet).toHaveBeenCalledWith("/api/v1/scripts/regular/alias/my_script/disable"); + }); + + it("setScopeState encodes name", async () => { + const { apiGet } = await import("../client.js"); + await scriptsApi.setScopeState("MyScope", true); + expect(apiGet).toHaveBeenCalledWith("/api/v1/scripts/actions/scope/MyScope/enable"); + }); +}); diff --git a/webclient-vue/src/api/modules/areas.js b/webclient-vue/src/api/modules/areas.js index 62f602d..d6b717f 100644 --- a/webclient-vue/src/api/modules/areas.js +++ b/webclient-vue/src/api/modules/areas.js @@ -1,12 +1,12 @@ import { apiGet, apiPost } from "../client"; export const areasApi = { - list() { - return apiGet("/api/v1/areas/list"); + list(options) { + return apiGet("/api/v1/areas/list", options); }, - innerList(areaId) { - return apiGet(`/api/v1/areas/id/${encodeURIComponent(String(areaId))}/list`); + innerList(areaId, options) { + return apiGet(`/api/v1/areas/id/${encodeURIComponent(String(areaId))}/list`, options); }, newArea(payload) { @@ -24,4 +24,20 @@ scripts(areaId) { return apiGet(`/api/v1/areas/id/${encodeURIComponent(String(areaId))}/scripts`); }, + + updateDisplayName(payload) { + return apiPost("/api/v1/areas/update-display-name", payload); + }, + + updateAlias(payload) { + return apiPost("/api/v1/areas/update-alias", payload); + }, + + placeInArea(payload) { + return apiPost("/api/v1/areas/place-in-area", payload); + }, + + unassign(areaId) { + return apiGet(`/api/v1/areas/id/${encodeURIComponent(String(areaId))}/unassign-from-area`); + }, }; diff --git a/webclient-vue/src/api/modules/devices.js b/webclient-vue/src/api/modules/devices.js index 38d0927..3f858a7 100644 --- a/webclient-vue/src/api/modules/devices.js +++ b/webclient-vue/src/api/modules/devices.js @@ -40,4 +40,16 @@ action(payload) { return apiPost("/api/v1/devices/action", payload); }, + + scanningSetup(options) { + return apiGet("/api/v1/devices/scanning/setup", options); + }, + + scanningAll(options) { + return apiGet("/api/v1/devices/scanning/all", options); + }, + + setupNewDevice(payload) { + return apiPost("/api/v1/devices/setup/new-device", payload); + }, }; diff --git a/webclient-vue/src/api/modules/scripts.js b/webclient-vue/src/api/modules/scripts.js new file mode 100644 index 0000000..c18eabb --- /dev/null +++ b/webclient-vue/src/api/modules/scripts.js @@ -0,0 +1,31 @@ +import { apiGet, apiPost } from "../client"; + +export const scriptsApi = { + actionsList(options) { + return apiGet("/api/v1/scripts/actions/list", options); + }, + + regularList(options) { + return apiGet("/api/v1/scripts/regular/list", options); + }, + + scopesList(options) { + return apiGet("/api/v1/scripts/scopes/list", options); + }, + + runAction(alias, params = {}) { + return apiPost("/api/v1/scripts/actions/run", { alias, params }); + }, + + setActionState(alias, enabled) { + return apiGet(`/api/v1/scripts/actions/alias/${encodeURIComponent(alias)}/${enabled ? "enable" : "disable"}`); + }, + + setRegularState(alias, enabled) { + return apiGet(`/api/v1/scripts/regular/alias/${encodeURIComponent(alias)}/${enabled ? "enable" : "disable"}`); + }, + + setScopeState(name, enabled) { + return apiGet(`/api/v1/scripts/actions/scope/${encodeURIComponent(name)}/${enabled ? "enable" : "disable"}`); + }, +}; diff --git a/webclient-vue/src/app/main.js b/webclient-vue/src/app/main.js index d1161ba..379d764 100644 --- a/webclient-vue/src/app/main.js +++ b/webclient-vue/src/app/main.js @@ -2,6 +2,7 @@ import { createPinia } from "pinia"; import App from "./App.vue"; import { router } from "../router"; +import "@phosphor-icons/web/regular"; import "../styles/main.css"; createApp(App) diff --git a/webclient-vue/src/components/feedback/AppEmptyState.vue b/webclient-vue/src/components/feedback/AppEmptyState.vue index 05bc1be..e1b34a6 100644 --- a/webclient-vue/src/components/feedback/AppEmptyState.vue +++ b/webclient-vue/src/components/feedback/AppEmptyState.vue @@ -1,11 +1,10 @@ diff --git a/webclient-vue/src/components/layout/__tests__/AppShell.spec.js b/webclient-vue/src/components/layout/__tests__/AppShell.spec.js new file mode 100644 index 0000000..c3ee40f --- /dev/null +++ b/webclient-vue/src/components/layout/__tests__/AppShell.spec.js @@ -0,0 +1,35 @@ +import { describe, it, expect } from "vitest"; +import { mount } from "@vue/test-utils"; +import AppShell from "../AppShell.vue"; + +describe("AppShell", () => { + it("renders brand name", () => { + const wrapper = mount(AppShell, { + slots: { default: "Content" }, + }); + + expect(wrapper.text()).toContain("SHSERV WEB CLIENT"); + }); + + it("renders slot content", () => { + const wrapper = mount(AppShell, { + slots: { default: "

Page Content

" }, + }); + + expect(wrapper.text()).toContain("Page Content"); + }); + + it("renders navigation items with icons", () => { + const wrapper = mount(AppShell, { + slots: { default: "" }, + }); + + expect(wrapper.text()).toContain("Favorites"); + expect(wrapper.text()).toContain("Areas"); + expect(wrapper.text()).toContain("Devices"); + expect(wrapper.text()).toContain("Scanning"); + expect(wrapper.text()).toContain("Actions"); + expect(wrapper.text()).toContain("Regular"); + expect(wrapper.text()).toContain("Scopes"); + }); +}); diff --git a/webclient-vue/src/components/ui/UiBadge.vue b/webclient-vue/src/components/ui/UiBadge.vue deleted file mode 100644 index c79f27c..0000000 --- a/webclient-vue/src/components/ui/UiBadge.vue +++ /dev/null @@ -1,14 +0,0 @@ - - - diff --git a/webclient-vue/src/components/ui/UiButton.vue b/webclient-vue/src/components/ui/UiButton.vue deleted file mode 100644 index b6e17eb..0000000 --- a/webclient-vue/src/components/ui/UiButton.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/webclient-vue/src/features/areas/components/AreaTreeNode.vue b/webclient-vue/src/features/areas/components/AreaTreeNode.vue index 0af5f1a..618311e 100644 --- a/webclient-vue/src/features/areas/components/AreaTreeNode.vue +++ b/webclient-vue/src/features/areas/components/AreaTreeNode.vue @@ -1,12 +1,12 @@