@mxtommy/kip 3.10.0-beta.4 → 3.10.0-beta.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/plugin/index.js +234 -26
- package/plugin/openApi.json +141 -0
- package/public/3rdpartylicenses.txt +52 -52
- package/public/assets/help-docs/welcome.md +2 -2
- package/public/assets/svg/icons.svg +5 -1
- package/public/chunk-27I36ZYA.js +2 -0
- package/public/chunk-2YGPLTEW.js +6 -0
- package/public/chunk-4MSEQKGR.js +2 -0
- package/public/chunk-5UBH6OWU.js +5 -0
- package/public/{chunk-Y2IB2JDT.js → chunk-CEAJKNTR.js} +10 -10
- package/public/chunk-DPDOUEUH.js +9 -0
- package/public/chunk-HO743WL4.js +3 -0
- package/public/chunk-I3H3XQ5E.js +1 -0
- package/public/{chunk-LRX3XYXK.js → chunk-I772OX6I.js} +1 -1
- package/public/chunk-IUCJGG6N.js +1 -0
- package/public/chunk-JNQX4WPI.js +1 -0
- package/public/chunk-JNXVWBL2.js +1 -0
- package/public/chunk-KNN6A4MI.js +2 -0
- package/public/chunk-LKF7CNOE.js +2 -0
- package/public/chunk-LTNAKI5X.js +1 -0
- package/public/chunk-MMJBKCH5.js +1 -0
- package/public/{chunk-GJ33QBJ6.js → chunk-MZURT7UO.js} +1 -1
- package/public/chunk-PB7HT3XU.js +15 -0
- package/public/chunk-PR5DAT3I.js +2 -0
- package/public/{chunk-J3WNXGAQ.js → chunk-QG3TFUUS.js} +1 -1
- package/public/{chunk-TDHAZ7DS.js → chunk-QXMATZB6.js} +1 -1
- package/public/chunk-SPUZNRYQ.js +5 -0
- package/public/chunk-TFULQA7J.js +3 -0
- package/public/{chunk-7OMETTVK.js → chunk-USU5F3L5.js} +1 -1
- package/public/index.html +1 -1
- package/public/{main-SVHIKBQ5.js → main-MAUOXZ64.js} +12 -14
- package/COPILOT.md +0 -362
- package/eslint.config.js +0 -45
- package/kip-plugin/src/index.ts +0 -49
- package/kip-plugin/tsconfig.plugin.json +0 -13
- package/public/chunk-BZF6OYAF.js +0 -7
- package/public/chunk-FW2LAMAA.js +0 -16
- package/public/chunk-GBHBJN24.js +0 -1
- package/public/chunk-GDOUBCED.js +0 -1
- package/public/chunk-HKUJILH7.js +0 -6
- package/public/chunk-JFDPDIG2.js +0 -2
- package/public/chunk-KJQ6X3BT.js +0 -2
- package/public/chunk-MXKB5Z6M.js +0 -5
- package/public/chunk-NL52VRFS.js +0 -1
- package/public/chunk-PTADMSJZ.js +0 -1
- package/public/chunk-T5GXSVMN.js +0 -1
- package/public/chunk-VSGAZDLP.js +0 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mxtommy/kip",
|
|
3
|
-
"version": "3.10.0-beta.
|
|
3
|
+
"version": "3.10.0-beta.40",
|
|
4
4
|
"description": "An advanced and versatile marine instrumentation package to display Signal K data.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
"keywords": [
|
|
26
26
|
"signalk-webapp",
|
|
27
27
|
"signalk-category-instruments",
|
|
28
|
+
"signalk-category-notifications",
|
|
28
29
|
"signalk-node-server-plugin",
|
|
29
30
|
"signal k",
|
|
30
31
|
"signalk",
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
"build:plugin": "tsc -p ./kip-plugin/tsconfig.plugin.json",
|
|
47
48
|
"build:dev": "ng build --configuration=dev",
|
|
48
49
|
"build:prod": "ng build --configuration=production",
|
|
49
|
-
"build:all": "npm run build:
|
|
50
|
+
"build:all": "npm run build:plugin && npm run build:prod",
|
|
50
51
|
"e2e": "ng e2e"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
package/plugin/index.js
CHANGED
|
@@ -1,47 +1,255 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
36
|
+
const server_api_1 = require("@signalk/server-api");
|
|
37
|
+
const openapi = __importStar(require("./openApi.json"));
|
|
38
|
+
exports.default = (server) => {
|
|
39
|
+
const API_PATHS = {
|
|
40
|
+
DISPLAYS: `/displays`,
|
|
41
|
+
INSTANCE: `/displays/:displayId`,
|
|
42
|
+
ACTIVE_SCREEN: `/displays/:displayId/activeScreen`
|
|
43
|
+
};
|
|
44
|
+
// Helpers
|
|
45
|
+
function getDisplaySelfPath(displayId, suffix) {
|
|
46
|
+
const tail = suffix ? `.${suffix}` : '';
|
|
47
|
+
const want = `displays.${displayId}${tail}`;
|
|
48
|
+
const full = server.getSelfPath(want);
|
|
49
|
+
server.debug(`getDisplaySelfPath: displayId: ${displayId}, suffix: ${suffix}, want=${want}, fullPath=${full}`);
|
|
50
|
+
return full;
|
|
51
|
+
}
|
|
52
|
+
function getAvailableDisplays() {
|
|
53
|
+
const fullPath = server.getSelfPath('displays');
|
|
54
|
+
server.debug(`getAvailableDisplays: fullPath=${JSON.stringify(fullPath)}`);
|
|
55
|
+
return server.getPath('vessels.self.displays');
|
|
56
|
+
}
|
|
57
|
+
function pathToDotNotation(path) {
|
|
58
|
+
const dottedPath = path.replace(/\//g, '.').replace(/^\./, '');
|
|
59
|
+
server.debug(`pathToDotNotation: input path=${path}, dottedPath=${dottedPath}`);
|
|
60
|
+
return dottedPath;
|
|
61
|
+
}
|
|
62
|
+
function sendOk(res, body) {
|
|
63
|
+
if (body === undefined)
|
|
64
|
+
return res.status(204).end();
|
|
65
|
+
return res.status(200).json(body);
|
|
66
|
+
}
|
|
67
|
+
function sendFail(res, statusCode, message) {
|
|
68
|
+
return res.status(statusCode).json({ state: 'FAILED', statusCode, message });
|
|
69
|
+
}
|
|
4
70
|
const plugin = {
|
|
5
71
|
id: 'kip',
|
|
6
72
|
name: 'KIP',
|
|
7
73
|
description: 'KIP server plugin',
|
|
8
74
|
start: (settings) => {
|
|
9
|
-
|
|
10
|
-
|
|
75
|
+
server.debug(`Starting plugin with settings: ${JSON.stringify(settings)}`);
|
|
76
|
+
server.setPluginStatus(`Starting...`);
|
|
11
77
|
},
|
|
12
78
|
stop: () => {
|
|
13
|
-
|
|
79
|
+
server.debug(`Stopping plugin`);
|
|
80
|
+
const msg = 'Stopped.';
|
|
81
|
+
server.setPluginStatus(msg);
|
|
14
82
|
},
|
|
15
83
|
schema: () => {
|
|
16
84
|
return {
|
|
17
85
|
type: "object",
|
|
18
86
|
properties: {}
|
|
19
87
|
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
88
|
+
},
|
|
89
|
+
registerWithRouter(router) {
|
|
90
|
+
server.debug(`Registering plugin routes: ${API_PATHS.DISPLAYS}, ${API_PATHS.INSTANCE}, ${API_PATHS.ACTIVE_SCREEN}`);
|
|
91
|
+
// Validate/normalize :displayId where present
|
|
92
|
+
router.param('displayId', (req, res, next, displayId) => {
|
|
93
|
+
if (displayId == null)
|
|
94
|
+
return sendFail(res, 400, 'Missing displayId parameter');
|
|
95
|
+
try {
|
|
96
|
+
let id = String(displayId);
|
|
97
|
+
// Decode percent-encoding if present
|
|
98
|
+
try {
|
|
99
|
+
id = decodeURIComponent(id);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// ignore decode errors, keep original id
|
|
103
|
+
}
|
|
104
|
+
// If someone sent JSON as the path segment, try to recover {"displayId":"..."}
|
|
105
|
+
if (id.trim().startsWith('{')) {
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(id);
|
|
108
|
+
if (parsed && typeof parsed.displayId === 'string') {
|
|
109
|
+
id = parsed.displayId;
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
return sendFail(res, 400, 'Invalid displayId format in JSON');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return sendFail(res, 400, 'Invalid displayId JSON');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Basic safety: allow UUID-like strings (alphanum + dash)
|
|
120
|
+
if (!/^[A-Za-z0-9-]+$/.test(id)) {
|
|
121
|
+
return sendFail(res, 400, 'Invalid displayId format');
|
|
122
|
+
}
|
|
123
|
+
req.displayId = id;
|
|
124
|
+
next();
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return sendFail(res, 400, 'Missing or invalid displayId parameter');
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
router.put(`${API_PATHS.INSTANCE}`, async (req, res) => {
|
|
131
|
+
server.debug(`** PUT ${API_PATHS.INSTANCE}. Params: ${JSON.stringify(req.params)} Body: ${JSON.stringify(req.body)}`);
|
|
132
|
+
try {
|
|
133
|
+
const dottedPath = pathToDotNotation(req.path);
|
|
134
|
+
server.debug(`Updating SK path ${dottedPath}`);
|
|
135
|
+
server.handleMessage(plugin.id, {
|
|
136
|
+
updates: [
|
|
29
137
|
{
|
|
30
|
-
|
|
31
|
-
|
|
138
|
+
values: [
|
|
139
|
+
{
|
|
140
|
+
path: dottedPath,
|
|
141
|
+
value: req.body ?? null
|
|
142
|
+
}
|
|
143
|
+
]
|
|
32
144
|
}
|
|
33
145
|
]
|
|
146
|
+
}, server_api_1.SKVersion.v1);
|
|
147
|
+
return res.status(200).json({ state: 'SUCCESS', statusCode: 200 });
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
const msg = `HandleMessage failed with errors!`;
|
|
151
|
+
server.setPluginError(msg);
|
|
152
|
+
server.error(`Error in HandleMessage: ${error}`);
|
|
153
|
+
return sendFail(res, 400, error.message);
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
router.put(`${API_PATHS.ACTIVE_SCREEN}`, async (req, res) => {
|
|
157
|
+
server.debug(`** PUT ${API_PATHS.ACTIVE_SCREEN}. Params: ${JSON.stringify(req.params)} Body: ${JSON.stringify(req.body)}`);
|
|
158
|
+
try {
|
|
159
|
+
const dottedPath = pathToDotNotation(req.path);
|
|
160
|
+
server.debug(`Updating SK path ${dottedPath} with body.screenIdx`);
|
|
161
|
+
server.handleMessage(plugin.id, {
|
|
162
|
+
updates: [
|
|
163
|
+
{
|
|
164
|
+
values: [
|
|
165
|
+
{
|
|
166
|
+
path: dottedPath,
|
|
167
|
+
value: req.body.screenIdx !== undefined ? req.body.screenIdx : null
|
|
168
|
+
}
|
|
169
|
+
]
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
}, server_api_1.SKVersion.v1);
|
|
173
|
+
return res.status(200).json({ state: 'SUCCESS', statusCode: 200 });
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
const msg = `HandleMessage failed with errors!`;
|
|
177
|
+
server.setPluginError(msg);
|
|
178
|
+
server.error(`Error in HandleMessage: ${error}`);
|
|
179
|
+
return sendFail(res, 400, error.message);
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
router.get(API_PATHS.DISPLAYS, (req, res) => {
|
|
183
|
+
server.debug(`** GET ${API_PATHS.DISPLAYS}. Params: ${JSON.stringify(req.params)}`);
|
|
184
|
+
try {
|
|
185
|
+
const displays = getAvailableDisplays();
|
|
186
|
+
const items = displays && typeof displays === 'object'
|
|
187
|
+
? Object.entries(displays)
|
|
188
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
189
|
+
.filter(([_, v]) => v && typeof v === 'object')
|
|
190
|
+
.map(([displayId, v]) => ({
|
|
191
|
+
displayId,
|
|
192
|
+
displayName: v.displayName ?? null
|
|
193
|
+
}))
|
|
194
|
+
: [];
|
|
195
|
+
server.debug(`getAvailableDisplays returned: ${JSON.stringify(displays)}`);
|
|
196
|
+
server.debug(`Found ${items.length} displays: ${JSON.stringify(items)}`);
|
|
197
|
+
return res.status(200).json(items);
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
server.error(`Error reading displays: ${String(error.message || error)}`);
|
|
201
|
+
return sendFail(res, 400, error.message);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
router.get(`${API_PATHS.INSTANCE}`, (req, res) => {
|
|
205
|
+
server.debug(`** GET ${API_PATHS.INSTANCE}. Params: ${JSON.stringify(req.params)}`);
|
|
206
|
+
try {
|
|
207
|
+
const displayId = req.displayId;
|
|
208
|
+
if (!displayId) {
|
|
209
|
+
return sendFail(res, 400, 'Missing displayId parameter');
|
|
34
210
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
211
|
+
const fullPath = getDisplaySelfPath(displayId);
|
|
212
|
+
const value = server.getPath(fullPath);
|
|
213
|
+
if (value === undefined) {
|
|
214
|
+
return sendFail(res, 404, `Display ${displayId} not found`);
|
|
215
|
+
}
|
|
216
|
+
return sendOk(res, value);
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
server.error(`Error reading display ${req.params?.displayId}: ${String(error.message || error)}`);
|
|
220
|
+
return sendFail(res, 400, error.message);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
router.get(`${API_PATHS.ACTIVE_SCREEN}`, (req, res) => {
|
|
224
|
+
server.debug(`** GET ${API_PATHS.ACTIVE_SCREEN}. Params: ${JSON.stringify(req.params)}`);
|
|
225
|
+
try {
|
|
226
|
+
const displayId = req.displayId;
|
|
227
|
+
if (!displayId) {
|
|
228
|
+
return sendFail(res, 400, 'Missing displayId parameter');
|
|
229
|
+
}
|
|
230
|
+
const fullPath = getDisplaySelfPath(displayId, 'activeScreen');
|
|
231
|
+
const value = server.getPath(fullPath);
|
|
232
|
+
if (value === undefined) {
|
|
233
|
+
return sendFail(res, 404, `Active screen for display ${displayId} not found`);
|
|
234
|
+
}
|
|
235
|
+
return sendOk(res, value);
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
server.error(`Error reading activeScreen for ${req.params?.displayId}: ${String(error.message || error)}`);
|
|
239
|
+
return sendFail(res, 400, error.message);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
// List all registered routes for debugging
|
|
243
|
+
if (router.stack) {
|
|
244
|
+
router.stack.forEach((layer) => {
|
|
245
|
+
if (layer.route && layer.route.path) {
|
|
246
|
+
server.debug(`Registered route: ${layer.route.stack[0].method.toUpperCase()} ${layer.route.path}`);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
server.setPluginStatus(`Providing remote display screen control`);
|
|
251
|
+
},
|
|
252
|
+
getOpenApi: () => openapi
|
|
253
|
+
};
|
|
46
254
|
return plugin;
|
|
47
255
|
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
{
|
|
2
|
+
"openapi": "3.0.0",
|
|
3
|
+
"info": {
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"title": "KIP Remote Displays API",
|
|
6
|
+
"description": "API endpoints to list KIP displays and control the active screen for a given KIP instance via Signal K self.displays tree.\n\nUsage:\n- List displays:\n curl -s http://localhost:3000/plugins/kip/displays\n- Read a display entry:\n curl -s http://localhost:3000/plugins/kip/displays/{displayId}\n- Set a display entry:\n curl -s -X PUT -H 'Content-Type: application/json' -d '{\"displayName\":\"Mast\"}' http://localhost:3000/plugins/kip/displays/{displayId}\n- Get active screen:\n curl -s http://localhost:3000/plugins/kip/displays/{displayId}/activeScreen\n- Set active screen:\n curl -s -X PUT -H 'Content-Type: application/json' -d '{\"screenIdx\":1}' http://localhost:3000/plugins/kip/displays/{displayId}/activeScreen",
|
|
7
|
+
"termsOfService": "http://signalk.org/terms/",
|
|
8
|
+
"license": {
|
|
9
|
+
"name": "Apache 2.0",
|
|
10
|
+
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"externalDocs": {
|
|
14
|
+
"url": "http://signalk.org/specification/",
|
|
15
|
+
"description": "Signal K specification."
|
|
16
|
+
},
|
|
17
|
+
"servers": [
|
|
18
|
+
{ "url": "/" }
|
|
19
|
+
],
|
|
20
|
+
"tags": [
|
|
21
|
+
{ "name": "Displays", "description": "KIP display discovery and control." }
|
|
22
|
+
],
|
|
23
|
+
"components": {
|
|
24
|
+
"schemas": {
|
|
25
|
+
"DisplayInfo": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"properties": {
|
|
28
|
+
"displayId": { "type": "string", "description": "KIP instance UUID" },
|
|
29
|
+
"displayName": { "type": "string", "nullable": true }
|
|
30
|
+
},
|
|
31
|
+
"required": ["displayId"]
|
|
32
|
+
},
|
|
33
|
+
"SuccessResponse": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"state": { "type": "string", "enum": ["SUCCESS"] },
|
|
37
|
+
"statusCode": { "type": "integer", "enum": [200] }
|
|
38
|
+
},
|
|
39
|
+
"required": ["state", "statusCode"]
|
|
40
|
+
},
|
|
41
|
+
"ErrorResponse": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"state": { "type": "string", "enum": ["FAILED"] },
|
|
45
|
+
"statusCode": { "type": "integer" },
|
|
46
|
+
"message": { "type": "string" }
|
|
47
|
+
},
|
|
48
|
+
"required": ["state", "statusCode", "message"]
|
|
49
|
+
},
|
|
50
|
+
"ActiveScreenSetRequest": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {
|
|
53
|
+
"screenIdx": { "type": "integer", "nullable": true, "description": "Index of active screen or null to clear" }
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"AnyObjectOrNull": {
|
|
57
|
+
"oneOf": [
|
|
58
|
+
{ "type": "object", "additionalProperties": true },
|
|
59
|
+
{ "type": "null" }
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"parameters": {
|
|
64
|
+
"DisplayIdParam": {
|
|
65
|
+
"in": "path",
|
|
66
|
+
"required": true,
|
|
67
|
+
"name": "displayId",
|
|
68
|
+
"description": "KIP instance UUID",
|
|
69
|
+
"schema": { "type": "string" }
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"securitySchemes": {
|
|
73
|
+
"bearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT" },
|
|
74
|
+
"cookieAuth": { "type": "apiKey", "in": "cookie", "name": "JAUTHENTICATION" }
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"security": [{ "cookieAuth": [] }, { "bearerAuth": [] }],
|
|
78
|
+
"paths": {
|
|
79
|
+
"/plugins/kip/displays": {
|
|
80
|
+
"get": {
|
|
81
|
+
"tags": ["Displays"],
|
|
82
|
+
"summary": "List available KIP displays",
|
|
83
|
+
"responses": {
|
|
84
|
+
"200": {
|
|
85
|
+
"description": "List of KIP instances discovered under self.displays",
|
|
86
|
+
"content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/DisplayInfo" } } } }
|
|
87
|
+
},
|
|
88
|
+
"400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"/plugins/kip/displays/{displayId}": {
|
|
93
|
+
"parameters": [{ "$ref": "#/components/parameters/DisplayIdParam" }],
|
|
94
|
+
"get": {
|
|
95
|
+
"tags": ["Displays"],
|
|
96
|
+
"summary": "Get raw display entry under self.displays.{displayId}",
|
|
97
|
+
"responses": {
|
|
98
|
+
"200": { "description": "Display object", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnyObjectOrNull" } } } },
|
|
99
|
+
"404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
|
|
100
|
+
"400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"put": {
|
|
104
|
+
"tags": ["Displays"],
|
|
105
|
+
"summary": "Set display entry under self.displays.{displayId}",
|
|
106
|
+
"requestBody": {
|
|
107
|
+
"required": false,
|
|
108
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/AnyObjectOrNull" } } }
|
|
109
|
+
},
|
|
110
|
+
"responses": {
|
|
111
|
+
"200": { "description": "Updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } },
|
|
112
|
+
"400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"/plugins/kip/displays/{displayId}/activeScreen": {
|
|
117
|
+
"parameters": [{ "$ref": "#/components/parameters/DisplayIdParam" }],
|
|
118
|
+
"get": {
|
|
119
|
+
"tags": ["Displays"],
|
|
120
|
+
"summary": "Get active screen index for display",
|
|
121
|
+
"responses": {
|
|
122
|
+
"200": { "description": "Active screen index or null", "content": { "application/json": { "schema": { "type": "integer", "nullable": true } } } },
|
|
123
|
+
"404": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } },
|
|
124
|
+
"400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"put": {
|
|
128
|
+
"tags": ["Displays"],
|
|
129
|
+
"summary": "Set active screen index for display",
|
|
130
|
+
"requestBody": {
|
|
131
|
+
"required": false,
|
|
132
|
+
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ActiveScreenSetRequest" } } }
|
|
133
|
+
},
|
|
134
|
+
"responses": {
|
|
135
|
+
"200": { "description": "Updated", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SuccessResponse" } } } },
|
|
136
|
+
"400": { "description": "Bad request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } } }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -478,58 +478,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
478
478
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
479
479
|
SOFTWARE.
|
|
480
480
|
|
|
481
|
-
--------------------------------------------------------------------------------
|
|
482
|
-
Package: @angular/router
|
|
483
|
-
License: "MIT"
|
|
484
|
-
|
|
485
|
-
The MIT License
|
|
486
|
-
|
|
487
|
-
Copyright (c) 2010-2025 Google LLC. https://angular.dev/license
|
|
488
|
-
|
|
489
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
490
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
491
|
-
in the Software without restriction, including without limitation the rights
|
|
492
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
493
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
494
|
-
furnished to do so, subject to the following conditions:
|
|
495
|
-
|
|
496
|
-
The above copyright notice and this permission notice shall be included in
|
|
497
|
-
all copies or substantial portions of the Software.
|
|
498
|
-
|
|
499
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
500
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
501
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
502
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
503
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
504
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
505
|
-
THE SOFTWARE.
|
|
506
|
-
|
|
507
|
-
--------------------------------------------------------------------------------
|
|
508
|
-
Package: compare-versions
|
|
509
|
-
License: "MIT"
|
|
510
|
-
|
|
511
|
-
The MIT License (MIT)
|
|
512
|
-
|
|
513
|
-
Copyright (c) 2015-2021 Ole Michelsen
|
|
514
|
-
|
|
515
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
516
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
517
|
-
in the Software without restriction, including without limitation the rights
|
|
518
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
519
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
520
|
-
furnished to do so, subject to the following conditions:
|
|
521
|
-
|
|
522
|
-
The above copyright notice and this permission notice shall be included in all
|
|
523
|
-
copies or substantial portions of the Software.
|
|
524
|
-
|
|
525
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
526
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
527
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
528
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
529
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
530
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
531
|
-
SOFTWARE.
|
|
532
|
-
|
|
533
481
|
--------------------------------------------------------------------------------
|
|
534
482
|
Package: rxjs
|
|
535
483
|
License: "Apache-2.0"
|
|
@@ -789,6 +737,32 @@ maintained libraries used by this software which have their own
|
|
|
789
737
|
licenses; we recommend you read them, as their terms may differ from the
|
|
790
738
|
terms above.
|
|
791
739
|
|
|
740
|
+
--------------------------------------------------------------------------------
|
|
741
|
+
Package: compare-versions
|
|
742
|
+
License: "MIT"
|
|
743
|
+
|
|
744
|
+
The MIT License (MIT)
|
|
745
|
+
|
|
746
|
+
Copyright (c) 2015-2021 Ole Michelsen
|
|
747
|
+
|
|
748
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
749
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
750
|
+
in the Software without restriction, including without limitation the rights
|
|
751
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
752
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
753
|
+
furnished to do so, subject to the following conditions:
|
|
754
|
+
|
|
755
|
+
The above copyright notice and this permission notice shall be included in all
|
|
756
|
+
copies or substantial portions of the Software.
|
|
757
|
+
|
|
758
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
759
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
760
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
761
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
762
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
763
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
764
|
+
SOFTWARE.
|
|
765
|
+
|
|
792
766
|
--------------------------------------------------------------------------------
|
|
793
767
|
Package: @angular/forms
|
|
794
768
|
License: "MIT"
|
|
@@ -883,6 +857,32 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
883
857
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
884
858
|
THE SOFTWARE.
|
|
885
859
|
|
|
860
|
+
--------------------------------------------------------------------------------
|
|
861
|
+
Package: @angular/router
|
|
862
|
+
License: "MIT"
|
|
863
|
+
|
|
864
|
+
The MIT License
|
|
865
|
+
|
|
866
|
+
Copyright (c) 2010-2025 Google LLC. https://angular.dev/license
|
|
867
|
+
|
|
868
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
869
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
870
|
+
in the Software without restriction, including without limitation the rights
|
|
871
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
872
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
873
|
+
furnished to do so, subject to the following conditions:
|
|
874
|
+
|
|
875
|
+
The above copyright notice and this permission notice shall be included in
|
|
876
|
+
all copies or substantial portions of the Software.
|
|
877
|
+
|
|
878
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
879
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
880
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
881
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
882
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
883
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
884
|
+
THE SOFTWARE.
|
|
885
|
+
|
|
886
886
|
--------------------------------------------------------------------------------
|
|
887
887
|
Package: core-js
|
|
888
888
|
License: "MIT"
|
|
@@ -3,8 +3,8 @@ KIP supports multiple input modes for seamless navigation across devices.
|
|
|
3
3
|
|
|
4
4
|
| Actions | Touch | Mouse | Keyboard Shortcuts |
|
|
5
5
|
|----------------------------|--------------|------------------------------|----------------------------------------------------|
|
|
6
|
-
| Open Actions
|
|
7
|
-
| Open Notification
|
|
6
|
+
| Open Actions sidenav | Swipe left | Click, drag left, and release| <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>←</kbd> (Left Arrow) |
|
|
7
|
+
| Open Notification sidenav | Swipe right | Click, drag right, and release| <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>→</kbd> (Right Arrow) |
|
|
8
8
|
| Cycle through dashboards | Swipe up/down | Click, drag up/down, and release| <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>↑</kbd>/<kbd>↓</kbd> (Up/Down Arrow) |
|
|
9
9
|
| Toggle Fullscreen | N/A | N/A | <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>F</kbd> |
|
|
10
10
|
| Toggle Night mode | N/A | N/A | <kbd>Shift</kbd> + <kbd>Ctrl</kbd> + <kbd>N</kbd> |
|
|
@@ -59,12 +59,16 @@
|
|
|
59
59
|
<path fill="currentColor" d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2m0 16H5V5h14z" />
|
|
60
60
|
<path fill="var(--mat-sys-primary)" d="M7 7h4v4H7zm6 0h4v4h-4z" />
|
|
61
61
|
</svg>
|
|
62
|
-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14"
|
|
62
|
+
<svg id="settings" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14" height="14" width="14">
|
|
63
63
|
<path fill="none" d="m4.093 3.976 0.36 -0.93a0.838 0.838 0 0 1 0.78 -0.536h0.687a0.838 0.838 0 0 1 0.779 0.536l0.36 0.93 1.223 0.704 0.99 -0.151a0.838 0.838 0 0 1 0.837 0.41l0.335 0.587a0.837 0.837 0 0 1 -0.067 0.947l-0.612 0.779v1.407l0.629 0.78a0.838 0.838 0 0 1 0.067 0.946l-0.335 0.587a0.837 0.837 0 0 1 -0.838 0.41l-0.989 -0.15 -1.223 0.703 -0.36 0.93a0.838 0.838 0 0 1 -0.78 0.536h-0.703a0.838 0.838 0 0 1 -0.78 -0.536l-0.36 -0.93 -1.223 -0.704 -0.988 0.151a0.838 0.838 0 0 1 -0.838 -0.41l-0.335 -0.587a0.838 0.838 0 0 1 0.067 -0.946l0.611 -0.78V7.252l-0.628 -0.78a0.838 0.838 0 0 1 -0.067 -0.946l0.335 -0.586a0.838 0.838 0 0 1 0.838 -0.411l0.988 0.15 1.24 -0.703Zm-0.192 3.98a1.676 1.676 0 1 0 3.351 0 1.676 1.676 0 0 0 -3.351 0Z" stroke-width="1"></path>
|
|
64
64
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M5.92 2.51h-0.687a0.838 0.838 0 0 0 -0.78 0.536l-0.36 0.93 -1.24 0.704 -0.988 -0.151a0.838 0.838 0 0 0 -0.838 0.41l-0.335 0.587a0.838 0.838 0 0 0 0.067 0.947l0.628 0.779v1.407l-0.611 0.78a0.838 0.838 0 0 0 -0.067 0.946l0.335 0.587a0.837 0.837 0 0 0 0.838 0.41l0.988 -0.15 1.223 0.703 0.36 0.93a0.838 0.838 0 0 0 0.78 0.536h0.704a0.838 0.838 0 0 0 0.779 -0.536l0.36 -0.93 1.223 -0.704 0.989 0.151a0.838 0.838 0 0 0 0.838 -0.41l0.335 -0.587a0.838 0.838 0 0 0 -0.067 -0.946l-0.629 -0.78v-0.907m-5.864 0.204a1.676 1.676 0 1 0 3.351 0 1.676 1.676 0 0 0 -3.351 0Z" stroke-width="1"></path>
|
|
65
65
|
<path fill="var(--mat-sys-primary)" d="M8.1 3.603c-0.306 -0.053 -0.306 -0.494 0 -0.548A2.779 2.779 0 0 0 10.34 0.912l0.018 -0.085c0.067 -0.303 0.499 -0.305 0.568 -0.002l0.023 0.098a2.793 2.793 0 0 0 2.244 2.13c0.309 0.055 0.309 0.498 0 0.551a2.793 2.793 0 0 0 -2.244 2.131l-0.023 0.099c-0.069 0.302 -0.501 0.3 -0.568 -0.003l-0.018 -0.084a2.779 2.779 0 0 0 -2.238 -2.144Z" stroke-width="1"></path>
|
|
66
66
|
<path stroke="var(--mat-sys-primary)" stroke-linecap="round" stroke-linejoin="round" d="M8.1 3.603c-0.306 -0.053 -0.306 -0.494 0 -0.548A2.779 2.779 0 0 0 10.34 0.912l0.018 -0.085c0.067 -0.303 0.499 -0.305 0.568 -0.002l0.023 0.098a2.793 2.793 0 0 0 2.244 2.13c0.309 0.055 0.309 0.498 0 0.551a2.793 2.793 0 0 0 -2.244 2.131l-0.023 0.099c-0.069 0.302 -0.501 0.3 -0.568 -0.003l-0.018 -0.084a2.779 2.779 0 0 0 -2.238 -2.144Z" stroke-width="1"></path>
|
|
67
67
|
</svg>
|
|
68
|
+
<svg id="remote-control" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 16 16" height="16" width="16">
|
|
69
|
+
<path fill="var(--mat-sys-primary)" fill-opacity="0.5" d="M14 3.5v9c0 0.132625 -0.0526875 0.2598125 -0.1464375 0.3535625S13.632625 13 13.5 13H3c0 -0.2651875 -0.10535625 -0.5195625 -0.29289375 -0.707125C2.51956875 12.105375 2.26521875 12 2 12V3.5c0 -0.13260625 0.05268125 -0.2597875 0.14644375 -0.35355625C2.2402125 3.05268125 2.36739375 3 2.5 3h11c0.132625 0 0.2598125 0.05268125 0.3535625 0.14644375C13.9473125 3.2402125 14 3.36739375 14 3.5Z" stroke-width="1"></path>
|
|
70
|
+
<path fill="currentColor" d="M14.5 3.5v9c0 0.2651875 -0.105375 0.5195625 -0.292875 0.707125 -0.1875625 0.1875 -0.4419375 0.292875 -0.707125 0.292875h-4.5c-0.132625 0 -0.2598125 -0.0526875 -0.3535625 -0.1464375S8.5 13.132625 8.5 13s0.0526875 -0.2598125 0.1464375 -0.3535625S8.867375 12.5 9 12.5h4.5V3.5H2.5v2.5c0 0.13260625 -0.05268125 0.2598125 -0.14644375 0.3535625C2.2597875 6.4473125 2.13260625 6.5 2 6.5s-0.2597875 -0.0526875 -0.35355625 -0.1464375C1.55268125 6.2598125 1.5 6.13260625 1.5 6V3.5c0 -0.26521875 0.10535625 -0.51956875 0.29289375 -0.70710625C1.98043125 2.60535625 2.23478125 2.5 2.5 2.5h11c0.2651875 0 0.5195625 0.10535625 0.707125 0.29289375C14.394625 2.98043125 14.5 3.23478125 14.5 3.5ZM2 11.5c-0.13260625 0 -0.2597875 0.0526875 -0.35355625 0.1464375C1.55268125 11.7401875 1.5 11.867375 1.5 12s0.05268125 0.2598125 0.14644375 0.3535625C1.7402125 12.4473125 1.86739375 12.5 2 12.5s0.2597875 0.0526875 0.35355625 0.1464375C2.44731875 12.7401875 2.5 12.867375 2.5 13s0.05268125 0.2598125 0.14644375 0.3535625C2.7402125 13.4473125 2.86739375 13.5 3 13.5s0.2597875 -0.0526875 0.35355625 -0.1464375C3.44731875 13.2598125 3.5 13.132625 3.5 13c0 -0.3978125 -0.1580375 -0.779375 -0.4393375 -1.0606875C2.77935625 11.6580625 2.397825 11.5 2 11.5Zm0 -2c-0.13260625 0 -0.2597875 0.0526875 -0.35355625 0.1464375C1.55268125 9.7401875 1.5 9.867375 1.5 10s0.05268125 0.2598125 0.14644375 0.3535625C1.7402125 10.4473125 1.86739375 10.5 2 10.5c0.66304375 0 1.298925 0.263375 1.76776875 0.73225C4.23660625 11.7010625 4.5 12.3369375 4.5 13c0 0.132625 0.05268125 0.2598125 0.14644375 0.3535625C4.7402125 13.4473125 4.86739375 13.5 5 13.5s0.2597875 -0.0526875 0.35355625 -0.1464375C5.44731875 13.2598125 5.5 13.132625 5.5 13c-0.00099375 -0.9279375 -0.3700625 -1.817625 -1.026225 -2.47375C3.8176125 9.8700625 2.92795 9.501 2 9.5Zm0 -2c-0.13260625 0 -0.2597875 0.0526875 -0.35355625 0.1464375C1.55268125 7.7401875 1.5 7.867375 1.5 8s0.05268125 0.2598125 0.14644375 0.3535625C1.7402125 8.4473125 1.86739375 8.5 2 8.5c1.19306875 0.0013125 2.33689375 0.475875 3.18051875 1.3195C6.02414375 10.663125 6.4986875 11.8069375 6.5 13c0 0.132625 0.0526875 0.2598125 0.1464375 0.3535625S6.867375 13.5 7 13.5s0.2598125 -0.0526875 0.3535625 -0.1464375S7.5 13.132625 7.5 13c-0.001625 -1.4581875 -0.581625 -2.8561875 -1.6127375 -3.88725C4.85616875 8.081625 3.45818125 7.501625 2 7.5Z" stroke-width="1"></path>
|
|
71
|
+
</svg>
|
|
68
72
|
<svg id="help-center" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
|
69
73
|
<circle fill="currentColor" fill-opacity="0" stroke="currentColor" stroke-width="2" cx="12" cy="13" r="9" />
|
|
70
74
|
<text fill="var(--mat-sys-primary)" x="12" y="19" font-size="16" font-family="Roboto" font-style="bolder" text-anchor="middle" >?</text>
|