homebridge-generac 0.1.0-beta.1
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/CHANGELOG.md +32 -0
- package/LICENSE +200 -0
- package/NOTICE +20 -0
- package/README.md +179 -0
- package/SECURITY.md +17 -0
- package/config.schema.json +133 -0
- package/dist/accessory.d.ts +54 -0
- package/dist/accessory.js +191 -0
- package/dist/accessory.js.map +1 -0
- package/dist/api.d.ts +43 -0
- package/dist/api.js +124 -0
- package/dist/api.js.map +1 -0
- package/dist/attention.d.ts +18 -0
- package/dist/attention.js +40 -0
- package/dist/attention.js.map +1 -0
- package/dist/auth.d.ts +56 -0
- package/dist/auth.js +412 -0
- package/dist/auth.js.map +1 -0
- package/dist/captures.d.ts +9 -0
- package/dist/captures.js +42 -0
- package/dist/captures.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +116 -0
- package/dist/cli.js.map +1 -0
- package/dist/exercise.d.ts +42 -0
- package/dist/exercise.js +115 -0
- package/dist/exercise.js.map +1 -0
- package/dist/files.d.ts +5 -0
- package/dist/files.js +28 -0
- package/dist/files.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +74 -0
- package/dist/model.js +167 -0
- package/dist/model.js.map +1 -0
- package/dist/platform.d.ts +117 -0
- package/dist/platform.js +596 -0
- package/dist/platform.js.map +1 -0
- package/dist/settings.d.ts +75 -0
- package/dist/settings.js +115 -0
- package/dist/settings.js.map +1 -0
- package/dist/state.d.ts +28 -0
- package/dist/state.js +43 -0
- package/dist/state.js.map +1 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.js +48 -0
- package/dist/types.js.map +1 -0
- package/dist/ui/server.d.ts +96 -0
- package/dist/ui/server.js +328 -0
- package/dist/ui/server.js.map +1 -0
- package/homebridge-ui/public/generac-banner.png +0 -0
- package/homebridge-ui/public/generac.css +255 -0
- package/homebridge-ui/public/index.css +510 -0
- package/homebridge-ui/public/index.html +14 -0
- package/homebridge-ui/public/js/api.js +31 -0
- package/homebridge-ui/public/js/app.js +1 -0
- package/homebridge-ui/public/js/card.js +27 -0
- package/homebridge-ui/public/js/copy.js +167 -0
- package/homebridge-ui/public/js/dom.js +247 -0
- package/homebridge-ui/public/js/footer.js +26 -0
- package/homebridge-ui/public/js/format.js +62 -0
- package/homebridge-ui/public/js/main.js +288 -0
- package/homebridge-ui/public/js/mark.js +34 -0
- package/homebridge-ui/public/js/model.js +120 -0
- package/homebridge-ui/public/js/sections/account.js +272 -0
- package/homebridge-ui/public/js/sections/generators.js +165 -0
- package/homebridge-ui/public/js/sections/settings.js +114 -0
- package/homebridge-ui/public/js/validate.js +45 -0
- package/homebridge-ui/server.js +8 -0
- package/package.json +70 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The settings page's server side (SPEC section 10). Started by the Homebridge UI as a child process through
|
|
3
|
+
* homebridge-ui/server.js; the platform never loads this module. It answers /status from credentials.json and
|
|
4
|
+
* the platform's state.json, runs the Connect flow (the only time the UI process talks to Generac), writes
|
|
5
|
+
* credentials.json with the platform's atomic writer, and deletes it on Disconnect.
|
|
6
|
+
*
|
|
7
|
+
* The password and the MFA code pass through `connectStart` and `connectCode` once each and are never logged or
|
|
8
|
+
* stored. The pending login is a single in-memory record; a second Connect replaces it.
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { HomebridgePluginUiServer } from '@homebridge/plugin-ui-utils';
|
|
13
|
+
import { readCredentials, writeCredentials } from '../api.js';
|
|
14
|
+
import { AuthError, login as realLogin } from '../auth.js';
|
|
15
|
+
import { credentialCandidates, dataDir, PLATFORM_NAME, pluginVersion, primaryCredentialsPath, RESET_MARKER, statePath } from '../settings.js';
|
|
16
|
+
import { buildStateFile, readStateFile, writeStateFile } from '../state.js';
|
|
17
|
+
/** A pending Connect expires this long after Sign in (SPEC section 10). */
|
|
18
|
+
export const PENDING_LOGIN_TTL_MS = 5 * 60 * 1000;
|
|
19
|
+
/** Three rejected codes end the sign-in (SPEC section 4.1). */
|
|
20
|
+
export const MAX_CODE_ATTEMPTS = 3;
|
|
21
|
+
/** A promise with its settlers, settled at most once. */
|
|
22
|
+
class Deferred {
|
|
23
|
+
promise;
|
|
24
|
+
settle;
|
|
25
|
+
done = false;
|
|
26
|
+
constructor() {
|
|
27
|
+
this.promise = new Promise((resolve) => {
|
|
28
|
+
this.settle = resolve;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
resolve(value) {
|
|
32
|
+
if (!this.done) {
|
|
33
|
+
this.done = true;
|
|
34
|
+
this.settle(value);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
class CancelledError extends Error {
|
|
39
|
+
constructor(reason) {
|
|
40
|
+
super(`sign-in ${reason}`);
|
|
41
|
+
this.name = 'CancelledError';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Maps a failed login to the page's error codes (SPEC section 10). */
|
|
45
|
+
export function mapError(err, phase) {
|
|
46
|
+
if (phase === 'code') {
|
|
47
|
+
if (err instanceof AuthError && /three rejected codes/.test(err.message)) {
|
|
48
|
+
return { error: 'too_many' };
|
|
49
|
+
}
|
|
50
|
+
return { error: 'expired' };
|
|
51
|
+
}
|
|
52
|
+
if (err instanceof AuthError) {
|
|
53
|
+
switch (err.step) {
|
|
54
|
+
case 'password':
|
|
55
|
+
return { error: 'wrong_password' };
|
|
56
|
+
case 'identifier':
|
|
57
|
+
return { error: 'unknown_email' };
|
|
58
|
+
case 'mfa':
|
|
59
|
+
return { error: 'unsupported_factor' };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { error: 'network' };
|
|
63
|
+
}
|
|
64
|
+
function text(payload, key) {
|
|
65
|
+
const value = payload !== null && typeof payload === 'object' ? payload[key] : undefined;
|
|
66
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
67
|
+
}
|
|
68
|
+
export class GeneracUiHandlers {
|
|
69
|
+
opts;
|
|
70
|
+
login;
|
|
71
|
+
now;
|
|
72
|
+
version;
|
|
73
|
+
pending = null;
|
|
74
|
+
constructor(opts) {
|
|
75
|
+
this.opts = opts;
|
|
76
|
+
this.login = opts.login ?? realLogin;
|
|
77
|
+
this.now = opts.now ?? Date.now;
|
|
78
|
+
this.version = opts.version ?? pluginVersion();
|
|
79
|
+
}
|
|
80
|
+
/** The request paths of SPEC section 10 and their handlers, for HomebridgePluginUiServer.onRequest. */
|
|
81
|
+
routes() {
|
|
82
|
+
return {
|
|
83
|
+
'/status': () => this.status(),
|
|
84
|
+
'/connect/start': (payload) => this.connectStart(payload),
|
|
85
|
+
'/connect/code': (payload) => this.connectCode(payload),
|
|
86
|
+
'/connect/cancel': () => this.connectCancel(),
|
|
87
|
+
'/disconnect': () => this.disconnect(),
|
|
88
|
+
'/reset': () => this.reset(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// /status
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
async status() {
|
|
95
|
+
const creds = this.findCredentials();
|
|
96
|
+
const state = readStateFile(statePath(this.opts.storagePath));
|
|
97
|
+
let account;
|
|
98
|
+
if (!creds) {
|
|
99
|
+
account = { state: 'not_connected' };
|
|
100
|
+
}
|
|
101
|
+
else if (this.stateNewerThan(state, creds.creds)) {
|
|
102
|
+
account = { state: state.account.state, email: creds.creds.email, lastChecked: state.account.lastChecked };
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// Credentials exist but the platform has not polled with them yet (SPEC section 10).
|
|
106
|
+
account = { state: 'checking', email: creds.creds.email };
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
account,
|
|
110
|
+
generators: state?.generators ?? [],
|
|
111
|
+
others: (state?.others ?? []).map((o) => ({ type: o.type, name: o.name })),
|
|
112
|
+
version: this.version,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/** True when the state file's account entry was written after these credentials were created. */
|
|
116
|
+
stateNewerThan(state, creds) {
|
|
117
|
+
if (!state || state.account.state === 'not_connected') {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const updated = Date.parse(state.updatedAt);
|
|
121
|
+
const created = Date.parse(creds.created_at);
|
|
122
|
+
if (Number.isNaN(updated)) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return Number.isNaN(created) || updated > created;
|
|
126
|
+
}
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// Connect flow (SPEC section 10)
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
async connectStart(payload) {
|
|
131
|
+
const email = text(payload, 'email');
|
|
132
|
+
const password = text(payload, 'password');
|
|
133
|
+
if (!email || !password) {
|
|
134
|
+
return { error: 'network' };
|
|
135
|
+
}
|
|
136
|
+
this.discardPending('replaced');
|
|
137
|
+
const pending = {
|
|
138
|
+
email, startedAt: this.now(), phase: 'start', waiter: new Deferred(), prompt: null, attempts: 0, cancelled: false, expiry: null,
|
|
139
|
+
};
|
|
140
|
+
this.pending = pending;
|
|
141
|
+
pending.expiry = setTimeout(() => this.expire(pending), PENDING_LOGIN_TTL_MS);
|
|
142
|
+
pending.expiry.unref();
|
|
143
|
+
const mfaPrompt = (type, attempt) => new Promise((resolve, reject) => {
|
|
144
|
+
if (pending.cancelled) {
|
|
145
|
+
reject(new CancelledError('cancelled'));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
pending.prompt = { type, resolve, reject };
|
|
149
|
+
// A second or third prompt means Auth0 rejected the previous code.
|
|
150
|
+
this.settle(pending, attempt > 1 ? { error: 'wrong_code' } : { step: 'code', method: type });
|
|
151
|
+
});
|
|
152
|
+
// The password lives in this call's frames only; nothing here keeps a reference to it.
|
|
153
|
+
this.login(email, password, { mfaPrompt }).then((result) => {
|
|
154
|
+
if (pending.cancelled) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
this.writeCredentials(email, result);
|
|
158
|
+
this.finish(pending);
|
|
159
|
+
this.settle(pending, { step: 'done' });
|
|
160
|
+
}, (err) => {
|
|
161
|
+
if (pending.cancelled) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
this.finish(pending);
|
|
165
|
+
this.settle(pending, mapError(err, pending.phase));
|
|
166
|
+
});
|
|
167
|
+
return pending.waiter.promise;
|
|
168
|
+
}
|
|
169
|
+
async connectCode(payload) {
|
|
170
|
+
const code = text(payload, 'code');
|
|
171
|
+
const pending = this.pending;
|
|
172
|
+
if (!pending || !pending.prompt || !code) {
|
|
173
|
+
return { error: 'expired' };
|
|
174
|
+
}
|
|
175
|
+
if (this.now() - pending.startedAt >= PENDING_LOGIN_TTL_MS) {
|
|
176
|
+
this.expire(pending);
|
|
177
|
+
return { error: 'expired' };
|
|
178
|
+
}
|
|
179
|
+
if (pending.attempts >= MAX_CODE_ATTEMPTS) {
|
|
180
|
+
return { error: 'too_many' };
|
|
181
|
+
}
|
|
182
|
+
pending.attempts++;
|
|
183
|
+
pending.phase = 'code';
|
|
184
|
+
pending.waiter = new Deferred();
|
|
185
|
+
const prompt = pending.prompt;
|
|
186
|
+
pending.prompt = null;
|
|
187
|
+
prompt.resolve(code);
|
|
188
|
+
return pending.waiter.promise;
|
|
189
|
+
}
|
|
190
|
+
async connectCancel() {
|
|
191
|
+
this.discardPending('cancelled');
|
|
192
|
+
return { ok: true };
|
|
193
|
+
}
|
|
194
|
+
/** Whether a Connect is in progress (for tests). */
|
|
195
|
+
get hasPending() {
|
|
196
|
+
return this.pending !== null;
|
|
197
|
+
}
|
|
198
|
+
settle(pending, response) {
|
|
199
|
+
const waiter = pending.waiter;
|
|
200
|
+
pending.waiter = null;
|
|
201
|
+
waiter?.resolve(response);
|
|
202
|
+
}
|
|
203
|
+
/** The login ended (success or failure): forget it. */
|
|
204
|
+
finish(pending) {
|
|
205
|
+
if (pending.expiry) {
|
|
206
|
+
clearTimeout(pending.expiry);
|
|
207
|
+
pending.expiry = null;
|
|
208
|
+
}
|
|
209
|
+
if (this.pending === pending) {
|
|
210
|
+
this.pending = null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/** Cancel, replace or expire: the login is abandoned, its result (if it ever arrives) ignored, its prompt rejected. */
|
|
214
|
+
discardPending(reason) {
|
|
215
|
+
const pending = this.pending;
|
|
216
|
+
if (!pending) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
pending.cancelled = true;
|
|
220
|
+
this.finish(pending);
|
|
221
|
+
const prompt = pending.prompt;
|
|
222
|
+
pending.prompt = null;
|
|
223
|
+
prompt?.reject(new CancelledError(reason));
|
|
224
|
+
// A request still waiting (Sign in in flight, or a code in flight) is answered so the page never hangs.
|
|
225
|
+
this.settle(pending, pending.phase === 'start' ? { error: 'network' } : { error: 'expired' });
|
|
226
|
+
}
|
|
227
|
+
expire(pending) {
|
|
228
|
+
if (this.pending === pending) {
|
|
229
|
+
this.discardPending('expired');
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
writeCredentials(email, result) {
|
|
233
|
+
writeCredentials(primaryCredentialsPath(this.opts.storagePath), {
|
|
234
|
+
email,
|
|
235
|
+
refresh_token: result.refreshToken,
|
|
236
|
+
dpop_private_key_pem: result.key.toPem(),
|
|
237
|
+
created_at: new Date(this.now()).toISOString(),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
// /disconnect and /reset
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
async disconnect() {
|
|
244
|
+
this.discardPending('cancelled');
|
|
245
|
+
this.deleteCredentials();
|
|
246
|
+
this.clearAccountState();
|
|
247
|
+
return { ok: true };
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* The Reset dialog (SPEC section 11.3 E): sign out, forget the saved state, and leave a marker so the platform
|
|
251
|
+
* removes every cached accessory on its next start. The page resets the platform block itself.
|
|
252
|
+
*/
|
|
253
|
+
async reset() {
|
|
254
|
+
this.discardPending('cancelled');
|
|
255
|
+
this.deleteCredentials();
|
|
256
|
+
fs.rmSync(statePath(this.opts.storagePath), { force: true });
|
|
257
|
+
fs.mkdirSync(dataDir(this.opts.storagePath), { recursive: true, mode: 0o700 });
|
|
258
|
+
fs.writeFileSync(path.join(dataDir(this.opts.storagePath), RESET_MARKER), new Date(this.now()).toISOString() + '\n', { mode: 0o600 });
|
|
259
|
+
return { ok: true };
|
|
260
|
+
}
|
|
261
|
+
deleteCredentials() {
|
|
262
|
+
const files = new Set([primaryCredentialsPath(this.opts.storagePath)]);
|
|
263
|
+
const found = this.findCredentials();
|
|
264
|
+
if (found) {
|
|
265
|
+
files.add(found.file);
|
|
266
|
+
}
|
|
267
|
+
for (const file of files) {
|
|
268
|
+
fs.rmSync(file, { force: true });
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** Clears the account section of state.json, keeping the last generator states for the page. */
|
|
272
|
+
clearAccountState() {
|
|
273
|
+
const file = statePath(this.opts.storagePath);
|
|
274
|
+
const state = readStateFile(file);
|
|
275
|
+
writeStateFile(file, buildStateFile({ state: 'not_connected' }, state?.generators ?? [], state?.others ?? [], new Date(this.now())));
|
|
276
|
+
}
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// Credentials on disk (SPEC section 4.4)
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
findCredentials() {
|
|
281
|
+
for (const file of credentialCandidates(this.opts.storagePath, this.configuredCredentialsPath())) {
|
|
282
|
+
try {
|
|
283
|
+
const creds = readCredentials(file);
|
|
284
|
+
if (creds) {
|
|
285
|
+
return { file, creds };
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// Unreadable or malformed: the platform warns about it; the page shows not connected.
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
/** `credentialsPath` from the platform block in config.json, when set. */
|
|
295
|
+
configuredCredentialsPath() {
|
|
296
|
+
if (!this.opts.configPath) {
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
const config = JSON.parse(fs.readFileSync(this.opts.configPath, 'utf8'));
|
|
301
|
+
const block = config.platforms?.find((p) => p && p.platform === PLATFORM_NAME);
|
|
302
|
+
const value = block?.credentialsPath;
|
|
303
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return undefined;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/** The process the Homebridge UI starts (homebridge-ui/server.js). */
|
|
311
|
+
export class GeneracUiServer extends HomebridgePluginUiServer {
|
|
312
|
+
constructor() {
|
|
313
|
+
super();
|
|
314
|
+
const storagePath = this.homebridgeStoragePath;
|
|
315
|
+
if (!storagePath) {
|
|
316
|
+
throw new Error('The Homebridge storage path is not available to the Generac settings UI server');
|
|
317
|
+
}
|
|
318
|
+
const handlers = new GeneracUiHandlers({ storagePath, configPath: this.homebridgeConfigPath });
|
|
319
|
+
for (const [route, handler] of Object.entries(handlers.routes())) {
|
|
320
|
+
this.onRequest(route, handler);
|
|
321
|
+
}
|
|
322
|
+
this.ready();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
export function startUiServer() {
|
|
326
|
+
return new GeneracUiServer();
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/ui/server.ts"],"names":[],"mappings":"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"}
|
|
Binary file
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/* What this page adds to the shell: the affiliation line under the intro (SPEC section 11.1, item 2a) and the account
|
|
2
|
+
and generator cards (11.3 B to D). Every colour is a host variable with the host's light value as the fallback; the dark values are re-declared
|
|
3
|
+
in index.css under the host's dark marker. */
|
|
4
|
+
|
|
5
|
+
.gn-body {
|
|
6
|
+
margin: 0 0 12px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.gn-body:last-child {
|
|
10
|
+
margin-bottom: 0;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* The email on the account card, monospace like every identifier. */
|
|
14
|
+
.gn-row {
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-wrap: wrap;
|
|
17
|
+
align-items: center;
|
|
18
|
+
gap: 8px 12px;
|
|
19
|
+
margin: 0 0 8px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.gn-email {
|
|
23
|
+
font-family: var(--bs-font-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.gn-meta {
|
|
27
|
+
color: var(--bs-secondary-color, currentColor);
|
|
28
|
+
font-size: 0.875em;
|
|
29
|
+
margin: 0 0 1rem;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* The row of buttons under a card body: the primary button and its text Cancel. */
|
|
33
|
+
.gn-actions {
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-wrap: wrap;
|
|
36
|
+
align-items: center;
|
|
37
|
+
gap: 8px 12px;
|
|
38
|
+
margin: 0 0 16px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* Badges beyond the shell's status badge (SPEC section 11.2): the host's subtle success (Ready), info (Running,
|
|
42
|
+
Exercising), danger (Fault), secondary (Not responding) and warning (Maintenance due, Low) variables; an outline
|
|
43
|
+
for Checking. */
|
|
44
|
+
.generac-ui .gn-badge {
|
|
45
|
+
border: 1px solid transparent;
|
|
46
|
+
white-space: nowrap;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.generac-ui .gn-badge-ready {
|
|
50
|
+
color: var(--bs-success-text-emphasis, #0a3622);
|
|
51
|
+
background-color: var(--bs-success-bg-subtle, #d1e7dd);
|
|
52
|
+
border-color: var(--bs-success-border-subtle, #a3cfbb);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.generac-ui .gn-badge-running,
|
|
56
|
+
.generac-ui .gn-badge-exercising {
|
|
57
|
+
color: var(--bs-info-text-emphasis, #055160);
|
|
58
|
+
background-color: var(--bs-info-bg-subtle, #cff4fc);
|
|
59
|
+
border-color: var(--bs-info-border-subtle, #9eeaf9);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.generac-ui .gn-badge-exercising {
|
|
63
|
+
background-color: transparent;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.generac-ui .gn-badge-fault {
|
|
67
|
+
color: var(--bs-danger-text-emphasis, #58151c);
|
|
68
|
+
background-color: var(--bs-danger-bg-subtle, #f8d7da);
|
|
69
|
+
border-color: var(--bs-danger-border-subtle, #f1aeb5);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.generac-ui .gn-badge-muted,
|
|
73
|
+
.generac-ui .gn-badge-outline {
|
|
74
|
+
color: var(--bs-secondary-color, currentColor);
|
|
75
|
+
background-color: transparent;
|
|
76
|
+
border-color: var(--bs-secondary, #6c757d);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.generac-ui .gn-badge-warn {
|
|
80
|
+
color: var(--bs-warning-text-emphasis, #664d03);
|
|
81
|
+
background-color: var(--bs-warning-bg-subtle, #fff3cd);
|
|
82
|
+
border-color: #ffecb5;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* The generator card title: the name in bold, then the model and the serial number on their own lines. */
|
|
86
|
+
.gn-title-block {
|
|
87
|
+
display: inline-block;
|
|
88
|
+
min-width: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.gn-model {
|
|
92
|
+
display: block;
|
|
93
|
+
font-weight: 400;
|
|
94
|
+
font-size: 0.875em;
|
|
95
|
+
line-height: 1.4;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.gn-serial {
|
|
99
|
+
display: block;
|
|
100
|
+
font-weight: 300;
|
|
101
|
+
font-size: 0.875em;
|
|
102
|
+
line-height: 1.4;
|
|
103
|
+
color: var(--bs-secondary-color, currentColor);
|
|
104
|
+
font-family: var(--bs-font-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/* The status row: the badges, then the fault reasons one per line (on a phone) or dot-separated, or the note. */
|
|
108
|
+
.gn-status {
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-wrap: wrap;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 6px 12px;
|
|
113
|
+
margin: 0 0 12px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.gn-reasons {
|
|
117
|
+
display: flex;
|
|
118
|
+
flex-wrap: wrap;
|
|
119
|
+
gap: 4px 12px;
|
|
120
|
+
margin: 0;
|
|
121
|
+
padding: 0;
|
|
122
|
+
list-style: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.gn-reasons li {
|
|
126
|
+
color: var(--bs-secondary-color, currentColor);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.gn-reasons li + li::before {
|
|
130
|
+
content: "\00B7";
|
|
131
|
+
margin-right: 12px;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.gn-note {
|
|
135
|
+
color: var(--bs-secondary-color, currentColor);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* The detail rows: label and value in two columns, one column on a phone. */
|
|
139
|
+
.gn-kv {
|
|
140
|
+
display: grid;
|
|
141
|
+
grid-template-columns: minmax(0, max-content) minmax(0, 1fr);
|
|
142
|
+
column-gap: 16px;
|
|
143
|
+
row-gap: 6px;
|
|
144
|
+
margin: 0 0 16px;
|
|
145
|
+
padding-top: 12px;
|
|
146
|
+
border-top: 1px solid var(--bs-border-color, rgba(128, 128, 128, 0.35));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.gn-kv dt {
|
|
150
|
+
color: var(--bs-secondary-color, currentColor);
|
|
151
|
+
font-weight: 400;
|
|
152
|
+
margin: 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.gn-kv dd {
|
|
156
|
+
margin: 0;
|
|
157
|
+
display: flex;
|
|
158
|
+
flex-wrap: wrap;
|
|
159
|
+
align-items: center;
|
|
160
|
+
gap: 4px 8px;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.gn-kv .gn-help {
|
|
164
|
+
font-size: 0.875em;
|
|
165
|
+
font-weight: 300;
|
|
166
|
+
color: var(--bs-secondary-color, currentColor);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@media (max-width: 599.98px) {
|
|
170
|
+
.gn-kv {
|
|
171
|
+
grid-template-columns: minmax(0, 1fr);
|
|
172
|
+
row-gap: 2px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.gn-kv dd {
|
|
176
|
+
margin-bottom: 8px;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/* Rename in the footer, editing in place: the Name field with Save name and Cancel beside it. */
|
|
181
|
+
.gn-rename {
|
|
182
|
+
display: flex;
|
|
183
|
+
flex-wrap: wrap;
|
|
184
|
+
align-items: center;
|
|
185
|
+
gap: 8px;
|
|
186
|
+
flex: 1 1 auto;
|
|
187
|
+
justify-content: flex-end;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.gn-rename-editor {
|
|
191
|
+
display: flex;
|
|
192
|
+
flex-wrap: wrap;
|
|
193
|
+
align-items: flex-end;
|
|
194
|
+
gap: 8px 12px;
|
|
195
|
+
width: 100%;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.gn-rename-editor > .mb-3 {
|
|
199
|
+
flex: 1 1 14rem;
|
|
200
|
+
margin-bottom: 0 !important;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.gn-rename-actions {
|
|
204
|
+
display: inline-flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
gap: 12px;
|
|
207
|
+
padding-bottom: 4px;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* The empty line and the "Also on your account" lines under the cards. */
|
|
211
|
+
.gn-empty,
|
|
212
|
+
.gn-also {
|
|
213
|
+
color: var(--bs-secondary-color, currentColor);
|
|
214
|
+
margin: 0 0 8px;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.gn-reset {
|
|
218
|
+
margin-top: 0.25rem;
|
|
219
|
+
margin-bottom: 1rem;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/* The Reset dialog, in the page flow directly below the Reset link (SPEC section 11.2): a bordered panel in the
|
|
223
|
+
card's colours, never a fixed overlay. */
|
|
224
|
+
.generac-ui .ns-inline-dialog {
|
|
225
|
+
margin-top: 0.75rem;
|
|
226
|
+
max-width: 28rem;
|
|
227
|
+
border: 1px solid var(--bs-border-color, rgba(128, 128, 128, 0.35));
|
|
228
|
+
border-radius: 0.5rem;
|
|
229
|
+
background: var(--bs-body-bg, #fff);
|
|
230
|
+
color: var(--bs-body-color, inherit);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.generac-ui .ns-inline-dialog-title {
|
|
234
|
+
padding: 0.75rem 1rem;
|
|
235
|
+
border-bottom: 1px solid var(--bs-border-color, rgba(128, 128, 128, 0.35));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.generac-ui .ns-inline-dialog-body {
|
|
239
|
+
padding: 1rem;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.generac-ui .ns-inline-dialog-actions {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-wrap: wrap;
|
|
245
|
+
align-items: center;
|
|
246
|
+
gap: 8px 12px;
|
|
247
|
+
padding: 0.75rem 1rem;
|
|
248
|
+
border-top: 1px solid var(--bs-border-color, rgba(128, 128, 128, 0.35));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/* The affiliation line: muted, directly under the intro paragraphs, never in the footer. */
|
|
252
|
+
.gn-affiliation {
|
|
253
|
+
margin: 0 0 0.5rem;
|
|
254
|
+
color: var(--bs-secondary-color, currentColor);
|
|
255
|
+
}
|