@7365admin1/layer-common 4.0.3-staging.230 → 4.0.3-staging.232
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.
|
@@ -5,9 +5,16 @@
|
|
|
5
5
|
{{ isEditing ? "Edit Plan" : "Create New Plan" }}
|
|
6
6
|
</v-card-title>
|
|
7
7
|
|
|
8
|
-
<!--
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
<!--
|
|
9
|
+
Form Content. The FORM sits INSIDE the body, not around it. The body is
|
|
10
|
+
what scrolls; a `v-form` wrapped around body + footer is an unshrinkable
|
|
11
|
+
flex item, so a long applications list pushed the footer past the card's
|
|
12
|
+
capped height and Save became unreachable. Same shape as
|
|
13
|
+
`AccessCardAddForm`. Submit is fired from the footer button by hand
|
|
14
|
+
because the button now lives outside the form element.
|
|
15
|
+
-->
|
|
16
|
+
<v-card-text class="screen-modal__body px-6 pt-4">
|
|
17
|
+
<v-form ref="formRef" @submit.prevent="submit">
|
|
11
18
|
<!-- Plan Details Section -->
|
|
12
19
|
<div class="plan-form__section">Plan Details</div>
|
|
13
20
|
|
|
@@ -196,24 +203,24 @@
|
|
|
196
203
|
>
|
|
197
204
|
{{ errors.pricing }}
|
|
198
205
|
</v-alert>
|
|
199
|
-
</v-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
</v-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
</v-
|
|
214
|
-
</v-
|
|
215
|
-
</v-
|
|
216
|
-
</v-
|
|
206
|
+
</v-form>
|
|
207
|
+
</v-card-text>
|
|
208
|
+
|
|
209
|
+
<!-- Footer Buttons -->
|
|
210
|
+
<v-card-actions class="screen-modal__footer">
|
|
211
|
+
<v-row no-gutters>
|
|
212
|
+
<v-col cols="6" class="pr-2">
|
|
213
|
+
<v-btn block variant="outlined" class="text-none screen-btn-ghost" @click="$emit('cancel')">
|
|
214
|
+
Cancel
|
|
215
|
+
</v-btn>
|
|
216
|
+
</v-col>
|
|
217
|
+
<v-col cols="6" class="pl-2">
|
|
218
|
+
<v-btn block variant="flat" class="text-none screen-btn-primary" @click="submit">
|
|
219
|
+
{{ isEditing ? "Save Changes" : "Create Plan" }}
|
|
220
|
+
</v-btn>
|
|
221
|
+
</v-col>
|
|
222
|
+
</v-row>
|
|
223
|
+
</v-card-actions>
|
|
217
224
|
</v-card>
|
|
218
225
|
</template>
|
|
219
226
|
|
|
@@ -181,15 +181,20 @@
|
|
|
181
181
|
</v-data-table>
|
|
182
182
|
</AppCard>
|
|
183
183
|
|
|
184
|
-
<!--
|
|
184
|
+
<!--
|
|
185
|
+
Create / Edit Dialog. The form component renders its OWN `.screen-modal`
|
|
186
|
+
card, so it has to be the dialog's DIRECT child: Vuetify only makes
|
|
187
|
+
`.v-overlay__content > .v-card` a capped flex column. Wrapping it in a
|
|
188
|
+
second `v-card` handed that treatment to the wrapper and left the real
|
|
189
|
+
card a plain `overflow: hidden` block - it grew past the viewport and
|
|
190
|
+
clipped its own Cancel/Save footer, with no scrollbar to reach it.
|
|
191
|
+
-->
|
|
185
192
|
<v-dialog v-model="showModal" max-width="600px" persistent>
|
|
186
|
-
<
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
/>
|
|
192
|
-
</v-card>
|
|
193
|
+
<CreateSubscriptionPlan
|
|
194
|
+
:initial-plan="editingPlan"
|
|
195
|
+
@cancel="closeModal"
|
|
196
|
+
@submit="handleSubmit"
|
|
197
|
+
/>
|
|
193
198
|
</v-dialog>
|
|
194
199
|
|
|
195
200
|
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "4.0.3-staging.
|
|
5
|
+
"version": "4.0.3-staging.232",
|
|
6
6
|
"author": "7365admin1",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
playerSignal,
|
|
16
16
|
sharedCapabilityNote,
|
|
17
17
|
tileView,
|
|
18
|
+
UNVERIFIED_DETAIL,
|
|
18
19
|
wallCameras,
|
|
19
20
|
wallLayout,
|
|
20
21
|
wallPageCount,
|
|
@@ -381,6 +382,68 @@ test("a server that says live video is unsupported is believed", () => {
|
|
|
381
382
|
|
|
382
383
|
/* ------------------------------------------- the server's reason wins */
|
|
383
384
|
|
|
385
|
+
/**
|
|
386
|
+
* THE PRODUCTION DEFECT.
|
|
387
|
+
*
|
|
388
|
+
* Every tile on the live wall drew "Not set up yet" while the identical build
|
|
389
|
+
* on staging played fine. The only difference between the two environments was
|
|
390
|
+
* a recorder entry on the API host — and the live picture never touches a
|
|
391
|
+
* recorder: the tile embeds the relay's own player page. The still-frame
|
|
392
|
+
* refusal was being used as a gate on live video, so a picture that was
|
|
393
|
+
* available the whole time was never asked for.
|
|
394
|
+
*/
|
|
395
|
+
test("a still-frame refusal does not black out a camera that CAN show live video", () => {
|
|
396
|
+
const noRecorder = "No recorder is configured for this camera's relay.";
|
|
397
|
+
const cam = ip({
|
|
398
|
+
unavailableReason: noRecorder,
|
|
399
|
+
capabilities: { liveVideo: { state: "supported" } },
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
const v = tileView(cam, { player: "page-up" });
|
|
403
|
+
assert.equal(v.showPlayer, true);
|
|
404
|
+
assert.equal(v.state, "unverified");
|
|
405
|
+
// No scrim across the picture: `detail` is drawn over the whole tile.
|
|
406
|
+
assert.equal(v.detail, null);
|
|
407
|
+
// The tile still says what is missing, on the note, so a moving picture over
|
|
408
|
+
// "Recorder not checked" does not read as a contradiction.
|
|
409
|
+
assert.match(v.note, /no recorder is set up/i);
|
|
410
|
+
assert.match(v.note, /cannot confirm the picture is arriving/);
|
|
411
|
+
|
|
412
|
+
// And a confirmed frame is still just Live, with the caveat kept.
|
|
413
|
+
const playing = tileView(cam, { player: "playing" });
|
|
414
|
+
assert.equal(playing.label, "Live");
|
|
415
|
+
assert.equal(playing.showPlayer, true);
|
|
416
|
+
assert.match(playing.note, /straight from the video relay/i);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test("a camera that genuinely cannot show live video still says so, with the server's reason", () => {
|
|
420
|
+
const noRecorder = "No recorder is configured for this camera's relay.";
|
|
421
|
+
|
|
422
|
+
// Explicitly unsupported: the honest dead tile, unchanged.
|
|
423
|
+
const off = tileView(
|
|
424
|
+
ip({
|
|
425
|
+
unavailableReason: noRecorder,
|
|
426
|
+
capabilities: { liveVideo: { state: "unsupported", detail: "This camera is not active." } },
|
|
427
|
+
})
|
|
428
|
+
);
|
|
429
|
+
assert.equal(off.showPlayer, false);
|
|
430
|
+
assert.equal(off.state, "unavailable");
|
|
431
|
+
assert.equal(off.detail, noRecorder);
|
|
432
|
+
|
|
433
|
+
// Unknown is not a licence to try: a server that never said "supported" is
|
|
434
|
+
// still refused, exactly as before.
|
|
435
|
+
const unknown = tileView(ip({ unavailableReason: noRecorder, capabilities: {} }));
|
|
436
|
+
assert.equal(unknown.showPlayer, false);
|
|
437
|
+
assert.equal(unknown.detail, noRecorder);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
test("the staging case is untouched: no refusal, no caveat", () => {
|
|
441
|
+
const cam = ip({ capabilities: { liveVideo: { state: "supported" } } });
|
|
442
|
+
assert.equal(tileView(cam, { player: "playing" }).note, null);
|
|
443
|
+
assert.equal(tileView(cam, { player: "page-up" }).note, UNVERIFIED_DETAIL);
|
|
444
|
+
assert.equal(tileView(cam, { player: "page-up" }).showPlayer, true);
|
|
445
|
+
});
|
|
446
|
+
|
|
384
447
|
test("the server's own refusal is shown verbatim, not replaced with a guess", () => {
|
|
385
448
|
// The one that matters: the browser cannot know this, and until the API host
|
|
386
449
|
// is configured it is the true answer for every camera in the estate.
|
package/utils/camera-wall.ts
CHANGED
|
@@ -664,6 +664,21 @@ export type TTileView = {
|
|
|
664
664
|
export const UNVERIFIED_DETAIL =
|
|
665
665
|
"This page cannot confirm the picture is arriving, so check the tile yourself. If it is blank or frozen, report the camera.";
|
|
666
666
|
|
|
667
|
+
/**
|
|
668
|
+
* The caveat for a tile that is showing a picture while the server is still
|
|
669
|
+
* refusing STILL frames.
|
|
670
|
+
*
|
|
671
|
+
* Without it the tile and the recorder line read as a contradiction: a moving
|
|
672
|
+
* picture above the words "Recorder not checked". They are both true, because
|
|
673
|
+
* they are answers to different questions — the picture comes from the relay's
|
|
674
|
+
* own player page, browser to relay, and never passes the recorder — so this
|
|
675
|
+
* says which is which rather than softening either one. It goes on the NOTE,
|
|
676
|
+
* never on `detail`: `detail` is drawn across the whole tile and would cover
|
|
677
|
+
* the very picture it is explaining.
|
|
678
|
+
*/
|
|
679
|
+
export const RELAY_ONLY_NOTE =
|
|
680
|
+
"The picture comes straight from the video relay, so it is live even though no recorder is set up for this camera — that is what the recorder line below is reporting. Still pictures and the recorder check need that setup.";
|
|
681
|
+
|
|
667
682
|
/**
|
|
668
683
|
* Rule 4, plus every reason a tile may have nothing to show.
|
|
669
684
|
*
|
|
@@ -711,26 +726,36 @@ export function tileView(
|
|
|
711
726
|
if (!camera) return dead("unavailable", "No camera in this position.");
|
|
712
727
|
if (!online)
|
|
713
728
|
return dead("offline", "This browser has no internet connection.");
|
|
714
|
-
// The server's refusal wins over anything reconstructed here
|
|
715
|
-
//
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
//
|
|
729
|
+
// The server's refusal wins over anything reconstructed here — but only for
|
|
730
|
+
// the question it is answering. `unavailableReason` is the STILL-FRAME
|
|
731
|
+
// refusal (`snapshotRefusalReason`), and its usual value, "no recorder is
|
|
732
|
+
// configured for this camera's relay", is about a recorder the live picture
|
|
733
|
+
// never touches: the tile embeds the relay's own player page, browser to
|
|
734
|
+
// relay, with no server in the path. So it kills the tile only when the
|
|
735
|
+
// server has also said live video itself is not supported. Otherwise a wall
|
|
736
|
+
// of black rectangles claimed a setup problem for a picture that was
|
|
737
|
+
// available the whole time — which is exactly what production showed while
|
|
738
|
+
// staging, identical code with a recorder configured, played fine.
|
|
739
|
+
const live = capabilityView(camera, "liveVideo");
|
|
719
740
|
const reason = camera.unavailableReason?.trim();
|
|
720
|
-
if (reason) return dead("unavailable", reason);
|
|
741
|
+
if (reason && live.state !== "supported") return dead("unavailable", reason);
|
|
721
742
|
if (camera.status && camera.status !== "active")
|
|
722
743
|
return dead("unavailable", "This camera is not active.");
|
|
723
744
|
if (!camera.host) return dead("unavailable", "This camera has no address configured.");
|
|
724
745
|
|
|
725
|
-
const live = capabilityView(camera, "liveVideo");
|
|
726
746
|
if (live.state === "unsupported") return dead("unavailable", live.detail);
|
|
727
747
|
|
|
728
748
|
const player = opts.player ?? "loading";
|
|
749
|
+
// Drawing a picture past an outstanding still-frame refusal owes the reader
|
|
750
|
+
// the reason it is not a contradiction. Note, not detail — see RELAY_ONLY_NOTE.
|
|
751
|
+
const caveat = reason ? RELAY_ONLY_NOTE : null;
|
|
752
|
+
const withCaveat = (note: string | null): string | null =>
|
|
753
|
+
[note, caveat].filter(Boolean).join(" ") || null;
|
|
729
754
|
const showing = (state: TTileState, detail: string | null): TTileView => ({
|
|
730
755
|
state,
|
|
731
756
|
label: TILE_LABELS[state],
|
|
732
757
|
detail,
|
|
733
|
-
note: detail,
|
|
758
|
+
note: withCaveat(detail),
|
|
734
759
|
showPlayer: true,
|
|
735
760
|
});
|
|
736
761
|
|
|
@@ -753,7 +778,7 @@ export function tileView(
|
|
|
753
778
|
case "playing":
|
|
754
779
|
return showing("live", null);
|
|
755
780
|
case "page-up":
|
|
756
|
-
return { ...showing("unverified", null), note: UNVERIFIED_DETAIL };
|
|
781
|
+
return { ...showing("unverified", null), note: withCaveat(UNVERIFIED_DETAIL) };
|
|
757
782
|
default:
|
|
758
783
|
return showing("connecting", null);
|
|
759
784
|
}
|