@7365admin1/layer-common 4.111.0 → 4.112.0
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
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @iservice365/layer-common
|
|
2
2
|
|
|
3
|
+
## 4.112.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9350b17: Robots: "Open HERO Fleet Core" button (`RobotFleetCoreButton`, in the site robots header) for Super Admin and Property Management. Shown only when `GET /api/robots/fleet-core/session?check=1` answers 200; on click it opens a new tab, fetches the one-hour sign-in pass and points the tab at the console (`https://…/#token=…` only). Web only.
|
|
8
|
+
|
|
3
9
|
## 4.111.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
|
@@ -3080,11 +3086,11 @@ Monthly report: a month that had patrols opens the list of that month's patrol r
|
|
|
3080
3086
|
`utils/console-tier.ts` + `composables/useConsoleTier.ts` mirror the server's
|
|
3081
3087
|
own rule from two endpoints the console already calls, unprojected:
|
|
3082
3088
|
|
|
3083
|
-
|
|
3084
|
-
|
|
3089
|
+
GET /api/members/user/:user/app/admin the Seven365 staff membership
|
|
3090
|
+
GET /api/roles/id/:role that membership's role document
|
|
3085
3091
|
|
|
3086
|
-
|
|
3087
|
-
|
|
3092
|
+
owner = member.type === "admin" && role.type === "admin" && role.default === true
|
|
3093
|
+
staff = member.type === "admin" && role.type === "admin"
|
|
3088
3094
|
|
|
3089
3095
|
`role.default` is the marker because it is the only property of a platform
|
|
3090
3096
|
staff role no API caller can set - `role.controller.ts` validates create and
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!--
|
|
3
|
+
"Open HERO Fleet Core" (web only, owner 2026-10-09: Super Admin + Property
|
|
4
|
+
Management). Shown only when the server says this account may open the
|
|
5
|
+
console (`?check=1` answered 200) - 404 where Fleet Core is not set up and
|
|
6
|
+
403 for everyone else keep it hidden. The pass is fetched on click only.
|
|
7
|
+
-->
|
|
8
|
+
<v-btn
|
|
9
|
+
v-if="available"
|
|
10
|
+
size="small"
|
|
11
|
+
variant="tonal"
|
|
12
|
+
color="primary"
|
|
13
|
+
prepend-icon="mdi-open-in-new"
|
|
14
|
+
:loading="opening"
|
|
15
|
+
@click="open"
|
|
16
|
+
>
|
|
17
|
+
Open HERO Fleet Core
|
|
18
|
+
</v-btn>
|
|
19
|
+
<span v-if="failed" class="hero-tag hero-tag--neutral" role="status">Could not open HERO Fleet Core. Try again.</span>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { onMounted, ref } from "vue";
|
|
24
|
+
import { openFleetCoreConsole } from "../../utils/robot-fleet-core";
|
|
25
|
+
|
|
26
|
+
const { getFleetCoreSession } = useRobots();
|
|
27
|
+
const available = ref(false);
|
|
28
|
+
const opening = ref(false);
|
|
29
|
+
const failed = ref(false);
|
|
30
|
+
|
|
31
|
+
onMounted(async () => {
|
|
32
|
+
try {
|
|
33
|
+
available.value = (await getFleetCoreSession({ check: true }))?.available === true;
|
|
34
|
+
} catch {
|
|
35
|
+
available.value = false;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
async function open() {
|
|
40
|
+
opening.value = true;
|
|
41
|
+
failed.value = false;
|
|
42
|
+
failed.value = !(await openFleetCoreConsole(
|
|
43
|
+
() => getFleetCoreSession(),
|
|
44
|
+
() => window.open("", "_blank") as any,
|
|
45
|
+
));
|
|
46
|
+
opening.value = false;
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
</div>
|
|
36
36
|
<span class="hero-head__spacer" />
|
|
37
37
|
<div class="hero-header__right">
|
|
38
|
+
<RobotFleetCoreButton />
|
|
38
39
|
<RobotLiveIndicator :connected="connected" />
|
|
39
40
|
<span class="hero-tag hero-tag--neutral">{{ role }}</span>
|
|
40
41
|
</div>
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
<script setup lang="ts">
|
|
46
47
|
import RobotMark from "./Mark.vue";
|
|
47
48
|
import RobotLiveIndicator from "./LiveIndicator.vue";
|
|
49
|
+
import RobotFleetCoreButton from "./FleetCoreButton.vue";
|
|
48
50
|
import { typeLabel, type TRobotType } from "../../utils/robot-display";
|
|
49
51
|
|
|
50
52
|
withDefaults(
|
package/composables/useRobots.ts
CHANGED
|
@@ -400,6 +400,18 @@ export default function useRobots() {
|
|
|
400
400
|
});
|
|
401
401
|
}
|
|
402
402
|
|
|
403
|
+
/**
|
|
404
|
+
* HERO Fleet Core sign-in pass (web only): `{ url: "<console>/#token=...", expiresAt }`.
|
|
405
|
+
* `check: true` only asks whether this account may open the console (mints nothing).
|
|
406
|
+
* 404 where Fleet Core is not set up, 403 for anyone but Super Admin / Property Management.
|
|
407
|
+
*/
|
|
408
|
+
function getFleetCoreSession({ check = false }: { check?: boolean } = {}) {
|
|
409
|
+
return useNuxtApp().$api<{ url?: string; expiresAt?: string; available?: boolean }>("/api/robots/fleet-core/session", {
|
|
410
|
+
method: "GET",
|
|
411
|
+
query: check ? { check: "1" } : {},
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
403
415
|
/* ------------------------------------------ alerts + activity log */
|
|
404
416
|
|
|
405
417
|
/**
|
|
@@ -566,6 +578,7 @@ export default function useRobots() {
|
|
|
566
578
|
listBuildingMaps,
|
|
567
579
|
listSiteBuildingMaps,
|
|
568
580
|
getFleetCoreBuilding,
|
|
581
|
+
getFleetCoreSession,
|
|
569
582
|
getSiteFaults,
|
|
570
583
|
getSiteActivity,
|
|
571
584
|
getNetworkActivity,
|
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.
|
|
5
|
+
"version": "4.112.0",
|
|
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.",
|
|
@@ -55,3 +55,41 @@ export function fleetLiftItems(map: Record<string, any> | null | undefined, rows
|
|
|
55
55
|
};
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
/* ---- "Open HERO Fleet Core" (web only, owner 2026-10-09) ---------------- */
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Is this a console URL we may open? https, and the pass in the fragment
|
|
63
|
+
* (`#token=`) - the only form the console reads, and one no server ever logs.
|
|
64
|
+
*/
|
|
65
|
+
export function isFleetCoreConsoleUrl(url: unknown): url is string {
|
|
66
|
+
if (typeof url !== "string") return false;
|
|
67
|
+
try {
|
|
68
|
+
const u = new URL(url);
|
|
69
|
+
return u.protocol === "https:" && /^#token=[\w-]+\.[\w-]+\.[\w-]+$/.test(u.hash);
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Open the console in a new tab. The tab is opened BEFORE the pass is fetched
|
|
77
|
+
* (inside the click), so popup blockers allow it; it is then pointed at the
|
|
78
|
+
* console, or closed if no pass came back. Returns false when it failed.
|
|
79
|
+
*/
|
|
80
|
+
export async function openFleetCoreConsole(
|
|
81
|
+
getSession: () => Promise<{ url?: unknown } | null | undefined>,
|
|
82
|
+
openTab: () => { location: { href: string }; opener: unknown; close: () => void } | null,
|
|
83
|
+
): Promise<boolean> {
|
|
84
|
+
const tab = openTab();
|
|
85
|
+
if (tab) tab.opener = null; // the console never gets a handle on this app
|
|
86
|
+
try {
|
|
87
|
+
const session = await getSession();
|
|
88
|
+
if (!tab || !isFleetCoreConsoleUrl(session?.url)) throw new Error("no pass");
|
|
89
|
+
tab.location.href = session!.url as string;
|
|
90
|
+
return true;
|
|
91
|
+
} catch {
|
|
92
|
+
tab?.close();
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|