@7365admin1/layer-common 4.0.3-staging.229 → 4.0.3-staging.231
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.
|
@@ -727,15 +727,15 @@ const props = defineProps({
|
|
|
727
727
|
},
|
|
728
728
|
canAddServiceProvider: {
|
|
729
729
|
type: Boolean,
|
|
730
|
-
default:
|
|
730
|
+
default: undefined,
|
|
731
731
|
},
|
|
732
732
|
canInviteServiceProvider: {
|
|
733
733
|
type: Boolean,
|
|
734
|
-
default:
|
|
734
|
+
default: undefined,
|
|
735
735
|
},
|
|
736
736
|
canViewServiceProviders: {
|
|
737
737
|
type: Boolean,
|
|
738
|
-
default:
|
|
738
|
+
default: undefined,
|
|
739
739
|
},
|
|
740
740
|
showAddButton: {
|
|
741
741
|
type: Boolean,
|
|
@@ -747,6 +747,25 @@ const props = defineProps({
|
|
|
747
747
|
},
|
|
748
748
|
});
|
|
749
749
|
|
|
750
|
+
// No app binds these three -- there was no permission catalogue key to bind
|
|
751
|
+
// them to, so they defaulted to `true` and every action was open to any
|
|
752
|
+
// signed-in user in every consumer. `useServiceProviderPermission` computes
|
|
753
|
+
// the real gate now; an app that DOES pass the prop still wins over it.
|
|
754
|
+
const {
|
|
755
|
+
canAddServiceProvider: derivedCanAddServiceProvider,
|
|
756
|
+
canInviteServiceProvider: derivedCanInviteServiceProvider,
|
|
757
|
+
canViewServiceProviders: derivedCanViewServiceProviders,
|
|
758
|
+
} = useServiceProviderPermission();
|
|
759
|
+
const canAddServiceProvider = computed(
|
|
760
|
+
() => props.canAddServiceProvider ?? derivedCanAddServiceProvider.value
|
|
761
|
+
);
|
|
762
|
+
const canInviteServiceProvider = computed(
|
|
763
|
+
() => props.canInviteServiceProvider ?? derivedCanInviteServiceProvider.value
|
|
764
|
+
);
|
|
765
|
+
const canViewServiceProviders = computed(
|
|
766
|
+
() => props.canViewServiceProviders ?? derivedCanViewServiceProviders.value
|
|
767
|
+
);
|
|
768
|
+
|
|
750
769
|
// Only property-management contributes a service-provider-mgmt/billing page;
|
|
751
770
|
// null in every other consumer, which hides the button instead of throwing.
|
|
752
771
|
const billingRouteName = pickRouteName(
|
|
@@ -221,6 +221,21 @@ export function useCommonPermissions() {
|
|
|
221
221
|
},
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
const serviceProviderPermissions: Record<string, TPermission> = {
|
|
225
|
+
"add-service-provider": {
|
|
226
|
+
check: true,
|
|
227
|
+
description: "Allows the user to add a new service provider.",
|
|
228
|
+
},
|
|
229
|
+
"invite-service-provider": {
|
|
230
|
+
check: true,
|
|
231
|
+
description: "Allows the user to invite a service provider to the organization.",
|
|
232
|
+
},
|
|
233
|
+
"see-all-service-providers": {
|
|
234
|
+
check: true,
|
|
235
|
+
description: "Allows the user to view the list of all service providers.",
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
|
|
224
239
|
const siteSettingsPermissions: Record<string, TPermission> = {
|
|
225
240
|
"manage-site-information": {
|
|
226
241
|
check: true,
|
|
@@ -291,6 +306,7 @@ export function useCommonPermissions() {
|
|
|
291
306
|
buildingManagementPermissions,
|
|
292
307
|
buildingUnitManagementPermissions,
|
|
293
308
|
bulletinBoardPermissions,
|
|
309
|
+
serviceProviderPermissions,
|
|
294
310
|
siteSettingsPermissions,
|
|
295
311
|
|
|
296
312
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { useCommonPermissions } from "./useCommonPermission";
|
|
2
|
+
|
|
3
|
+
export function useServiceProviderPermission() {
|
|
4
|
+
const { hasPermission } = usePermission();
|
|
5
|
+
const { serviceProviderPermissions } = useCommonPermissions();
|
|
6
|
+
const permissions: TPermissions = { "service-provider": serviceProviderPermissions };
|
|
7
|
+
|
|
8
|
+
const { userAppRole } = useLocalSetup();
|
|
9
|
+
|
|
10
|
+
const canAddServiceProvider = computed(() => {
|
|
11
|
+
if (!userAppRole.value) return false;
|
|
12
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
13
|
+
return hasPermission(userAppRole.value, permissions, "service-provider", "add-service-provider");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const canInviteServiceProvider = computed(() => {
|
|
17
|
+
if (!userAppRole.value) return false;
|
|
18
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
19
|
+
return hasPermission(userAppRole.value, permissions, "service-provider", "invite-service-provider");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const canViewServiceProviders = computed(() => {
|
|
23
|
+
if (!userAppRole.value) return false;
|
|
24
|
+
if (userAppRole.value.permissions.includes("*")) return true;
|
|
25
|
+
return hasPermission(userAppRole.value, permissions, "service-provider", "see-all-service-providers");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
canAddServiceProvider,
|
|
30
|
+
canInviteServiceProvider,
|
|
31
|
+
canViewServiceProviders,
|
|
32
|
+
};
|
|
33
|
+
}
|
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.231",
|
|
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
|
}
|