@adventurelabs/scout-core 1.4.84 → 1.4.85
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/dist/api_keys/actions.d.ts +2 -1
- package/dist/api_keys/actions.js +2 -2
- package/dist/helpers/herds.js +92 -13
- package/dist/helpers/index.d.ts +0 -1
- package/dist/helpers/index.js +0 -1
- package/dist/helpers/layers.d.ts +3 -0
- package/dist/helpers/layers.js +20 -0
- package/dist/helpers/parts.d.ts +7 -0
- package/dist/helpers/parts.js +32 -0
- package/dist/helpers/plans.d.ts +3 -0
- package/dist/helpers/plans.js +20 -0
- package/dist/helpers/providers.d.ts +3 -0
- package/dist/helpers/providers.js +20 -0
- package/dist/helpers/users.d.ts +1 -0
- package/dist/helpers/users.js +30 -0
- package/dist/helpers/zones.d.ts +2 -1
- package/dist/helpers/zones.js +2 -2
- package/dist/hooks/useScoutRefresh.js +24 -8
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/store/scout.d.ts +1 -0
- package/dist/store/scout.js +18 -2
- package/dist/types/db.d.ts +0 -3
- package/dist/types/herd_module.d.ts +2 -17
- package/dist/types/herd_module.js +0 -166
- package/dist/types/supabase.d.ts +6 -61
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { IApiKeyScout } from "../types/db";
|
|
2
|
+
import { SupabaseClient } from "@supabase/supabase-js";
|
|
2
3
|
export declare function test_api_key_loading(device_id: number): Promise<boolean>;
|
|
3
4
|
export declare function server_list_api_keys(device_id: number): Promise<IApiKeyScout[]>;
|
|
4
|
-
export declare function server_list_api_keys_batch(device_ids: number[]): Promise<{
|
|
5
|
+
export declare function server_list_api_keys_batch(device_ids: number[], client?: SupabaseClient): Promise<{
|
|
5
6
|
[device_id: number]: IApiKeyScout[];
|
|
6
7
|
}>;
|
package/dist/api_keys/actions.js
CHANGED
|
@@ -31,8 +31,8 @@ export async function server_list_api_keys(device_id) {
|
|
|
31
31
|
}
|
|
32
32
|
return data_to_return;
|
|
33
33
|
}
|
|
34
|
-
export async function server_list_api_keys_batch(device_ids) {
|
|
35
|
-
const supabase = await newServerClient();
|
|
34
|
+
export async function server_list_api_keys_batch(device_ids, client) {
|
|
35
|
+
const supabase = client || (await newServerClient());
|
|
36
36
|
// Check if the batch function exists by trying a simple call
|
|
37
37
|
try {
|
|
38
38
|
const { data, error } = await supabase.rpc("load_api_keys_batch", {
|
package/dist/helpers/herds.js
CHANGED
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
"use server";
|
|
2
|
+
import { createHash } from "crypto";
|
|
2
3
|
import { newServerClient } from "../supabase/server";
|
|
3
4
|
import { EnumWebResponse, IWebResponse, } from "../types/requests";
|
|
4
|
-
import {
|
|
5
|
+
import { LABELS } from "../constants/annotator";
|
|
6
|
+
import { get_devices_by_herd } from "./devices";
|
|
7
|
+
import { server_get_plans_by_herd_ids } from "./plans";
|
|
8
|
+
import { server_get_layers_by_herd_ids } from "./layers";
|
|
9
|
+
import { server_get_providers_by_herd_ids } from "./providers";
|
|
10
|
+
import { server_get_users_with_herd_access_batch } from "./users";
|
|
11
|
+
import { get_parts_by_herd_ids } from "./parts";
|
|
12
|
+
import { server_get_more_zones_and_actions_for_herd } from "./zones";
|
|
13
|
+
import { server_list_api_keys_batch } from "../api_keys/actions";
|
|
14
|
+
import { server_get_session_summaries_by_herd } from "./session_summaries";
|
|
15
|
+
import { server_get_session_usage_over_time_by_herd } from "./sessions";
|
|
5
16
|
export async function get_herds(client) {
|
|
6
17
|
const { data: herds } = await client.from("herds").select();
|
|
7
18
|
if (!herds) {
|
|
@@ -71,12 +82,17 @@ export async function createHerd(newHerd) {
|
|
|
71
82
|
return IWebResponse.success(true).to_compatible();
|
|
72
83
|
}
|
|
73
84
|
}
|
|
85
|
+
// Stable hash of modules (queries return rows in a deterministic DB order), minus
|
|
86
|
+
// the per-fetch timestamp, so the client can skip no-op updates without a deep walk.
|
|
87
|
+
function hashHerdModules(modules) {
|
|
88
|
+
const stable = modules.map(({ timestamp_last_refreshed: _ts, ...rest }) => rest);
|
|
89
|
+
return createHash("sha1").update(JSON.stringify(stable)).digest("hex");
|
|
90
|
+
}
|
|
74
91
|
export async function server_load_herd_modules() {
|
|
75
92
|
const startTime = Date.now();
|
|
76
|
-
const
|
|
77
|
-
const herdsResult = await get_herds_with_location(
|
|
78
|
-
if (herdsResult.status !== EnumWebResponse.SUCCESS ||
|
|
79
|
-
!herdsResult.data) {
|
|
93
|
+
const client = await newServerClient();
|
|
94
|
+
const herdsResult = await get_herds_with_location(client);
|
|
95
|
+
if (herdsResult.status !== EnumWebResponse.SUCCESS || !herdsResult.data) {
|
|
80
96
|
return {
|
|
81
97
|
status: EnumWebResponse.ERROR,
|
|
82
98
|
msg: herdsResult.msg ?? "Failed to load herds",
|
|
@@ -84,26 +100,88 @@ export async function server_load_herd_modules() {
|
|
|
84
100
|
time_finished: Date.now(),
|
|
85
101
|
time_sent: Date.now(),
|
|
86
102
|
server_processing_time_ms: Date.now() - startTime,
|
|
103
|
+
content_hash: "",
|
|
87
104
|
};
|
|
88
105
|
}
|
|
89
106
|
const herds = herdsResult.data.filter((h) => h.id != null);
|
|
90
|
-
const endTime = Date.now();
|
|
91
|
-
const totalLoadTime = endTime - startTime;
|
|
92
107
|
if (herds.length === 0) {
|
|
93
|
-
|
|
108
|
+
const endTime = Date.now();
|
|
109
|
+
console.log(`[server_load_herd_modules] No accessible herds (${endTime - startTime}ms)`);
|
|
94
110
|
return {
|
|
95
111
|
status: EnumWebResponse.SUCCESS,
|
|
96
112
|
msg: "No herds accessible",
|
|
97
113
|
data: [],
|
|
98
114
|
time_finished: endTime,
|
|
99
115
|
time_sent: endTime,
|
|
100
|
-
server_processing_time_ms:
|
|
116
|
+
server_processing_time_ms: endTime - startTime,
|
|
117
|
+
content_hash: hashHerdModules([]),
|
|
101
118
|
};
|
|
102
119
|
}
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
120
|
+
const herdIds = herds.map((h) => h.id);
|
|
121
|
+
// Run a per-herd RPC across all herds in parallel, tolerating individual failures.
|
|
122
|
+
const perHerd = (fn) => Promise.all(herds.map((h) => fn(h.id).catch(() => ({
|
|
123
|
+
status: EnumWebResponse.ERROR,
|
|
124
|
+
msg: "",
|
|
125
|
+
data: null,
|
|
126
|
+
}))));
|
|
127
|
+
const grouped = (p) => p.then((r) => r.data ?? {}).catch(() => ({}));
|
|
128
|
+
const [plansByHerd, layersByHerd, providersByHerd, usersByHerd, partsByDevice, devicesResults, zonesResults, summariesResults, usageResults,] = await Promise.all([
|
|
129
|
+
grouped(server_get_plans_by_herd_ids(herdIds, client)),
|
|
130
|
+
grouped(server_get_layers_by_herd_ids(herdIds, client)),
|
|
131
|
+
grouped(server_get_providers_by_herd_ids(herdIds, client)),
|
|
132
|
+
server_get_users_with_herd_access_batch(herdIds, client)
|
|
133
|
+
.then((r) => (r.status === EnumWebResponse.SUCCESS ? r.data : null))
|
|
134
|
+
.catch(() => null),
|
|
135
|
+
grouped(get_parts_by_herd_ids(client, herdIds)),
|
|
136
|
+
perHerd((id) => get_devices_by_herd(id, client)),
|
|
137
|
+
perHerd((id) => server_get_more_zones_and_actions_for_herd(id, 0, 10, client)),
|
|
138
|
+
perHerd((id) => server_get_session_summaries_by_herd(id, client)),
|
|
139
|
+
perHerd((id) => server_get_session_usage_over_time_by_herd(id, client)),
|
|
140
|
+
]);
|
|
141
|
+
const devicesByHerdIndex = devicesResults.map((res) => res.status === EnumWebResponse.SUCCESS && res.data ? res.data : []);
|
|
142
|
+
const allDevices = devicesByHerdIndex.flat();
|
|
143
|
+
const allDeviceIds = allDevices.map((device) => device.id ?? 0);
|
|
144
|
+
let apiKeysByDevice = {};
|
|
145
|
+
if (allDeviceIds.length > 0) {
|
|
146
|
+
try {
|
|
147
|
+
apiKeysByDevice = await server_list_api_keys_batch(allDeviceIds, client);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
console.error(`[server_load_herd_modules] Failed to load API keys:`, error);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const device of allDevices) {
|
|
154
|
+
const deviceId = device.id ?? 0;
|
|
155
|
+
device.api_keys_scout = apiKeysByDevice[deviceId] || [];
|
|
156
|
+
device.parts = partsByDevice[deviceId] || [];
|
|
157
|
+
}
|
|
158
|
+
const serialized_herd_modules = herds.map((herd, index) => {
|
|
159
|
+
const zonesRes = zonesResults[index];
|
|
160
|
+
const summariesRes = summariesResults[index];
|
|
161
|
+
const usageRes = usageResults[index];
|
|
162
|
+
return {
|
|
163
|
+
herd,
|
|
164
|
+
devices: devicesByHerdIndex[index],
|
|
165
|
+
timestamp_last_refreshed: Date.now(),
|
|
166
|
+
user_roles: usersByHerd ? usersByHerd[herd.id] ?? [] : null,
|
|
167
|
+
labels: LABELS,
|
|
168
|
+
plans: plansByHerd[herd.id] ?? [],
|
|
169
|
+
zones: zonesRes.status === EnumWebResponse.SUCCESS && zonesRes.data
|
|
170
|
+
? zonesRes.data
|
|
171
|
+
: [],
|
|
172
|
+
layers: layersByHerd[herd.id] ?? [],
|
|
173
|
+
providers: providersByHerd[herd.id] ?? [],
|
|
174
|
+
session_summaries: summariesRes.status === EnumWebResponse.SUCCESS && summariesRes.data
|
|
175
|
+
? summariesRes.data
|
|
176
|
+
: null,
|
|
177
|
+
session_usage: usageRes.status === EnumWebResponse.SUCCESS && usageRes.data
|
|
178
|
+
? usageRes.data
|
|
179
|
+
: null,
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
const endTime = Date.now();
|
|
183
|
+
const totalLoadTime = endTime - startTime;
|
|
184
|
+
console.log(`[server_load_herd_modules] Loaded ${herds.length} herds in ${totalLoadTime}ms (batched queries)`);
|
|
107
185
|
return {
|
|
108
186
|
status: EnumWebResponse.SUCCESS,
|
|
109
187
|
msg: "Herd modules loaded successfully",
|
|
@@ -111,5 +189,6 @@ export async function server_load_herd_modules() {
|
|
|
111
189
|
time_finished: endTime,
|
|
112
190
|
time_sent: endTime,
|
|
113
191
|
server_processing_time_ms: totalLoadTime,
|
|
192
|
+
content_hash: hashHerdModules(serialized_herd_modules),
|
|
114
193
|
};
|
|
115
194
|
}
|
package/dist/helpers/index.d.ts
CHANGED
package/dist/helpers/index.js
CHANGED
package/dist/helpers/layers.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Database } from "../types/supabase";
|
|
1
2
|
import { ILayer } from "../types/db";
|
|
2
3
|
import { IWebResponseCompatible } from "../types/requests";
|
|
4
|
+
import { SupabaseClient } from "@supabase/supabase-js";
|
|
3
5
|
export declare function server_get_layers_by_herd(herd_id: number): Promise<IWebResponseCompatible<ILayer[]>>;
|
|
6
|
+
export declare function server_get_layers_by_herd_ids(herd_ids: number[], client: SupabaseClient<Database>): Promise<IWebResponseCompatible<Record<number, ILayer[]>>>;
|
package/dist/helpers/layers.js
CHANGED
|
@@ -19,3 +19,23 @@ export async function server_get_layers_by_herd(herd_id) {
|
|
|
19
19
|
return IWebResponse.success(data).to_compatible();
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
// Fetches layers for many herds in one query, grouped by herd_id.
|
|
23
|
+
export async function server_get_layers_by_herd_ids(herd_ids, client) {
|
|
24
|
+
var _a;
|
|
25
|
+
if (herd_ids.length === 0) {
|
|
26
|
+
return IWebResponse.success({}).to_compatible();
|
|
27
|
+
}
|
|
28
|
+
const { data, error } = await client
|
|
29
|
+
.from("layers")
|
|
30
|
+
.select("*")
|
|
31
|
+
.in("herd_id", herd_ids)
|
|
32
|
+
.order("id", { ascending: true });
|
|
33
|
+
if (error) {
|
|
34
|
+
return { status: EnumWebResponse.ERROR, msg: error.message, data: null };
|
|
35
|
+
}
|
|
36
|
+
const grouped = {};
|
|
37
|
+
for (const row of data ?? []) {
|
|
38
|
+
(grouped[_a = row.herd_id] ?? (grouped[_a] = [])).push(row);
|
|
39
|
+
}
|
|
40
|
+
return IWebResponse.success(grouped).to_compatible();
|
|
41
|
+
}
|
package/dist/helpers/parts.d.ts
CHANGED
|
@@ -75,6 +75,13 @@ export declare function get_parts_by_certificate_id(client: SupabaseClient<Datab
|
|
|
75
75
|
* @param herd_id - ID of the herd to get parts for
|
|
76
76
|
*/
|
|
77
77
|
export declare function get_parts_by_herd_id(client: SupabaseClient<Database>, herd_id: number): Promise<IWebResponseCompatible<IPart[]>>;
|
|
78
|
+
/**
|
|
79
|
+
* Retrieves active parts for all devices across many herds in one query,
|
|
80
|
+
* grouped by device_id.
|
|
81
|
+
* @param client - Supabase client instance
|
|
82
|
+
* @param herd_ids - IDs of the herds to get parts for
|
|
83
|
+
*/
|
|
84
|
+
export declare function get_parts_by_herd_ids(client: SupabaseClient<Database>, herd_ids: number[]): Promise<IWebResponseCompatible<Record<number, IPart[]>>>;
|
|
78
85
|
/**
|
|
79
86
|
* Restores a deleted part to active lifecycle
|
|
80
87
|
* @param client - Supabase client instance
|
package/dist/helpers/parts.js
CHANGED
|
@@ -238,6 +238,38 @@ export async function get_parts_by_herd_id(client, herd_id) {
|
|
|
238
238
|
}
|
|
239
239
|
return IWebResponse.success(data).to_compatible();
|
|
240
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Retrieves active parts for all devices across many herds in one query,
|
|
243
|
+
* grouped by device_id.
|
|
244
|
+
* @param client - Supabase client instance
|
|
245
|
+
* @param herd_ids - IDs of the herds to get parts for
|
|
246
|
+
*/
|
|
247
|
+
export async function get_parts_by_herd_ids(client, herd_ids) {
|
|
248
|
+
var _a;
|
|
249
|
+
if (herd_ids.length === 0) {
|
|
250
|
+
return IWebResponse.success({}).to_compatible();
|
|
251
|
+
}
|
|
252
|
+
const { data, error } = await client
|
|
253
|
+
.from("parts")
|
|
254
|
+
.select(`
|
|
255
|
+
*,
|
|
256
|
+
devices!parts_device_id_fkey(herd_id)
|
|
257
|
+
`)
|
|
258
|
+
.in("devices.herd_id", herd_ids)
|
|
259
|
+
.eq("lifecycle", "active")
|
|
260
|
+
.order("created_at", { ascending: false })
|
|
261
|
+
.order("id", { ascending: true });
|
|
262
|
+
if (error) {
|
|
263
|
+
return IWebResponse.error(error.message).to_compatible();
|
|
264
|
+
}
|
|
265
|
+
const grouped = {};
|
|
266
|
+
for (const row of data ?? []) {
|
|
267
|
+
if (row.device_id == null)
|
|
268
|
+
continue;
|
|
269
|
+
(grouped[_a = row.device_id] ?? (grouped[_a] = [])).push(row);
|
|
270
|
+
}
|
|
271
|
+
return IWebResponse.success(grouped).to_compatible();
|
|
272
|
+
}
|
|
241
273
|
/**
|
|
242
274
|
* Restores a deleted part to active lifecycle
|
|
243
275
|
* @param client - Supabase client instance
|
package/dist/helpers/plans.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { Database } from "../types/supabase";
|
|
1
2
|
import { IPlan } from "../types/db";
|
|
2
3
|
import { IWebResponseCompatible } from "../types/requests";
|
|
4
|
+
import { SupabaseClient } from "@supabase/supabase-js";
|
|
3
5
|
export declare function server_get_plans_by_herd(herd_id: number): Promise<IWebResponseCompatible<IPlan[]>>;
|
|
6
|
+
export declare function server_get_plans_by_herd_ids(herd_ids: number[], client: SupabaseClient<Database>): Promise<IWebResponseCompatible<Record<number, IPlan[]>>>;
|
|
4
7
|
export declare function server_create_plans(plans: IPlan[]): Promise<IWebResponseCompatible<IPlan[]>>;
|
|
5
8
|
export declare function server_delete_plans_by_ids(plan_ids: number[]): Promise<IWebResponseCompatible<boolean>>;
|
package/dist/helpers/plans.js
CHANGED
|
@@ -19,6 +19,26 @@ export async function server_get_plans_by_herd(herd_id) {
|
|
|
19
19
|
return IWebResponse.success(data).to_compatible();
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
// Fetches plans for many herds in one query, grouped by herd_id.
|
|
23
|
+
export async function server_get_plans_by_herd_ids(herd_ids, client) {
|
|
24
|
+
var _a;
|
|
25
|
+
if (herd_ids.length === 0) {
|
|
26
|
+
return IWebResponse.success({}).to_compatible();
|
|
27
|
+
}
|
|
28
|
+
const { data, error } = await client
|
|
29
|
+
.from("plans")
|
|
30
|
+
.select("*")
|
|
31
|
+
.in("herd_id", herd_ids)
|
|
32
|
+
.order("id", { ascending: true });
|
|
33
|
+
if (error) {
|
|
34
|
+
return { status: EnumWebResponse.ERROR, msg: error.message, data: null };
|
|
35
|
+
}
|
|
36
|
+
const grouped = {};
|
|
37
|
+
for (const row of data ?? []) {
|
|
38
|
+
(grouped[_a = row.herd_id] ?? (grouped[_a] = [])).push(row);
|
|
39
|
+
}
|
|
40
|
+
return IWebResponse.success(grouped).to_compatible();
|
|
41
|
+
}
|
|
22
42
|
// function that uploads plan to our db
|
|
23
43
|
export async function server_create_plans(plans) {
|
|
24
44
|
// loop through plans and format
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { Database } from "../types/supabase";
|
|
1
2
|
import { IProvider } from "../types/db";
|
|
2
3
|
import { IWebResponseCompatible } from "../types/requests";
|
|
4
|
+
import { SupabaseClient } from "@supabase/supabase-js";
|
|
3
5
|
export declare function server_get_providers_by_herd(herd_id: number): Promise<IWebResponseCompatible<IProvider[]>>;
|
|
6
|
+
export declare function server_get_providers_by_herd_ids(herd_ids: number[], client: SupabaseClient<Database>): Promise<IWebResponseCompatible<Record<number, IProvider[]>>>;
|
|
4
7
|
export declare function server_create_provider(provider: Omit<IProvider, "id" | "created_at">): Promise<IWebResponseCompatible<IProvider>>;
|
|
5
8
|
export declare function server_update_provider(provider_id: number, updates: Partial<Omit<IProvider, "id" | "created_at">>): Promise<IWebResponseCompatible<IProvider>>;
|
|
6
9
|
export declare function server_delete_providers_by_ids(provider_ids: number[]): Promise<IWebResponseCompatible<boolean>>;
|
|
@@ -19,6 +19,26 @@ export async function server_get_providers_by_herd(herd_id) {
|
|
|
19
19
|
return IWebResponse.success(data).to_compatible();
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
// Fetches providers for many herds in one query, grouped by herd_id.
|
|
23
|
+
export async function server_get_providers_by_herd_ids(herd_ids, client) {
|
|
24
|
+
var _a;
|
|
25
|
+
if (herd_ids.length === 0) {
|
|
26
|
+
return IWebResponse.success({}).to_compatible();
|
|
27
|
+
}
|
|
28
|
+
const { data, error } = await client
|
|
29
|
+
.from("providers")
|
|
30
|
+
.select("*")
|
|
31
|
+
.in("herd_id", herd_ids)
|
|
32
|
+
.order("id", { ascending: true });
|
|
33
|
+
if (error) {
|
|
34
|
+
return { status: EnumWebResponse.ERROR, msg: error.message, data: null };
|
|
35
|
+
}
|
|
36
|
+
const grouped = {};
|
|
37
|
+
for (const row of data ?? []) {
|
|
38
|
+
(grouped[_a = row.herd_id] ?? (grouped[_a] = [])).push(row);
|
|
39
|
+
}
|
|
40
|
+
return IWebResponse.success(grouped).to_compatible();
|
|
41
|
+
}
|
|
22
42
|
// function that creates a new provider in our db
|
|
23
43
|
export async function server_create_provider(provider) {
|
|
24
44
|
const supabase = await newServerClient();
|
package/dist/helpers/users.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Database } from "../types/supabase";
|
|
|
5
5
|
export declare function server_get_user_roles(herd_id: number): Promise<IWebResponseCompatible<IUserRolePerHerd[]>>;
|
|
6
6
|
export declare function server_get_user(): Promise<IWebResponseCompatible<IUser | null>>;
|
|
7
7
|
export declare function server_get_users_with_herd_access(herd_id: number, supabaseClient?: SupabaseClient): Promise<IWebResponseCompatible<IUserAndRole[]>>;
|
|
8
|
+
export declare function server_get_users_with_herd_access_batch(herd_ids: number[], client: SupabaseClient): Promise<IWebResponseCompatible<Record<number, IUserAndRole[]>>>;
|
|
8
9
|
export declare function server_upsert_user_herd_role(herd_id: number, username: string, herd_role_id: number): Promise<IWebResponseCompatible<IUserAndRole | null>>;
|
|
9
10
|
export type UserProfileUpdate = Database["public"]["Tables"]["users"]["Update"];
|
|
10
11
|
export declare function update_user_profile(client: SupabaseClient<Database>, user_id: string, patch: UserProfileUpdate): Promise<IWebResponseCompatible<IUserProfileRow | null>>;
|
package/dist/helpers/users.js
CHANGED
|
@@ -49,6 +49,36 @@ export async function server_get_users_with_herd_access(herd_id, supabaseClient)
|
|
|
49
49
|
}));
|
|
50
50
|
return IWebResponse.success(transformedData).to_compatible();
|
|
51
51
|
}
|
|
52
|
+
// Fetches users-with-role for many herds in one query, grouped by herd_id.
|
|
53
|
+
export async function server_get_users_with_herd_access_batch(herd_ids, client) {
|
|
54
|
+
var _a;
|
|
55
|
+
if (herd_ids.length === 0) {
|
|
56
|
+
return IWebResponse.success({}).to_compatible();
|
|
57
|
+
}
|
|
58
|
+
const { data, error } = await client
|
|
59
|
+
.from("users_roles_per_herd")
|
|
60
|
+
.select(`
|
|
61
|
+
herd_id,
|
|
62
|
+
herd_role_id,
|
|
63
|
+
herd_roles (*),
|
|
64
|
+
users!users_roles_per_herd_user_id_fkey (*)
|
|
65
|
+
`)
|
|
66
|
+
.in("herd_id", herd_ids)
|
|
67
|
+
.order("herd_id", { ascending: true })
|
|
68
|
+
.order("user_id", { ascending: true });
|
|
69
|
+
if (error) {
|
|
70
|
+
return { status: EnumWebResponse.ERROR, msg: error.message, data: null };
|
|
71
|
+
}
|
|
72
|
+
const grouped = {};
|
|
73
|
+
for (const item of (data ?? [])) {
|
|
74
|
+
(grouped[_a = item.herd_id] ?? (grouped[_a] = [])).push({
|
|
75
|
+
user: item.users,
|
|
76
|
+
herd_role_id: item.herd_role_id,
|
|
77
|
+
herd_role: item.herd_roles,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return IWebResponse.success(grouped).to_compatible();
|
|
81
|
+
}
|
|
52
82
|
export async function server_upsert_user_herd_role(herd_id, username, herd_role_id) {
|
|
53
83
|
const supabase = await newServerClient();
|
|
54
84
|
const { data: user, error: user_error } = await supabase
|
package/dist/helpers/zones.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IZoneWithActions } from "../types/db";
|
|
2
2
|
import { IWebResponseCompatible } from "../types/requests";
|
|
3
|
+
import { SupabaseClient } from "@supabase/supabase-js";
|
|
3
4
|
/**
|
|
4
5
|
* Get more zones and actions for a herd
|
|
5
6
|
* @param herd_id - The ID of the herd to get zones and actions for
|
|
@@ -8,7 +9,7 @@ import { IWebResponseCompatible } from "../types/requests";
|
|
|
8
9
|
* @returns A list of zones and actions
|
|
9
10
|
* @throws An error if the zones or actions are not fetched
|
|
10
11
|
*/
|
|
11
|
-
export declare function server_get_more_zones_and_actions_for_herd(herd_id: number, offset: number, page_count?: number): Promise<IWebResponseCompatible<IZoneWithActions[]>>;
|
|
12
|
+
export declare function server_get_more_zones_and_actions_for_herd(herd_id: number, offset: number, page_count?: number, client?: SupabaseClient): Promise<IWebResponseCompatible<IZoneWithActions[]>>;
|
|
12
13
|
/**
|
|
13
14
|
* Create zones and actions for a herd
|
|
14
15
|
* @param zones - The zones to create
|
package/dist/helpers/zones.js
CHANGED
|
@@ -8,10 +8,10 @@ import { EnumWebResponse, IWebResponse, } from "../types/requests";
|
|
|
8
8
|
* @returns A list of zones and actions
|
|
9
9
|
* @throws An error if the zones or actions are not fetched
|
|
10
10
|
*/
|
|
11
|
-
export async function server_get_more_zones_and_actions_for_herd(herd_id, offset, page_count = 10) {
|
|
11
|
+
export async function server_get_more_zones_and_actions_for_herd(herd_id, offset, page_count = 10, client) {
|
|
12
12
|
const from = offset * page_count;
|
|
13
13
|
const to = from + page_count - 1;
|
|
14
|
-
const supabase = await newServerClient();
|
|
14
|
+
const supabase = client || (await newServerClient());
|
|
15
15
|
// make rpc call to get_events_with_tags_for_herd(herd_id, offset, limit)
|
|
16
16
|
const { data, error } = await supabase.rpc("get_zones_and_actions_for_herd", {
|
|
17
17
|
herd_id_caller: herd_id,
|
|
@@ -136,6 +136,7 @@ export function useScoutRefresh(options = {}) {
|
|
|
136
136
|
dispatch(setStatus(EnumScoutStateStatus.LOADING));
|
|
137
137
|
dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.LOADING));
|
|
138
138
|
let cachedHerdModules = null;
|
|
139
|
+
let cachedContentHash;
|
|
139
140
|
let cacheLoadDuration = 0;
|
|
140
141
|
// Step 1: Load from cache first if enabled
|
|
141
142
|
if (cacheFirst) {
|
|
@@ -147,6 +148,7 @@ export function useScoutRefresh(options = {}) {
|
|
|
147
148
|
dispatch(setCacheLoadDuration(cacheLoadDuration));
|
|
148
149
|
if (cacheResult.data && cacheResult.data.length > 0) {
|
|
149
150
|
cachedHerdModules = cacheResult.data;
|
|
151
|
+
cachedContentHash = cacheResult.metadata?.etag;
|
|
150
152
|
console.log(`[useScoutRefresh] Loaded ${cachedHerdModules.length} herd modules from cache in ${cacheLoadDuration}ms (age: ${Math.round(cacheResult.age / 1000)}s, stale: ${cacheResult.isStale})`);
|
|
151
153
|
// Set data source to CACHE initially
|
|
152
154
|
dispatch(setDataSource(EnumDataSource.CACHE));
|
|
@@ -158,7 +160,10 @@ export function useScoutRefresh(options = {}) {
|
|
|
158
160
|
}));
|
|
159
161
|
// Update the store with cached data
|
|
160
162
|
console.log(`[useScoutRefresh] Updating store with cached herd modules`);
|
|
161
|
-
dispatchInTransition(dispatch, setHerdModules(
|
|
163
|
+
dispatchInTransition(dispatch, setHerdModules({
|
|
164
|
+
modules: cachedHerdModules,
|
|
165
|
+
contentHash: cachedContentHash,
|
|
166
|
+
}));
|
|
162
167
|
dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED));
|
|
163
168
|
// If cache is fresh, we still background fetch but don't wait (only when online)
|
|
164
169
|
if (!cacheResult.isStale) {
|
|
@@ -193,21 +198,25 @@ export function useScoutRefresh(options = {}) {
|
|
|
193
198
|
EnumWebResponse.SUCCESS &&
|
|
194
199
|
Array.isArray(backgroundHerdModulesResult.data) &&
|
|
195
200
|
backgroundUserResult) {
|
|
201
|
+
const backgroundContentHash = backgroundHerdModulesResult.content_hash;
|
|
196
202
|
// Update cache with fresh data
|
|
197
203
|
try {
|
|
198
|
-
await scoutCache.setHerdModules(backgroundHerdModulesResult.data, cacheTtlMs);
|
|
204
|
+
await scoutCache.setHerdModules(backgroundHerdModulesResult.data, cacheTtlMs, backgroundContentHash);
|
|
199
205
|
}
|
|
200
206
|
catch (cacheError) {
|
|
201
207
|
console.warn("[useScoutRefresh] Background cache save failed:", cacheError);
|
|
202
208
|
await handleIndexedDbError(cacheError, "background cache save", async () => {
|
|
203
209
|
if (backgroundHerdModulesResult.data) {
|
|
204
|
-
await scoutCache.setHerdModules(backgroundHerdModulesResult.data, cacheTtlMs);
|
|
210
|
+
await scoutCache.setHerdModules(backgroundHerdModulesResult.data, cacheTtlMs, backgroundContentHash);
|
|
205
211
|
}
|
|
206
212
|
});
|
|
207
213
|
}
|
|
208
214
|
// Update store with fresh data from background request
|
|
209
215
|
console.log(`[useScoutRefresh] Updating store with background herd modules`);
|
|
210
|
-
dispatchInTransition(dispatch, setHerdModules(
|
|
216
|
+
dispatchInTransition(dispatch, setHerdModules({
|
|
217
|
+
modules: backgroundHerdModulesResult.data,
|
|
218
|
+
contentHash: backgroundContentHash,
|
|
219
|
+
}));
|
|
211
220
|
// Update data source to DATABASE
|
|
212
221
|
dispatch(setDataSource(EnumDataSource.DATABASE));
|
|
213
222
|
dispatch(setDataSourceInfo({
|
|
@@ -248,7 +257,10 @@ export function useScoutRefresh(options = {}) {
|
|
|
248
257
|
source: EnumDataSource.CACHE,
|
|
249
258
|
timestamp: Date.now(),
|
|
250
259
|
}));
|
|
251
|
-
dispatchInTransition(dispatch, setHerdModules(
|
|
260
|
+
dispatchInTransition(dispatch, setHerdModules({
|
|
261
|
+
modules: cachedHerdModules,
|
|
262
|
+
contentHash: cachedContentHash,
|
|
263
|
+
}));
|
|
252
264
|
dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED));
|
|
253
265
|
}
|
|
254
266
|
else {
|
|
@@ -310,6 +322,7 @@ export function useScoutRefresh(options = {}) {
|
|
|
310
322
|
console.log(`[useScoutRefresh] Data validation took: ${validationDuration}ms`);
|
|
311
323
|
// Use the validated data
|
|
312
324
|
const compatible_new_herd_modules = herdModulesResponse.data;
|
|
325
|
+
const freshContentHash = herdModulesResponse.content_hash;
|
|
313
326
|
// Set data source to DATABASE
|
|
314
327
|
dispatch(setDataSource(EnumDataSource.DATABASE));
|
|
315
328
|
dispatch(setDataSourceInfo({
|
|
@@ -319,7 +332,7 @@ export function useScoutRefresh(options = {}) {
|
|
|
319
332
|
// Step 3: Update cache with fresh data
|
|
320
333
|
const cacheSaveStartTime = Date.now();
|
|
321
334
|
try {
|
|
322
|
-
await scoutCache.setHerdModules(compatible_new_herd_modules, cacheTtlMs);
|
|
335
|
+
await scoutCache.setHerdModules(compatible_new_herd_modules, cacheTtlMs, freshContentHash);
|
|
323
336
|
const cacheSaveDuration = Date.now() - cacheSaveStartTime;
|
|
324
337
|
timingRefs.current.cacheSaveDuration = cacheSaveDuration;
|
|
325
338
|
console.log(`[useScoutRefresh] Cache updated in ${cacheSaveDuration}ms with TTL: ${Math.round(cacheTtlMs / 1000)}s`);
|
|
@@ -327,14 +340,17 @@ export function useScoutRefresh(options = {}) {
|
|
|
327
340
|
catch (cacheError) {
|
|
328
341
|
console.warn("[useScoutRefresh] Cache save failed:", cacheError);
|
|
329
342
|
await handleIndexedDbError(cacheError, "cache save", async () => {
|
|
330
|
-
await scoutCache.setHerdModules(compatible_new_herd_modules, cacheTtlMs);
|
|
343
|
+
await scoutCache.setHerdModules(compatible_new_herd_modules, cacheTtlMs, freshContentHash);
|
|
331
344
|
});
|
|
332
345
|
}
|
|
333
346
|
// Step 4: Update store with fresh data (reducer skips no-op updates)
|
|
334
347
|
const dataProcessingStartTime = Date.now();
|
|
335
348
|
// Update store with new data
|
|
336
349
|
console.log(`[useScoutRefresh] Updating store with fresh herd modules`);
|
|
337
|
-
dispatchInTransition(dispatch, setHerdModules(
|
|
350
|
+
dispatchInTransition(dispatch, setHerdModules({
|
|
351
|
+
modules: compatible_new_herd_modules,
|
|
352
|
+
contentHash: freshContentHash,
|
|
353
|
+
}));
|
|
338
354
|
dispatch(setHerdModulesLoadingState(EnumHerdModulesLoadingState.SUCCESSFULLY_LOADED));
|
|
339
355
|
const dataProcessingDuration = Date.now() - dataProcessingStartTime;
|
|
340
356
|
timingRefs.current.dataProcessingDuration = dataProcessingDuration;
|
package/dist/index.d.ts
CHANGED
|
@@ -20,7 +20,8 @@ export * from "./helpers/bounding_boxes";
|
|
|
20
20
|
export * from "./helpers/chat";
|
|
21
21
|
export * from "./helpers/certificates";
|
|
22
22
|
export * from "./helpers/connectivity";
|
|
23
|
-
export * from "./helpers/
|
|
23
|
+
export * from "./helpers/user_credentials";
|
|
24
|
+
export * from "./helpers/herd_credentials";
|
|
24
25
|
export * from "./helpers/db";
|
|
25
26
|
export * from "./helpers/devices";
|
|
26
27
|
export * from "./helpers/email";
|
|
@@ -82,6 +83,6 @@ export * from "./store/api";
|
|
|
82
83
|
export * from "./supabase/middleware";
|
|
83
84
|
export * from "./supabase/server";
|
|
84
85
|
export * from "./api_keys/actions";
|
|
85
|
-
export type {
|
|
86
|
-
export type { IDevice, IEvent, IUser, IHerd, IHerdPrettyLocation, IEventWithTags, IZoneWithActions,
|
|
86
|
+
export type { IHerdModule } from "./types/herd_module";
|
|
87
|
+
export type { IDevice, IEvent, IUser, IHerd, IHerdPrettyLocation, IEventWithTags, IZoneWithActions, IPointsForUser, PointsForUserInsert, PointsForUserUpdate, ICertificate, CertificateInsert, CertificateUpdate, IUserAndRole, IUserProfileRow, IHerdInvitation, IHerdInvitationWithHerdSlug, IHerdAllowedDomain, IUserRolePerHerd, IAbility, IHerdRole, IHerdRoleWithAbilities, HerdRoleSystemName, HerdInvitationStatus, IApiKeyScout, ILayer, IHeartbeat, IProvider, IConnectivity, ISession, ISessionWithCoordinates, IConnectivityWithCoordinates, IObservation, ObservationInsert, ObservationUpdate, IAnalysisJob, IAnalysisTask, AnalysisWorkStatus, } from "./types/db";
|
|
87
88
|
export { EnumSessionsVisibility } from "./types/events";
|
package/dist/index.js
CHANGED
|
@@ -23,7 +23,8 @@ export * from "./helpers/bounding_boxes";
|
|
|
23
23
|
export * from "./helpers/chat";
|
|
24
24
|
export * from "./helpers/certificates";
|
|
25
25
|
export * from "./helpers/connectivity";
|
|
26
|
-
export * from "./helpers/
|
|
26
|
+
export * from "./helpers/user_credentials";
|
|
27
|
+
export * from "./helpers/herd_credentials";
|
|
27
28
|
export * from "./helpers/db";
|
|
28
29
|
export * from "./helpers/devices";
|
|
29
30
|
export * from "./helpers/email";
|
package/dist/store/scout.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export interface LoadingPerformance {
|
|
|
17
17
|
}
|
|
18
18
|
export interface ScoutState {
|
|
19
19
|
herd_modules: IHerdModule[];
|
|
20
|
+
herd_modules_content_hash: string | null;
|
|
20
21
|
status: EnumScoutStateStatus;
|
|
21
22
|
herd_modules_loading_state: EnumHerdModulesLoadingState;
|
|
22
23
|
loading_performance: LoadingPerformance;
|
package/dist/store/scout.js
CHANGED
|
@@ -9,6 +9,7 @@ export var EnumScoutStateStatus;
|
|
|
9
9
|
})(EnumScoutStateStatus || (EnumScoutStateStatus = {}));
|
|
10
10
|
const initialState = {
|
|
11
11
|
herd_modules: [],
|
|
12
|
+
herd_modules_content_hash: null,
|
|
12
13
|
status: EnumScoutStateStatus.LOADING,
|
|
13
14
|
herd_modules_loading_state: EnumHerdModulesLoadingState.NOT_LOADING,
|
|
14
15
|
loading_performance: {
|
|
@@ -33,10 +34,25 @@ export const scoutSlice = createSlice({
|
|
|
33
34
|
initialState,
|
|
34
35
|
reducers: {
|
|
35
36
|
setHerdModules: (state, action) => {
|
|
36
|
-
|
|
37
|
+
// Payload is the modules array (legacy) or { modules, contentHash }.
|
|
38
|
+
const payload = action.payload;
|
|
39
|
+
const modules = Array.isArray(payload)
|
|
40
|
+
? payload
|
|
41
|
+
: payload.modules;
|
|
42
|
+
const contentHash = Array.isArray(payload)
|
|
43
|
+
? null
|
|
44
|
+
: payload.contentHash ?? null;
|
|
45
|
+
// Prefer the cheap hash check; fall back to deep compare when absent.
|
|
46
|
+
if (contentHash != null) {
|
|
47
|
+
if (state.herd_modules_content_hash === contentHash) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else if (herdModulesSemanticallyEqual(current(state.herd_modules), modules)) {
|
|
37
52
|
return;
|
|
38
53
|
}
|
|
39
|
-
state.herd_modules =
|
|
54
|
+
state.herd_modules = modules;
|
|
55
|
+
state.herd_modules_content_hash = contentHash;
|
|
40
56
|
state.lastRefreshed = Date.now();
|
|
41
57
|
},
|
|
42
58
|
setStatus: (state, action) => {
|
package/dist/types/db.d.ts
CHANGED
|
@@ -58,7 +58,6 @@ export type IOperator = Database["public"]["Tables"]["operators"]["Row"];
|
|
|
58
58
|
export type IObservation = Database["public"]["Tables"]["observations"]["Row"];
|
|
59
59
|
export type IProvider = Database["public"]["Tables"]["providers"]["Row"];
|
|
60
60
|
export type IPart = Database["public"]["Tables"]["parts"]["Row"];
|
|
61
|
-
export type ICredential = Database["public"]["Tables"]["credentials"]["Row"];
|
|
62
61
|
export type IUserCredential = Database["public"]["Tables"]["user_credentials"]["Row"];
|
|
63
62
|
export type IHerdCredential = Database["public"]["Tables"]["herd_credentials"]["Row"];
|
|
64
63
|
export type IPointsForUser = Database["public"]["Tables"]["points_for_user"]["Row"];
|
|
@@ -102,8 +101,6 @@ export interface ISessionSummary {
|
|
|
102
101
|
}
|
|
103
102
|
export type ISessionUsageOverTime = Database["public"]["Functions"]["get_session_usage_over_time"]["Returns"];
|
|
104
103
|
export type PartInsert = Database["public"]["Tables"]["parts"]["Insert"];
|
|
105
|
-
export type CredentialInsert = Database["public"]["Tables"]["credentials"]["Insert"];
|
|
106
|
-
export type CredentialUpdate = Database["public"]["Tables"]["credentials"]["Update"];
|
|
107
104
|
export type UserCredentialInsert = Database["public"]["Tables"]["user_credentials"]["Insert"];
|
|
108
105
|
export type UserCredentialUpdate = Database["public"]["Tables"]["user_credentials"]["Update"];
|
|
109
106
|
export type HerdCredentialInsert = Database["public"]["Tables"]["herd_credentials"]["Insert"];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { SupabaseClient } from "@supabase/supabase-js";
|
|
2
1
|
import { IDevice, IHerdPrettyLocation, IPlan, ILayer, IProvider, IUserAndRole, IZoneWithActions, ISessionSummary, ISessionUsageOverTime } from "../types/db";
|
|
3
2
|
import { EnumWebResponse } from "./requests";
|
|
4
3
|
export declare enum EnumHerdModulesLoadingState {
|
|
@@ -7,22 +6,6 @@ export declare enum EnumHerdModulesLoadingState {
|
|
|
7
6
|
SUCCESSFULLY_LOADED = "SUCCESSFULLY_LOADED",
|
|
8
7
|
UNSUCCESSFULLY_LOADED = "UNSUCCESSFULLY_LOADED"
|
|
9
8
|
}
|
|
10
|
-
export declare class HerdModule {
|
|
11
|
-
herd: IHerdPrettyLocation;
|
|
12
|
-
devices: IDevice[];
|
|
13
|
-
zones: IZoneWithActions[];
|
|
14
|
-
timestamp_last_refreshed: number;
|
|
15
|
-
user_roles: IUserAndRole[] | null;
|
|
16
|
-
labels: string[];
|
|
17
|
-
plans: IPlan[];
|
|
18
|
-
layers: ILayer[];
|
|
19
|
-
providers: IProvider[];
|
|
20
|
-
session_summaries: ISessionSummary | null;
|
|
21
|
-
session_usage: ISessionUsageOverTime | null;
|
|
22
|
-
constructor(herd: IHerdPrettyLocation, devices: IDevice[], timestamp_last_refreshed: number, user_roles?: IUserAndRole[] | null, labels?: string[], plans?: IPlan[], zones?: IZoneWithActions[], layers?: ILayer[], providers?: IProvider[], session_summaries?: ISessionSummary | null, session_usage?: ISessionUsageOverTime | null);
|
|
23
|
-
to_serializable(): IHerdModule;
|
|
24
|
-
static from_herd(herd: IHerdPrettyLocation, client: SupabaseClient): Promise<HerdModule>;
|
|
25
|
-
}
|
|
26
9
|
export interface IHerdModule {
|
|
27
10
|
herd: IHerdPrettyLocation;
|
|
28
11
|
devices: IDevice[];
|
|
@@ -40,6 +23,7 @@ export interface IHerdModulesResponse {
|
|
|
40
23
|
data: IHerdModule[];
|
|
41
24
|
time_finished: number;
|
|
42
25
|
server_processing_time_ms: number;
|
|
26
|
+
content_hash: string;
|
|
43
27
|
}
|
|
44
28
|
export interface IHerdModulesResponseWithStatus {
|
|
45
29
|
status: EnumWebResponse;
|
|
@@ -48,4 +32,5 @@ export interface IHerdModulesResponseWithStatus {
|
|
|
48
32
|
time_finished: number;
|
|
49
33
|
time_sent: number;
|
|
50
34
|
server_processing_time_ms: number;
|
|
35
|
+
content_hash: string;
|
|
51
36
|
}
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
import { LABELS } from "../constants/annotator";
|
|
2
|
-
import { get_devices_by_herd } from "../helpers/devices";
|
|
3
|
-
import { server_get_plans_by_herd } from "../helpers/plans";
|
|
4
|
-
import { server_get_layers_by_herd } from "../helpers/layers";
|
|
5
|
-
import { server_get_providers_by_herd } from "../helpers/providers";
|
|
6
|
-
import { server_get_users_with_herd_access } from "../helpers/users";
|
|
7
|
-
import { get_parts_by_herd_id } from "../helpers/parts";
|
|
8
|
-
import { EnumWebResponse } from "./requests";
|
|
9
|
-
import { server_get_more_zones_and_actions_for_herd } from "../helpers/zones";
|
|
10
|
-
import { server_list_api_keys_batch } from "../api_keys/actions";
|
|
11
|
-
import { server_get_session_summaries_by_herd } from "../helpers/session_summaries";
|
|
12
|
-
import { server_get_session_usage_over_time_by_herd } from "../helpers/sessions";
|
|
13
1
|
export var EnumHerdModulesLoadingState;
|
|
14
2
|
(function (EnumHerdModulesLoadingState) {
|
|
15
3
|
EnumHerdModulesLoadingState["NOT_LOADING"] = "NOT_LOADING";
|
|
@@ -17,157 +5,3 @@ export var EnumHerdModulesLoadingState;
|
|
|
17
5
|
EnumHerdModulesLoadingState["SUCCESSFULLY_LOADED"] = "SUCCESSFULLY_LOADED";
|
|
18
6
|
EnumHerdModulesLoadingState["UNSUCCESSFULLY_LOADED"] = "UNSUCCESSFULLY_LOADED";
|
|
19
7
|
})(EnumHerdModulesLoadingState || (EnumHerdModulesLoadingState = {}));
|
|
20
|
-
export class HerdModule {
|
|
21
|
-
constructor(herd, devices, timestamp_last_refreshed, user_roles = null, labels = [], plans = [], zones = [], layers = [], providers = [], session_summaries = null, session_usage = null) {
|
|
22
|
-
this.user_roles = null;
|
|
23
|
-
this.labels = [];
|
|
24
|
-
this.plans = [];
|
|
25
|
-
this.layers = [];
|
|
26
|
-
this.providers = [];
|
|
27
|
-
this.session_summaries = null;
|
|
28
|
-
this.session_usage = null;
|
|
29
|
-
this.herd = herd;
|
|
30
|
-
this.devices = devices;
|
|
31
|
-
this.timestamp_last_refreshed = timestamp_last_refreshed;
|
|
32
|
-
this.user_roles = user_roles;
|
|
33
|
-
this.labels = labels;
|
|
34
|
-
this.plans = plans;
|
|
35
|
-
this.zones = zones;
|
|
36
|
-
this.layers = layers;
|
|
37
|
-
this.providers = providers;
|
|
38
|
-
this.session_summaries = session_summaries;
|
|
39
|
-
this.session_usage = session_usage;
|
|
40
|
-
}
|
|
41
|
-
to_serializable() {
|
|
42
|
-
return {
|
|
43
|
-
herd: this.herd,
|
|
44
|
-
devices: this.devices,
|
|
45
|
-
timestamp_last_refreshed: this.timestamp_last_refreshed,
|
|
46
|
-
user_roles: this.user_roles,
|
|
47
|
-
labels: this.labels,
|
|
48
|
-
plans: this.plans,
|
|
49
|
-
zones: this.zones,
|
|
50
|
-
layers: this.layers,
|
|
51
|
-
providers: this.providers,
|
|
52
|
-
session_summaries: this.session_summaries,
|
|
53
|
-
session_usage: this.session_usage,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
static async from_herd(herd, client) {
|
|
57
|
-
const startTime = Date.now();
|
|
58
|
-
try {
|
|
59
|
-
// Start loading herd-level data in parallel with devices
|
|
60
|
-
const herdLevelPromises = Promise.allSettled([
|
|
61
|
-
server_get_more_zones_and_actions_for_herd(herd.id, 0, 10).catch((error) => {
|
|
62
|
-
console.warn(`[HerdModule] Failed to get zones and actions:`, error);
|
|
63
|
-
return { status: EnumWebResponse.ERROR, data: null };
|
|
64
|
-
}),
|
|
65
|
-
server_get_users_with_herd_access(herd.id, client).catch((error) => {
|
|
66
|
-
console.warn(`[HerdModule] Failed to get user roles:`, error);
|
|
67
|
-
return { status: EnumWebResponse.ERROR, data: null };
|
|
68
|
-
}),
|
|
69
|
-
server_get_plans_by_herd(herd.id).catch((error) => {
|
|
70
|
-
console.warn(`[HerdModule] Failed to get plans:`, error);
|
|
71
|
-
return { status: EnumWebResponse.ERROR, data: null };
|
|
72
|
-
}),
|
|
73
|
-
server_get_layers_by_herd(herd.id).catch((error) => {
|
|
74
|
-
console.warn(`[HerdModule] Failed to get layers:`, error);
|
|
75
|
-
return { status: EnumWebResponse.ERROR, data: null };
|
|
76
|
-
}),
|
|
77
|
-
server_get_providers_by_herd(herd.id).catch((error) => {
|
|
78
|
-
console.warn(`[HerdModule] Failed to get providers:`, error);
|
|
79
|
-
return { status: EnumWebResponse.ERROR, data: null };
|
|
80
|
-
}),
|
|
81
|
-
server_get_session_summaries_by_herd(herd.id, client).catch((error) => {
|
|
82
|
-
console.warn(`[HerdModule] Failed to get session summaries:`, error);
|
|
83
|
-
return { status: EnumWebResponse.ERROR, data: null };
|
|
84
|
-
}),
|
|
85
|
-
server_get_session_usage_over_time_by_herd(herd.id, client).catch((error) => {
|
|
86
|
-
console.warn(`[HerdModule] Failed to get session usage:`, error);
|
|
87
|
-
return { status: EnumWebResponse.ERROR, data: null };
|
|
88
|
-
}),
|
|
89
|
-
]);
|
|
90
|
-
// Load devices and parts in parallel
|
|
91
|
-
const devicesPromise = get_devices_by_herd(herd.id, client);
|
|
92
|
-
const partsPromise = get_parts_by_herd_id(client, herd.id);
|
|
93
|
-
// Wait for devices, parts, and herd-level data
|
|
94
|
-
const [deviceResponse, partsResponse, herdLevelResults] = await Promise.all([devicesPromise, partsPromise, herdLevelPromises]);
|
|
95
|
-
// Check devices response
|
|
96
|
-
if (deviceResponse.status == EnumWebResponse.ERROR ||
|
|
97
|
-
!deviceResponse.data) {
|
|
98
|
-
console.warn(`[HerdModule] No devices found for herd ${herd.id}`);
|
|
99
|
-
return new HerdModule(herd, [], Date.now());
|
|
100
|
-
}
|
|
101
|
-
const new_devices = deviceResponse.data;
|
|
102
|
-
// Get parts data (optional - don't fail if parts can't be loaded)
|
|
103
|
-
let parts_data = [];
|
|
104
|
-
if (partsResponse.status !== EnumWebResponse.ERROR &&
|
|
105
|
-
partsResponse.data) {
|
|
106
|
-
parts_data = partsResponse.data;
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
console.warn(`[HerdModule] Failed to load parts for herd ${herd.id}:`, partsResponse.status);
|
|
110
|
-
}
|
|
111
|
-
// Load API keys for devices if we have any
|
|
112
|
-
if (new_devices.length > 0) {
|
|
113
|
-
try {
|
|
114
|
-
const device_ids = new_devices.map((device) => device.id ?? 0);
|
|
115
|
-
const api_keys_batch = await server_list_api_keys_batch(device_ids);
|
|
116
|
-
// Assign API keys to devices
|
|
117
|
-
for (let i = 0; i < new_devices.length; i++) {
|
|
118
|
-
const device_id = new_devices[i].id ?? 0;
|
|
119
|
-
new_devices[i].api_keys_scout = api_keys_batch[device_id] || [];
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
catch (error) {
|
|
123
|
-
console.error(`[HerdModule] Failed to load API keys:`, error);
|
|
124
|
-
// Continue without API keys
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Associate parts with devices
|
|
128
|
-
if (parts_data.length > 0) {
|
|
129
|
-
for (const device of new_devices) {
|
|
130
|
-
device.parts = parts_data.filter((part) => part.device_id === device.id);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// Extract herd-level data with safe fallbacks
|
|
134
|
-
const [res_zones, res_user_roles, res_plans, res_layers, res_providers, session_summaries_result, session_usage_result,] = herdLevelResults;
|
|
135
|
-
const zones = res_zones.status === "fulfilled" && res_zones.value?.data
|
|
136
|
-
? res_zones.value.data
|
|
137
|
-
: [];
|
|
138
|
-
const user_roles = res_user_roles.status === "fulfilled" && res_user_roles.value?.data
|
|
139
|
-
? res_user_roles.value.data
|
|
140
|
-
: null;
|
|
141
|
-
const plans = res_plans.status === "fulfilled" && res_plans.value?.data
|
|
142
|
-
? res_plans.value.data
|
|
143
|
-
: [];
|
|
144
|
-
const layers = res_layers.status === "fulfilled" && res_layers.value?.data
|
|
145
|
-
? res_layers.value.data
|
|
146
|
-
: [];
|
|
147
|
-
const providers = res_providers.status === "fulfilled" && res_providers.value?.data
|
|
148
|
-
? res_providers.value.data
|
|
149
|
-
: [];
|
|
150
|
-
const session_summaries = session_summaries_result.status === "fulfilled" &&
|
|
151
|
-
session_summaries_result.value?.data
|
|
152
|
-
? session_summaries_result.value.data
|
|
153
|
-
: null;
|
|
154
|
-
const session_usage = session_usage_result.status === "fulfilled" &&
|
|
155
|
-
session_usage_result.value?.data
|
|
156
|
-
? session_usage_result.value.data
|
|
157
|
-
: null;
|
|
158
|
-
// TODO: store in DB and retrieve on load?
|
|
159
|
-
const newLabels = LABELS;
|
|
160
|
-
const endTime = Date.now();
|
|
161
|
-
const loadTime = endTime - startTime;
|
|
162
|
-
console.log(`[HerdModule] Loaded herd ${herd.slug} in ${loadTime}ms (${new_devices.length} devices)`);
|
|
163
|
-
return new HerdModule(herd, new_devices, Date.now(), user_roles, newLabels, plans, zones, layers, providers, session_summaries, session_usage);
|
|
164
|
-
}
|
|
165
|
-
catch (error) {
|
|
166
|
-
const endTime = Date.now();
|
|
167
|
-
const loadTime = endTime - startTime;
|
|
168
|
-
console.error(`[HerdModule] Critical error in HerdModule.from_herd (${loadTime}ms):`, error);
|
|
169
|
-
// Return a minimal but valid HerdModule instance to prevent complete failure
|
|
170
|
-
return new HerdModule(herd, [], Date.now(), null, [], [], [], [], [], null, null);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
package/dist/types/supabase.d.ts
CHANGED
|
@@ -514,47 +514,6 @@ export type Database = {
|
|
|
514
514
|
}
|
|
515
515
|
];
|
|
516
516
|
};
|
|
517
|
-
credentials: {
|
|
518
|
-
Row: {
|
|
519
|
-
created_at: string;
|
|
520
|
-
expiration: string | null;
|
|
521
|
-
id: number;
|
|
522
|
-
issuer: string;
|
|
523
|
-
tracking_number: string | null;
|
|
524
|
-
type: string;
|
|
525
|
-
updated_at: string | null;
|
|
526
|
-
user_id: string;
|
|
527
|
-
};
|
|
528
|
-
Insert: {
|
|
529
|
-
created_at?: string;
|
|
530
|
-
expiration?: string | null;
|
|
531
|
-
id?: number;
|
|
532
|
-
issuer: string;
|
|
533
|
-
tracking_number?: string | null;
|
|
534
|
-
type: string;
|
|
535
|
-
updated_at?: string | null;
|
|
536
|
-
user_id: string;
|
|
537
|
-
};
|
|
538
|
-
Update: {
|
|
539
|
-
created_at?: string;
|
|
540
|
-
expiration?: string | null;
|
|
541
|
-
id?: number;
|
|
542
|
-
issuer?: string;
|
|
543
|
-
tracking_number?: string | null;
|
|
544
|
-
type?: string;
|
|
545
|
-
updated_at?: string | null;
|
|
546
|
-
user_id?: string;
|
|
547
|
-
};
|
|
548
|
-
Relationships: [
|
|
549
|
-
{
|
|
550
|
-
foreignKeyName: "credentials_user_id_fkey";
|
|
551
|
-
columns: ["user_id"];
|
|
552
|
-
isOneToOne: false;
|
|
553
|
-
referencedRelation: "users";
|
|
554
|
-
referencedColumns: ["id"];
|
|
555
|
-
}
|
|
556
|
-
];
|
|
557
|
-
};
|
|
558
517
|
devices: {
|
|
559
518
|
Row: {
|
|
560
519
|
altitude: number | null;
|
|
@@ -1126,46 +1085,31 @@ export type Database = {
|
|
|
1126
1085
|
auto_delete_media_with_humans: boolean | null;
|
|
1127
1086
|
auto_delete_media_with_no_tracks: boolean | null;
|
|
1128
1087
|
description: string;
|
|
1129
|
-
earthranger_domain: string | null;
|
|
1130
|
-
earthranger_token: string | null;
|
|
1131
1088
|
id: number;
|
|
1132
1089
|
inserted_at: string;
|
|
1133
1090
|
is_public: boolean;
|
|
1134
1091
|
location: unknown;
|
|
1135
1092
|
slug: string;
|
|
1136
|
-
video_publisher_token: string | null;
|
|
1137
|
-
video_server_url: string | null;
|
|
1138
|
-
video_subscriber_token: string | null;
|
|
1139
1093
|
};
|
|
1140
1094
|
Insert: {
|
|
1141
1095
|
auto_delete_media_with_humans?: boolean | null;
|
|
1142
1096
|
auto_delete_media_with_no_tracks?: boolean | null;
|
|
1143
1097
|
description: string;
|
|
1144
|
-
earthranger_domain?: string | null;
|
|
1145
|
-
earthranger_token?: string | null;
|
|
1146
1098
|
id?: number;
|
|
1147
1099
|
inserted_at?: string;
|
|
1148
1100
|
is_public?: boolean;
|
|
1149
1101
|
location?: unknown;
|
|
1150
1102
|
slug: string;
|
|
1151
|
-
video_publisher_token?: string | null;
|
|
1152
|
-
video_server_url?: string | null;
|
|
1153
|
-
video_subscriber_token?: string | null;
|
|
1154
1103
|
};
|
|
1155
1104
|
Update: {
|
|
1156
1105
|
auto_delete_media_with_humans?: boolean | null;
|
|
1157
1106
|
auto_delete_media_with_no_tracks?: boolean | null;
|
|
1158
1107
|
description?: string;
|
|
1159
|
-
earthranger_domain?: string | null;
|
|
1160
|
-
earthranger_token?: string | null;
|
|
1161
1108
|
id?: number;
|
|
1162
1109
|
inserted_at?: string;
|
|
1163
1110
|
is_public?: boolean;
|
|
1164
1111
|
location?: unknown;
|
|
1165
1112
|
slug?: string;
|
|
1166
|
-
video_publisher_token?: string | null;
|
|
1167
|
-
video_server_url?: string | null;
|
|
1168
|
-
video_subscriber_token?: string | null;
|
|
1169
1113
|
};
|
|
1170
1114
|
Relationships: [];
|
|
1171
1115
|
};
|
|
@@ -1640,7 +1584,9 @@ export type Database = {
|
|
|
1640
1584
|
herd_id: number;
|
|
1641
1585
|
id: number;
|
|
1642
1586
|
key: string | null;
|
|
1587
|
+
sensitivity_tier: number | null;
|
|
1643
1588
|
source: string;
|
|
1589
|
+
timestamp_expiration: string | null;
|
|
1644
1590
|
type: string;
|
|
1645
1591
|
};
|
|
1646
1592
|
Insert: {
|
|
@@ -1648,7 +1594,9 @@ export type Database = {
|
|
|
1648
1594
|
herd_id: number;
|
|
1649
1595
|
id?: number;
|
|
1650
1596
|
key?: string | null;
|
|
1597
|
+
sensitivity_tier?: number | null;
|
|
1651
1598
|
source: string;
|
|
1599
|
+
timestamp_expiration?: string | null;
|
|
1652
1600
|
type: string;
|
|
1653
1601
|
};
|
|
1654
1602
|
Update: {
|
|
@@ -1656,7 +1604,9 @@ export type Database = {
|
|
|
1656
1604
|
herd_id?: number;
|
|
1657
1605
|
id?: number;
|
|
1658
1606
|
key?: string | null;
|
|
1607
|
+
sensitivity_tier?: number | null;
|
|
1659
1608
|
source?: string;
|
|
1609
|
+
timestamp_expiration?: string | null;
|
|
1660
1610
|
type?: string;
|
|
1661
1611
|
};
|
|
1662
1612
|
Relationships: [
|
|
@@ -4014,11 +3964,6 @@ export type Database = {
|
|
|
4014
3964
|
slug: string | null;
|
|
4015
3965
|
description: string | null;
|
|
4016
3966
|
is_public: boolean | null;
|
|
4017
|
-
earthranger_domain: string | null;
|
|
4018
|
-
earthranger_token: string | null;
|
|
4019
|
-
video_publisher_token: string | null;
|
|
4020
|
-
video_subscriber_token: string | null;
|
|
4021
|
-
video_server_url: string | null;
|
|
4022
3967
|
location: string | null;
|
|
4023
3968
|
latitude: number | null;
|
|
4024
3969
|
longitude: number | null;
|