@camstack/system 1.2.455 → 1.2.457
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/addon-runner.js +96 -10
- package/dist/addon-runner.mjs +96 -10
- package/dist/{agent-https-CLWrOO3o.mjs → agent-https-CK9Z4sSR.mjs} +32 -5
- package/dist/{agent-https-CCvlRQKV.js → agent-https-CoyOZ3pC.js} +37 -4
- package/dist/auth/auth-manager.d.ts +9 -0
- package/dist/auth/legacy-token-tables.d.ts +16 -0
- package/dist/auth/user-management-internal.d.ts +9 -28
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +2 -2
- package/dist/builtins/alerts/alerts.addon.mjs +2 -2
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- package/dist/builtins/console-logging/console-destination.d.ts +1 -2
- package/dist/builtins/console-logging/index.js +1 -4
- package/dist/builtins/console-logging/index.mjs +1 -4
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-history/capture/capture-engine.d.ts +6 -0
- package/dist/builtins/device-history/device-history.addon.js +441 -83
- package/dist/builtins/device-history/device-history.addon.mjs +441 -83
- package/dist/builtins/device-history/query/history-query.d.ts +35 -9
- package/dist/builtins/device-history/query/point-shape.d.ts +33 -0
- package/dist/builtins/device-history/retention/retention.d.ts +1 -1
- package/dist/builtins/device-history/rollup/rollup-engine.d.ts +2 -0
- package/dist/builtins/device-history/rollup/rollup-math.d.ts +6 -0
- package/dist/builtins/device-history/store/history-store.d.ts +7 -0
- package/dist/builtins/device-history/store/series-registry.d.ts +26 -1
- package/dist/builtins/device-manager/device-manager.addon.js +1 -1
- package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts +1 -2
- package/dist/builtins/hub-forwarder/index.js +1 -4
- package/dist/builtins/hub-forwarder/index.mjs +1 -4
- package/dist/builtins/keychain/index.js +4 -4
- package/dist/builtins/keychain/index.mjs +4 -4
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/auth-schema.d.ts +4 -7
- package/dist/builtins/local-auth/local-auth.addon.d.ts +7 -8
- package/dist/builtins/local-auth/local-auth.addon.js +1086 -1329
- package/dist/builtins/local-auth/local-auth.addon.mjs +1085 -1326
- package/dist/builtins/local-auth/preferences-record-kinds.d.ts +27 -0
- package/dist/builtins/local-auth/record-kinds.d.ts +4 -3
- package/dist/builtins/local-auth/user-preferences-store.d.ts +56 -0
- package/dist/builtins/local-network/local-network.addon.d.ts +8 -0
- package/dist/builtins/local-network/local-network.addon.js +30 -5
- package/dist/builtins/local-network/local-network.addon.mjs +30 -5
- package/dist/builtins/loki-logging/index.js +323 -7
- package/dist/builtins/loki-logging/index.mjs +323 -7
- package/dist/builtins/loki-logging/loki-destination.d.ts +20 -3
- package/dist/builtins/loki-logging/loki-read.d.ts +51 -0
- package/dist/builtins/native-metrics/native-metrics.addon.js +290 -112
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +290 -112
- package/dist/builtins/native-metrics/node-metrics-history-recorder.d.ts +106 -35
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/record-entries/index.js +1 -1
- package/dist/builtins/record-entries/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/index.js +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/records/records-engine-types.d.ts +1 -0
- package/dist/builtins/sqlite-storage/records/records-provider.d.ts +13 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +52 -6
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +52 -6
- package/dist/builtins/storage-orchestrator/location-history-recorder.d.ts +76 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +9 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +144 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +144 -1
- package/dist/builtins/system-config/system-config.addon.js +3 -3
- package/dist/builtins/system-config/system-config.addon.mjs +3 -3
- package/dist/builtins/winston-logging/index.js +165 -3
- package/dist/builtins/winston-logging/index.mjs +165 -3
- package/dist/builtins/winston-logging/log-file-cursor.d.ts +12 -0
- package/dist/builtins/winston-logging/log-file-index-store.d.ts +11 -0
- package/dist/builtins/winston-logging/log-file-index.d.ts +77 -0
- package/dist/builtins/winston-logging/log-file-read-client.d.ts +44 -0
- package/dist/builtins/winston-logging/log-file-read-protocol.d.ts +41 -0
- package/dist/builtins/winston-logging/log-file-read-worker.d.ts +1 -0
- package/dist/builtins/winston-logging/log-file-reader.d.ts +51 -0
- package/dist/builtins/winston-logging/winston-destination.d.ts +20 -2
- package/dist/{composition-sources-CSzD5_qX.mjs → composition-sources-B_jeZDRJ.mjs} +1 -1
- package/dist/{composition-sources-BxUkLNSo.js → composition-sources-qKyvMAvc.js} +1 -1
- package/dist/{constants-DruwCkfp.js → constants-B_X9cWEK.js} +1 -1
- package/dist/{constants-DOlYt1t1.mjs → constants-cxKcZRMi.mjs} +1 -1
- package/dist/{device-ca-uFbTyY_o.mjs → device-ca-BB6nHAL5.mjs} +1 -1
- package/dist/{device-ca-QPBgfqVY.js → device-ca-CVK3YcR_.js} +1 -1
- package/dist/{dist-DjQ8C-ku.js → dist-C89nqNnn.js} +721 -154
- package/dist/{dist-BpSENkRJ.mjs → dist-Dhux90yB.mjs} +668 -155
- package/dist/file-host-worker.js +1 -1
- package/dist/file-host-worker.mjs +1 -1
- package/dist/{hub-mdns-ciao-DlgkFJVY.mjs → hub-mdns-ciao-B8olZqqd.mjs} +20 -13
- package/dist/{hub-mdns-ciao-Bkrvgnma.js → hub-mdns-ciao-DB1Y9agt.js} +20 -13
- package/dist/index.d.ts +3 -4
- package/dist/index.js +145 -24
- package/dist/index.mjs +138 -22
- package/dist/kernel/config-schema.d.ts +5 -0
- package/dist/kernel/moleculer/accessory-prune-gate.d.ts +55 -0
- package/dist/log-file-read-protocol-B7M3X3bT.mjs +785 -0
- package/dist/log-file-read-protocol-D0WcLbIV.js +817 -0
- package/dist/log-file-read-worker.js +64 -0
- package/dist/log-file-read-worker.mjs +64 -0
- package/dist/log-read-filter-Bi066jwU.js +134 -0
- package/dist/log-read-filter-D7Ev3zm-.mjs +93 -0
- package/dist/logging/log-manager.d.ts +10 -6
- package/dist/logging/log-read-filter.d.ts +43 -0
- package/dist/{phase6-declare-gate-DrmTtz7F.js → phase6-declare-gate-BO-_jLh_.js} +1 -1
- package/dist/{phase6-declare-gate-JQUqgQgq.mjs → phase6-declare-gate-x9ZCbQJP.mjs} +1 -1
- package/dist/{restore-archive-4OP0EYg6.js → restore-archive-4DOUR5Gu.js} +1 -1
- package/dist/{restore-archive-YFSJRtKy.mjs → restore-archive-DyOaPeAr.mjs} +1 -1
- package/dist/tls/cert-issue.d.ts +16 -1
- package/dist/tls/cert-manager.d.ts +6 -0
- package/dist/tls/index.d.ts +1 -1
- package/dist/{wal-maintenance-TyuOEVy1.mjs → wal-maintenance-BMiX6UIa.mjs} +0 -0
- package/dist/{wal-maintenance-Bl0ZKjVR.js → wal-maintenance-DVN-DiVP.js} +0 -0
- package/package.json +1 -1
- package/dist/auth/admin-token-manager.d.ts +0 -51
- package/dist/auth/scoped-token-manager.d.ts +0 -25
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The per-user account preferences kind (D894), owned by `local-auth` because
|
|
3
|
+
* it owns the users the rows belong to — a user's delete wipes them in the
|
|
4
|
+
* same call (`endAllAccess`).
|
|
5
|
+
*
|
|
6
|
+
* Declared on its OWN, never folded into `localAuthRecordKinds`: that map is
|
|
7
|
+
* the phase-6 adoption set, and a kind missing from the catalog there sends
|
|
8
|
+
* the whole auth builtin back to its legacy tables.
|
|
9
|
+
*/
|
|
10
|
+
import { type DefinedRecordKind, type PreferenceValue } from "@camstack/types";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
/** One `(user, namespace)` row. The record id is {@link userPreferenceRecordId}. */
|
|
13
|
+
export type UserPreferenceBody = {
|
|
14
|
+
readonly userId: string;
|
|
15
|
+
readonly namespace: string;
|
|
16
|
+
readonly value: PreferenceValue;
|
|
17
|
+
readonly revision: number;
|
|
18
|
+
readonly updatedAt: number;
|
|
19
|
+
readonly updatedByDevice: string | null;
|
|
20
|
+
};
|
|
21
|
+
export declare const UserPreferenceBodySchema: z.ZodType<UserPreferenceBody>;
|
|
22
|
+
export type LocalAuthPreferencesRecordKindsMap = {
|
|
23
|
+
readonly "user-preferences": DefinedRecordKind<z.ZodType<UserPreferenceBody>>;
|
|
24
|
+
};
|
|
25
|
+
export declare const localAuthPreferencesRecordKinds: LocalAuthPreferencesRecordKindsMap;
|
|
26
|
+
/** Namespaces cannot hold `:`, so the id splits unambiguously. */
|
|
27
|
+
export declare function userPreferenceRecordId(userId: string, namespace: string): string;
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* use `storageAdmin.dropCollection` with `expectRows` after verify (see
|
|
6
6
|
* `docs/design/records-phase-6-auth-migration.md`).
|
|
7
7
|
*
|
|
8
|
-
* Before hub restart: mint and test an admin token (`
|
|
8
|
+
* Before hub restart: mint and test an admin token (`auth.mintToken` with `'*'`); snapshot every kind.
|
|
9
|
+
*
|
|
10
|
+
* `scoped-tokens` and `admin-tokens` hold pre-D892 rows read by nothing since
|
|
11
|
+
* D895; they stay declared so the layout is unchanged.
|
|
9
12
|
*/
|
|
10
13
|
import { ApiKeyRecordSchema, type DefinedRecordKind, type RecordKindHandle, type RecordsAccess, ScopedTokenSchema, TokenScopeSchema, UserRecordSchema } from "@camstack/types";
|
|
11
14
|
import { z } from "zod";
|
|
@@ -128,8 +131,6 @@ export declare const LOCAL_AUTH_RECORDS_LEGACY_COLLECTIONS: readonly string[];
|
|
|
128
131
|
export interface LocalAuthRecordStores {
|
|
129
132
|
readonly users: RecordKindHandle<UserBody>;
|
|
130
133
|
readonly apiKeys: RecordKindHandle<ApiKeyBody>;
|
|
131
|
-
readonly scopedTokens: RecordKindHandle<ScopedTokenBody>;
|
|
132
|
-
readonly adminTokens: RecordKindHandle<AdminTokenBody>;
|
|
133
134
|
readonly loginSessions: RecordKindHandle<LoginSessionBody>;
|
|
134
135
|
readonly loginSessionCutoffs: RecordKindHandle<LoginSessionCutoffBody>;
|
|
135
136
|
readonly oauthSessions: RecordKindHandle<OauthSessionBody>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-user account preferences (D894): one row per `(userId, namespace)`.
|
|
3
|
+
*
|
|
4
|
+
* Every method takes the OWNER's id as its first argument and touches only
|
|
5
|
+
* that user's rows; the hub router stamps it from the principal, and nothing
|
|
6
|
+
* here accepts a second user id.
|
|
7
|
+
*
|
|
8
|
+
* The poll (`changedSince`) is answered from an in-memory mirror of each
|
|
9
|
+
* user's `namespace → revision`, loaded once on first touch. This store is the
|
|
10
|
+
* kind's only writer (one in-process instance in `local-auth`), so the mirror
|
|
11
|
+
* cannot fall behind the rows; a poll that finds nothing changed reads no row.
|
|
12
|
+
* Writes of one user are serialized, so a read-modify-write never interleaves.
|
|
13
|
+
*/
|
|
14
|
+
import { type PreferenceRow, type PreferencesChangedSinceOutput, type PreferencesPutInput, type PreferencesPutOutput, type RecordKindHandle } from "@camstack/types";
|
|
15
|
+
import { type UserPreferenceBody } from "./preferences-record-kinds.js";
|
|
16
|
+
export type PreferencesLimitReason = "value-too-large" | "too-many-namespaces";
|
|
17
|
+
/** A write the limits refuse; nothing was stored. */
|
|
18
|
+
export declare class PreferencesLimitError extends Error {
|
|
19
|
+
readonly reason: PreferencesLimitReason;
|
|
20
|
+
constructor(reason: PreferencesLimitReason, message: string);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The limit refusal behind `error`, or `null`. By name and field, not
|
|
24
|
+
* `instanceof`: the store and its caller may load different copies of this
|
|
25
|
+
* module.
|
|
26
|
+
*/
|
|
27
|
+
export declare function preferencesLimitReasonOf(error: unknown): PreferencesLimitReason | null;
|
|
28
|
+
export interface UserPreferencesStoreOptions {
|
|
29
|
+
readonly now?: () => number;
|
|
30
|
+
}
|
|
31
|
+
export declare class UserPreferencesStore {
|
|
32
|
+
private readonly kind;
|
|
33
|
+
private readonly now;
|
|
34
|
+
/** `userId → namespace → revision`; absent until the user is first touched. */
|
|
35
|
+
private readonly mirrors;
|
|
36
|
+
private readonly locks;
|
|
37
|
+
constructor(kind: RecordKindHandle<UserPreferenceBody>, options?: UserPreferencesStoreOptions);
|
|
38
|
+
/** The caller's rows; `namespaces` narrows them (unknown ones are simply absent). */
|
|
39
|
+
get(userId: string, namespaces?: readonly string[]): Promise<readonly PreferenceRow[]>;
|
|
40
|
+
put(userId: string, input: PreferencesPutInput): Promise<PreferencesPutOutput>;
|
|
41
|
+
/** `false` when the namespace did not exist. */
|
|
42
|
+
delete(userId: string, namespace: string): Promise<boolean>;
|
|
43
|
+
/**
|
|
44
|
+
* What the client is missing: every row whose revision differs from the one
|
|
45
|
+
* it holds (or that it does not list), and every listed namespace now gone.
|
|
46
|
+
*/
|
|
47
|
+
changedSince(userId: string, known: Readonly<Record<string, number>>): Promise<PreferencesChangedSinceOutput>;
|
|
48
|
+
/** Every row of `userId` goes (user delete). Returns how many. */
|
|
49
|
+
wipeUser(userId: string): Promise<number>;
|
|
50
|
+
/** Rows whose user is not in `liveUserIds` go (boot sweep). Returns how many. */
|
|
51
|
+
wipeOrphans(liveUserIds: ReadonlySet<string>): Promise<number>;
|
|
52
|
+
private mirrorOf;
|
|
53
|
+
private scanUser;
|
|
54
|
+
/** Run `task` after every earlier write of the same user has settled. */
|
|
55
|
+
private serialized;
|
|
56
|
+
}
|
|
@@ -164,6 +164,14 @@ export declare class LocalNetworkAddon extends BaseAddon<LocalNetworkConfig> {
|
|
|
164
164
|
private reconcileMeshEndpoint;
|
|
165
165
|
private resolvedHttpPort;
|
|
166
166
|
private tlsDataDir;
|
|
167
|
+
/**
|
|
168
|
+
* The hub's name (D861) for a local root this action may CREATE (an existing root is
|
|
169
|
+
* never renamed). Hub only: on an agent the hub's name would mislabel the agent's own
|
|
170
|
+
* material, so it gets the plain name. A failed read falls back to the machine
|
|
171
|
+
* hostname (what the hub shows when unnamed) and says so: a certificate action must
|
|
172
|
+
* not fail over a label.
|
|
173
|
+
*/
|
|
174
|
+
private caHubName;
|
|
167
175
|
private snapshotTls;
|
|
168
176
|
/**
|
|
169
177
|
* Hot-swap the HTTPS listener onto the pair now on disk. A failure is not
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("../../dist-
|
|
4
|
-
const require_hub_mdns_ciao = require("../../hub-mdns-ciao-
|
|
3
|
+
const require_dist = require("../../dist-C89nqNnn.js");
|
|
4
|
+
const require_hub_mdns_ciao = require("../../hub-mdns-ciao-DB1Y9agt.js");
|
|
5
5
|
let zod = require("zod");
|
|
6
6
|
let node_net = require("node:net");
|
|
7
7
|
let node_os = require("node:os");
|
|
@@ -887,7 +887,10 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
887
887
|
getTlsStatus: async () => this.snapshotTls(),
|
|
888
888
|
regenerateCertificate: async (_input) => {
|
|
889
889
|
const extraSans = [...require_hub_mdns_ciao.readExtraSans(this.tlsDataDir())];
|
|
890
|
-
await require_hub_mdns_ciao.regenerateCertificate(this.tlsDataDir(), {
|
|
890
|
+
await require_hub_mdns_ciao.regenerateCertificate(this.tlsDataDir(), {
|
|
891
|
+
extraSans,
|
|
892
|
+
...await this.caHubName()
|
|
893
|
+
});
|
|
891
894
|
this.putCertificateOnListener("regenerate");
|
|
892
895
|
return this.snapshotTls();
|
|
893
896
|
},
|
|
@@ -910,7 +913,10 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
910
913
|
},
|
|
911
914
|
revertToGeneratedCertificate: async () => {
|
|
912
915
|
const extraSans = [...require_hub_mdns_ciao.readExtraSans(this.tlsDataDir())];
|
|
913
|
-
await require_hub_mdns_ciao.revertToGeneratedCertificate(this.tlsDataDir(), {
|
|
916
|
+
await require_hub_mdns_ciao.revertToGeneratedCertificate(this.tlsDataDir(), {
|
|
917
|
+
extraSans,
|
|
918
|
+
...await this.caHubName()
|
|
919
|
+
});
|
|
914
920
|
this.putCertificateOnListener("revert");
|
|
915
921
|
return this.snapshotTls();
|
|
916
922
|
}
|
|
@@ -1117,6 +1123,22 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
1117
1123
|
tlsDataDir() {
|
|
1118
1124
|
return this.ctx.nodeDataDir ?? this.ctx.dataDir;
|
|
1119
1125
|
}
|
|
1126
|
+
/**
|
|
1127
|
+
* The hub's name (D861) for a local root this action may CREATE (an existing root is
|
|
1128
|
+
* never renamed). Hub only: on an agent the hub's name would mislabel the agent's own
|
|
1129
|
+
* material, so it gets the plain name. A failed read falls back to the machine
|
|
1130
|
+
* hostname (what the hub shows when unnamed) and says so: a certificate action must
|
|
1131
|
+
* not fail over a label.
|
|
1132
|
+
*/
|
|
1133
|
+
async caHubName() {
|
|
1134
|
+
if ((this.ctx.kernel.localNodeId ?? "hub") !== "hub") return {};
|
|
1135
|
+
try {
|
|
1136
|
+
return { hubName: (await this.ctx.api.system.info.query()).serverName.name };
|
|
1137
|
+
} catch (err) {
|
|
1138
|
+
this.ctx.logger.warn("local-network: server name unreadable - naming the CA after the hostname", { meta: { error: require_dist.errMsg(err) } });
|
|
1139
|
+
return { hubName: require_dist.shortHostname(node_os.hostname()) };
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1120
1142
|
snapshotTls() {
|
|
1121
1143
|
const status = require_hub_mdns_ciao.readTlsAccessStatus(this.tlsDataDir(), require_hub_mdns_ciao.servedHttpsLeafFingerprint());
|
|
1122
1144
|
return {
|
|
@@ -1165,7 +1187,10 @@ var LocalNetworkAddon = class extends require_dist.BaseAddon {
|
|
|
1165
1187
|
if (hostname !== "") {
|
|
1166
1188
|
require_hub_mdns_ciao.writeExtraSans(this.tlsDataDir(), [hostname]);
|
|
1167
1189
|
if (require_hub_mdns_ciao.readTlsMode(this.tlsDataDir()) === "generated") {
|
|
1168
|
-
if ((await require_hub_mdns_ciao.ensureTlsCert(this.tlsDataDir(), {
|
|
1190
|
+
if ((await require_hub_mdns_ciao.ensureTlsCert(this.tlsDataDir(), {
|
|
1191
|
+
extraSans: [hostname],
|
|
1192
|
+
hubName: require_dist.shortHostname(node_os.hostname())
|
|
1193
|
+
})).generated) this.putCertificateOnListener("hostname");
|
|
1169
1194
|
}
|
|
1170
1195
|
} else require_hub_mdns_ciao.writeExtraSans(this.tlsDataDir(), []);
|
|
1171
1196
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { $n as
|
|
2
|
-
import { P as readLanHttpState, a as storeUploadedCertificate, d as servedHttpsLeafFingerprint, g as readTlsMode, h as readTlsAccessStatus, i as revertToGeneratedCertificate, j as applyLanHttp, k as DEFAULT_LAN_HTTP_PORT, m as readExtraSans, n as applyCertificateToListener, o as useStoredUploadedCertificate, r as regenerateCertificate, t as createCiaoPublisher, v as writeExtraSans, w as ensureTlsCert } from "../../hub-mdns-ciao-
|
|
1
|
+
import { $n as pickAppLinkHub, Cn as isAppLinkBase, Dn as isDefaultAppConnectionEndpoint, Fr as validateCustomAddress, H as HUB_MDNS_TXT_KEYS, Ln as localNetworkCapability, Lt as checkRoleAddress, Oa as EventCategory, V as HUB_MDNS_SERVICE_TYPE, Z as NetworkTunnelStartedDataSchema, cr as resolveAppConnections, fr as resolveHubAddressRoles, hr as resolveNotificationTapAddress, l as CAMSTACK_APP_LINK_ORIGIN, ni as BaseAddon, ti as errMsg, xr as shortHostname } from "../../dist-Dhux90yB.mjs";
|
|
2
|
+
import { P as readLanHttpState, a as storeUploadedCertificate, d as servedHttpsLeafFingerprint, g as readTlsMode, h as readTlsAccessStatus, i as revertToGeneratedCertificate, j as applyLanHttp, k as DEFAULT_LAN_HTTP_PORT, m as readExtraSans, n as applyCertificateToListener, o as useStoredUploadedCertificate, r as regenerateCertificate, t as createCiaoPublisher, v as writeExtraSans, w as ensureTlsCert } from "../../hub-mdns-ciao-B8olZqqd.mjs";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { isIP } from "node:net";
|
|
5
5
|
import * as os$1 from "node:os";
|
|
@@ -884,7 +884,10 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
884
884
|
getTlsStatus: async () => this.snapshotTls(),
|
|
885
885
|
regenerateCertificate: async (_input) => {
|
|
886
886
|
const extraSans = [...readExtraSans(this.tlsDataDir())];
|
|
887
|
-
await regenerateCertificate(this.tlsDataDir(), {
|
|
887
|
+
await regenerateCertificate(this.tlsDataDir(), {
|
|
888
|
+
extraSans,
|
|
889
|
+
...await this.caHubName()
|
|
890
|
+
});
|
|
888
891
|
this.putCertificateOnListener("regenerate");
|
|
889
892
|
return this.snapshotTls();
|
|
890
893
|
},
|
|
@@ -907,7 +910,10 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
907
910
|
},
|
|
908
911
|
revertToGeneratedCertificate: async () => {
|
|
909
912
|
const extraSans = [...readExtraSans(this.tlsDataDir())];
|
|
910
|
-
await revertToGeneratedCertificate(this.tlsDataDir(), {
|
|
913
|
+
await revertToGeneratedCertificate(this.tlsDataDir(), {
|
|
914
|
+
extraSans,
|
|
915
|
+
...await this.caHubName()
|
|
916
|
+
});
|
|
911
917
|
this.putCertificateOnListener("revert");
|
|
912
918
|
return this.snapshotTls();
|
|
913
919
|
}
|
|
@@ -1114,6 +1120,22 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
1114
1120
|
tlsDataDir() {
|
|
1115
1121
|
return this.ctx.nodeDataDir ?? this.ctx.dataDir;
|
|
1116
1122
|
}
|
|
1123
|
+
/**
|
|
1124
|
+
* The hub's name (D861) for a local root this action may CREATE (an existing root is
|
|
1125
|
+
* never renamed). Hub only: on an agent the hub's name would mislabel the agent's own
|
|
1126
|
+
* material, so it gets the plain name. A failed read falls back to the machine
|
|
1127
|
+
* hostname (what the hub shows when unnamed) and says so: a certificate action must
|
|
1128
|
+
* not fail over a label.
|
|
1129
|
+
*/
|
|
1130
|
+
async caHubName() {
|
|
1131
|
+
if ((this.ctx.kernel.localNodeId ?? "hub") !== "hub") return {};
|
|
1132
|
+
try {
|
|
1133
|
+
return { hubName: (await this.ctx.api.system.info.query()).serverName.name };
|
|
1134
|
+
} catch (err) {
|
|
1135
|
+
this.ctx.logger.warn("local-network: server name unreadable - naming the CA after the hostname", { meta: { error: errMsg(err) } });
|
|
1136
|
+
return { hubName: shortHostname(os$1.hostname()) };
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1117
1139
|
snapshotTls() {
|
|
1118
1140
|
const status = readTlsAccessStatus(this.tlsDataDir(), servedHttpsLeafFingerprint());
|
|
1119
1141
|
return {
|
|
@@ -1162,7 +1184,10 @@ var LocalNetworkAddon = class extends BaseAddon {
|
|
|
1162
1184
|
if (hostname !== "") {
|
|
1163
1185
|
writeExtraSans(this.tlsDataDir(), [hostname]);
|
|
1164
1186
|
if (readTlsMode(this.tlsDataDir()) === "generated") {
|
|
1165
|
-
if ((await ensureTlsCert(this.tlsDataDir(), {
|
|
1187
|
+
if ((await ensureTlsCert(this.tlsDataDir(), {
|
|
1188
|
+
extraSans: [hostname],
|
|
1189
|
+
hubName: shortHostname(os$1.hostname())
|
|
1190
|
+
})).generated) this.putCertificateOnListener("hostname");
|
|
1166
1191
|
}
|
|
1167
1192
|
} else writeExtraSans(this.tlsDataDir(), []);
|
|
1168
1193
|
}
|
|
@@ -3,7 +3,9 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-C89nqNnn.js");
|
|
7
|
+
const require_log_read_filter = require("../../log-read-filter-Bi066jwU.js");
|
|
8
|
+
let zod = require("zod");
|
|
7
9
|
//#region src/builtins/loki-logging/loki-payload.ts
|
|
8
10
|
/**
|
|
9
11
|
* Loki rejects a label name that is not a valid Prometheus label
|
|
@@ -41,6 +43,7 @@ function toUnixNano(timestamp) {
|
|
|
41
43
|
*/
|
|
42
44
|
function formatLokiLine(entry) {
|
|
43
45
|
const line = { message: entry.message };
|
|
46
|
+
if (entry.id !== void 0) line["id"] = entry.id;
|
|
44
47
|
if (entry.scope !== void 0 && entry.scope.length > 0) line["scope"] = entry.scope;
|
|
45
48
|
if (entry.tags !== void 0 && Object.keys(entry.tags).length > 0) line["tags"] = entry.tags;
|
|
46
49
|
if (entry.meta !== void 0 && Object.keys(entry.meta).length > 0) line["meta"] = entry.meta;
|
|
@@ -91,6 +94,231 @@ function buildPushBody(entries, labels) {
|
|
|
91
94
|
return { streams };
|
|
92
95
|
}
|
|
93
96
|
//#endregion
|
|
97
|
+
//#region src/builtins/loki-logging/loki-read.ts
|
|
98
|
+
/**
|
|
99
|
+
* Reading history back from Loki (D898): `GET /loki/api/v1/query_range`,
|
|
100
|
+
* newest first.
|
|
101
|
+
*
|
|
102
|
+
* The label selector narrows by what IS a label (`job`, `node`, `level` —
|
|
103
|
+
* see `loki-payload.ts` for why nothing else is); line filters (`|=`, `|~`)
|
|
104
|
+
* narrow by device, addon and text on the JSON body; the shared
|
|
105
|
+
* `lineMatches` then decides exactly, so a line filter only has to be a
|
|
106
|
+
* superset.
|
|
107
|
+
*
|
|
108
|
+
* The cursor is the nanosecond timestamp of the oldest line returned plus a
|
|
109
|
+
* short hash of each line returned AT that nanosecond: entries share
|
|
110
|
+
* timestamps (they are millisecond-precise), so "older than t" would drop the
|
|
111
|
+
* siblings a page cut through, and "t or older" would repeat them. The next
|
|
112
|
+
* page asks for `end = t + 1` (Loki's end is exclusive) and skips the hashes.
|
|
113
|
+
*/
|
|
114
|
+
var LEVELS = [
|
|
115
|
+
"debug",
|
|
116
|
+
"info",
|
|
117
|
+
"warn",
|
|
118
|
+
"error"
|
|
119
|
+
];
|
|
120
|
+
/** One query_range window. A busy stream fills `limit` long before this. */
|
|
121
|
+
var LOKI_WINDOW_MS = 360 * 60 * 1e3;
|
|
122
|
+
var MS_NS = 1000000n;
|
|
123
|
+
var CURSOR_PREFIX = "l1.";
|
|
124
|
+
var LokiCursorSchema = zod.z.object({
|
|
125
|
+
/** Nanosecond epoch, decimal string. */
|
|
126
|
+
n: zod.z.string().regex(/^\d{1,22}$/),
|
|
127
|
+
/** Hashes of the lines already returned at exactly `n`. */
|
|
128
|
+
h: zod.z.array(zod.z.string().max(16)).max(1e3)
|
|
129
|
+
});
|
|
130
|
+
function encodeLokiCursor(cursor) {
|
|
131
|
+
return `${CURSOR_PREFIX}${Buffer.from(JSON.stringify(cursor)).toString("base64url")}`;
|
|
132
|
+
}
|
|
133
|
+
function decodeLokiCursor(raw) {
|
|
134
|
+
if (!raw.startsWith(CURSOR_PREFIX)) return null;
|
|
135
|
+
try {
|
|
136
|
+
const parsed = LokiCursorSchema.safeParse(JSON.parse(Buffer.from(raw.slice(3), "base64url").toString("utf8")));
|
|
137
|
+
return parsed.success ? parsed.data : null;
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** FNV-1a, 32 bit, hex — a dedupe key, not a security property. */
|
|
143
|
+
function lineHash(line) {
|
|
144
|
+
let h = 2166136261;
|
|
145
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
146
|
+
h ^= line.charCodeAt(i);
|
|
147
|
+
h = Math.imul(h, 16777619);
|
|
148
|
+
}
|
|
149
|
+
return (h >>> 0).toString(16);
|
|
150
|
+
}
|
|
151
|
+
/** A LogQL double-quoted string literal. */
|
|
152
|
+
function quote(value) {
|
|
153
|
+
return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
|
|
154
|
+
}
|
|
155
|
+
function escapeRegex(value) {
|
|
156
|
+
return value.replaceAll(/[\\.+*?()|[\]{}^$]/g, (c) => `\\${c}`);
|
|
157
|
+
}
|
|
158
|
+
/** The LogQL for a read. Pure. */
|
|
159
|
+
function buildLogQl(query, labels) {
|
|
160
|
+
const selectors = [`job=${quote(labels.job)}`];
|
|
161
|
+
if (query.fromNodeId !== void 0) selectors.push(`node=${quote(query.fromNodeId)}`);
|
|
162
|
+
if (query.level !== void 0 && query.level !== "debug") {
|
|
163
|
+
const floor = LEVELS.indexOf(query.level);
|
|
164
|
+
selectors.push(`level=~${quote(LEVELS.slice(floor).join("|"))}`);
|
|
165
|
+
}
|
|
166
|
+
let logql = `{${selectors.join(",")}}`;
|
|
167
|
+
if (query.deviceId !== void 0) logql += ` |= ${quote(`"deviceId":${String(query.deviceId)}`)}`;
|
|
168
|
+
if (query.fromAddonId !== void 0) {
|
|
169
|
+
const bare = query.fromAddonId.split("@")[0] ?? query.fromAddonId;
|
|
170
|
+
logql += ` |= ${quote(`"addonId":"${bare}`)}`;
|
|
171
|
+
}
|
|
172
|
+
const text = query.text?.trim();
|
|
173
|
+
if (text !== void 0 && text.length > 0) logql += ` |~ ${quote(`(?i)${escapeRegex(text)}`)}`;
|
|
174
|
+
return logql;
|
|
175
|
+
}
|
|
176
|
+
var LokiResponseSchema = zod.z.object({ data: zod.z.object({ result: zod.z.array(zod.z.object({
|
|
177
|
+
stream: zod.z.record(zod.z.string(), zod.z.string()),
|
|
178
|
+
values: zod.z.array(zod.z.tuple([zod.z.string(), zod.z.string()]))
|
|
179
|
+
})) }) });
|
|
180
|
+
var LokiLineSchema = zod.z.object({
|
|
181
|
+
id: zod.z.string().optional(),
|
|
182
|
+
message: zod.z.string(),
|
|
183
|
+
scope: zod.z.string().optional(),
|
|
184
|
+
tags: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
185
|
+
meta: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
186
|
+
});
|
|
187
|
+
function levelOf(label) {
|
|
188
|
+
return label === "debug" || label === "warn" || label === "error" ? label : "info";
|
|
189
|
+
}
|
|
190
|
+
/** A query_range body → rows, newest first. Lines that are not ours are dropped. */
|
|
191
|
+
function parseQueryRange(body) {
|
|
192
|
+
const parsed = LokiResponseSchema.safeParse(body);
|
|
193
|
+
if (!parsed.success) throw new Error("Loki answered with an unexpected query_range shape");
|
|
194
|
+
const rows = [];
|
|
195
|
+
for (const stream of parsed.data.data.result) {
|
|
196
|
+
const level = levelOf(stream.stream["level"]);
|
|
197
|
+
for (const [nsText, text] of stream.values) {
|
|
198
|
+
let json;
|
|
199
|
+
try {
|
|
200
|
+
json = JSON.parse(text);
|
|
201
|
+
} catch {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const line = LokiLineSchema.safeParse(json);
|
|
205
|
+
if (!line.success) continue;
|
|
206
|
+
const ns = BigInt(nsText);
|
|
207
|
+
const timestamp = Number(ns / MS_NS);
|
|
208
|
+
const tags = {};
|
|
209
|
+
for (const [k, v] of Object.entries(line.data.tags ?? {})) if (typeof v === "string" || typeof v === "number") tags[k] = v;
|
|
210
|
+
const hasTags = Object.keys(tags).length > 0;
|
|
211
|
+
const entry = {
|
|
212
|
+
...line.data.id !== void 0 ? { id: line.data.id } : {},
|
|
213
|
+
timestamp,
|
|
214
|
+
level,
|
|
215
|
+
...line.data.scope !== void 0 ? { scope: line.data.scope } : {},
|
|
216
|
+
message: line.data.message,
|
|
217
|
+
...hasTags ? { tags } : {},
|
|
218
|
+
...line.data.meta !== void 0 ? { meta: line.data.meta } : {}
|
|
219
|
+
};
|
|
220
|
+
rows.push({
|
|
221
|
+
ns,
|
|
222
|
+
hash: lineHash(text),
|
|
223
|
+
entry,
|
|
224
|
+
line: {
|
|
225
|
+
timestamp,
|
|
226
|
+
level,
|
|
227
|
+
message: line.data.message,
|
|
228
|
+
scope: line.data.scope,
|
|
229
|
+
tags: hasTags ? tags : void 0,
|
|
230
|
+
meta: line.data.meta
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return rows.toSorted((a, b) => a.ns < b.ns ? 1 : a.ns > b.ns ? -1 : 0);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* One backward slice over Loki: windows of {@link LOKI_WINDOW_MS}, newest
|
|
239
|
+
* first, until `limit` matches, the horizon, the budget or a cancel.
|
|
240
|
+
*/
|
|
241
|
+
async function readLokiBackward(args) {
|
|
242
|
+
const { query } = args;
|
|
243
|
+
const now = args.now ?? Date.now;
|
|
244
|
+
const filter = require_log_read_filter.compileLogFilter(query);
|
|
245
|
+
const limit = query.limit ?? 200;
|
|
246
|
+
const deadline = now() + Math.min(query.budgetMs ?? 2e3, 1e4);
|
|
247
|
+
const logql = buildLogQl(query, args.labels);
|
|
248
|
+
const horizonMs = query.since ?? now() - 30 * 864e5;
|
|
249
|
+
const horizonNs = BigInt(Math.trunc(horizonMs)) * MS_NS;
|
|
250
|
+
const cursor = query.before !== void 0 ? decodeLokiCursor(query.before) : null;
|
|
251
|
+
const untilNs = query.until !== void 0 ? BigInt(Math.trunc(query.until) + 1) * MS_NS : null;
|
|
252
|
+
let endNs = cursor !== null ? BigInt(cursor.n) + 1n : untilNs ?? BigInt(now() + 1) * MS_NS;
|
|
253
|
+
let seenAtBoundary = new Set(cursor?.h ?? []);
|
|
254
|
+
let boundaryNs = cursor !== null ? BigInt(cursor.n) : null;
|
|
255
|
+
const entries = [];
|
|
256
|
+
let bytes = 0;
|
|
257
|
+
while (endNs > horizonNs) {
|
|
258
|
+
if (args.isCancelled()) return {
|
|
259
|
+
entries,
|
|
260
|
+
nextBefore: encodeLokiCursor({
|
|
261
|
+
n: String(endNs - 1n),
|
|
262
|
+
h: [...seenAtBoundary]
|
|
263
|
+
}),
|
|
264
|
+
nextAfter: null,
|
|
265
|
+
scannedBytes: bytes,
|
|
266
|
+
done: false,
|
|
267
|
+
cancelled: true
|
|
268
|
+
};
|
|
269
|
+
const windowStart = endNs - BigInt(LOKI_WINDOW_MS) * MS_NS;
|
|
270
|
+
const startNs = windowStart > horizonNs ? windowStart : horizonNs;
|
|
271
|
+
const want = limit - entries.length + seenAtBoundary.size;
|
|
272
|
+
const { rows, bytes: got } = await args.range({
|
|
273
|
+
logql,
|
|
274
|
+
startNs,
|
|
275
|
+
endNs,
|
|
276
|
+
limit: want
|
|
277
|
+
});
|
|
278
|
+
bytes += got;
|
|
279
|
+
let oldest = null;
|
|
280
|
+
for (const row of rows) {
|
|
281
|
+
if (boundaryNs !== null && row.ns === boundaryNs && seenAtBoundary.has(row.hash)) continue;
|
|
282
|
+
if (oldest === null || row.ns < oldest) {
|
|
283
|
+
if (oldest !== null && row.ns < oldest) seenAtBoundary = /* @__PURE__ */ new Set();
|
|
284
|
+
oldest = row.ns;
|
|
285
|
+
}
|
|
286
|
+
if (row.ns === oldest) seenAtBoundary.add(row.hash);
|
|
287
|
+
boundaryNs = oldest;
|
|
288
|
+
if (!require_log_read_filter.lineMatches(row.line, filter)) continue;
|
|
289
|
+
entries.push(row.entry);
|
|
290
|
+
if (entries.length >= limit) return {
|
|
291
|
+
entries,
|
|
292
|
+
nextBefore: encodeLokiCursor({
|
|
293
|
+
n: String(row.ns),
|
|
294
|
+
h: [...seenAtBoundary]
|
|
295
|
+
}),
|
|
296
|
+
nextAfter: null,
|
|
297
|
+
scannedBytes: bytes,
|
|
298
|
+
done: false
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (rows.length >= want && oldest !== null) endNs = oldest + 1n;
|
|
302
|
+
else {
|
|
303
|
+
endNs = startNs;
|
|
304
|
+
seenAtBoundary = /* @__PURE__ */ new Set();
|
|
305
|
+
boundaryNs = null;
|
|
306
|
+
}
|
|
307
|
+
if (now() >= deadline) break;
|
|
308
|
+
}
|
|
309
|
+
const done = endNs <= horizonNs;
|
|
310
|
+
return {
|
|
311
|
+
entries,
|
|
312
|
+
nextBefore: done ? null : encodeLokiCursor({
|
|
313
|
+
n: String(endNs - 1n),
|
|
314
|
+
h: [...seenAtBoundary]
|
|
315
|
+
}),
|
|
316
|
+
nextAfter: null,
|
|
317
|
+
scannedBytes: bytes,
|
|
318
|
+
done
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
//#endregion
|
|
94
322
|
//#region src/builtins/loki-logging/loki-destination.ts
|
|
95
323
|
var LEVEL_ORDER = {
|
|
96
324
|
debug: 0,
|
|
@@ -114,6 +342,8 @@ var LokiDestination = class {
|
|
|
114
342
|
failures = 0;
|
|
115
343
|
lastError = null;
|
|
116
344
|
lastPushAtMs = null;
|
|
345
|
+
/** In-flight history reads, by requestId, so `cancelRead` can abort the fetch. */
|
|
346
|
+
reads = /* @__PURE__ */ new Map();
|
|
117
347
|
fetchFn;
|
|
118
348
|
setIntervalFn;
|
|
119
349
|
clearIntervalFn;
|
|
@@ -121,7 +351,7 @@ var LokiDestination = class {
|
|
|
121
351
|
this.fetchFn = deps?.fetchFn ?? ((url, init) => globalThis.fetch(url, {
|
|
122
352
|
method: init.method,
|
|
123
353
|
headers: { ...init.headers },
|
|
124
|
-
body: init.body,
|
|
354
|
+
...init.body !== void 0 ? { body: init.body } : {},
|
|
125
355
|
...init.signal ? { signal: init.signal } : {}
|
|
126
356
|
}));
|
|
127
357
|
this.setIntervalFn = deps?.setIntervalFn ?? setInterval;
|
|
@@ -213,9 +443,10 @@ var LokiDestination = class {
|
|
|
213
443
|
}
|
|
214
444
|
async push(body, config) {
|
|
215
445
|
const endpoint = `${config.url.replace(/\/+$/, "")}/loki/api/v1/push`;
|
|
216
|
-
const headers = {
|
|
217
|
-
|
|
218
|
-
|
|
446
|
+
const headers = {
|
|
447
|
+
"content-type": "application/json",
|
|
448
|
+
...this.authHeaders(config)
|
|
449
|
+
};
|
|
219
450
|
const controller = new AbortController();
|
|
220
451
|
const timeout = setTimeout(() => controller.abort(), Math.max(1e3, config.timeoutMs));
|
|
221
452
|
try {
|
|
@@ -283,8 +514,93 @@ var LokiDestination = class {
|
|
|
283
514
|
};
|
|
284
515
|
}
|
|
285
516
|
}
|
|
286
|
-
|
|
287
|
-
|
|
517
|
+
/** Headers every call to this Loki carries: tenant + basic auth. */
|
|
518
|
+
authHeaders(config) {
|
|
519
|
+
const headers = {};
|
|
520
|
+
if (config.tenantId.trim().length > 0) headers["X-Scope-OrgID"] = config.tenantId.trim();
|
|
521
|
+
if (config.username.length > 0) headers["authorization"] = `Basic ${Buffer.from(`${config.username}:${config.password}`).toString("base64")}`;
|
|
522
|
+
return headers;
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Readable when it is configured (D898): the same URL and credentials the
|
|
526
|
+
* push uses answer `query_range`. Labels are `job`/`node`/`level`, so those
|
|
527
|
+
* filter at the index; device, addon and text are line filters.
|
|
528
|
+
*/
|
|
529
|
+
async describeRead() {
|
|
530
|
+
const configured = this.isActive();
|
|
531
|
+
return {
|
|
532
|
+
readable: configured,
|
|
533
|
+
reason: configured ? null : "Loki is not enabled or has no URL",
|
|
534
|
+
filters: {
|
|
535
|
+
deviceId: true,
|
|
536
|
+
addonId: true,
|
|
537
|
+
nodeId: true,
|
|
538
|
+
tags: true,
|
|
539
|
+
level: true,
|
|
540
|
+
text: true,
|
|
541
|
+
timeRange: true
|
|
542
|
+
},
|
|
543
|
+
retentionDays: null
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
/** One backward slice from `query_range`, newest first. Never logs. */
|
|
547
|
+
async read(query) {
|
|
548
|
+
const config = this.config;
|
|
549
|
+
if (config === null || !this.isActive()) throw new Error("Loki is not enabled or has no URL");
|
|
550
|
+
const controller = new AbortController();
|
|
551
|
+
const requestId = query.requestId;
|
|
552
|
+
if (requestId !== void 0) this.reads.set(requestId, controller);
|
|
553
|
+
const base = config.url.replace(/\/+$/, "");
|
|
554
|
+
const range = async ({ logql, startNs, endNs, limit }) => {
|
|
555
|
+
const params = new URLSearchParams({
|
|
556
|
+
query: logql,
|
|
557
|
+
start: String(startNs),
|
|
558
|
+
end: String(endNs),
|
|
559
|
+
limit: String(limit),
|
|
560
|
+
direction: "backward"
|
|
561
|
+
});
|
|
562
|
+
const timeout = setTimeout(() => controller.abort(), Math.max(1e3, config.timeoutMs));
|
|
563
|
+
try {
|
|
564
|
+
const response = await this.fetchFn(`${base}/loki/api/v1/query_range?${params.toString()}`, {
|
|
565
|
+
method: "GET",
|
|
566
|
+
headers: this.authHeaders(config),
|
|
567
|
+
signal: controller.signal
|
|
568
|
+
});
|
|
569
|
+
const text = await response.text();
|
|
570
|
+
if (!response.ok) throw new Error(`Loki query failed: ${String(response.status)}${text.length > 0 ? ` — ${text.slice(0, 200)}` : ""}`);
|
|
571
|
+
return {
|
|
572
|
+
rows: parseQueryRange(JSON.parse(text)),
|
|
573
|
+
bytes: Buffer.byteLength(text)
|
|
574
|
+
};
|
|
575
|
+
} finally {
|
|
576
|
+
clearTimeout(timeout);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
try {
|
|
580
|
+
return await readLokiBackward({
|
|
581
|
+
query,
|
|
582
|
+
labels: { job: config.job },
|
|
583
|
+
range,
|
|
584
|
+
isCancelled: () => controller.signal.aborted
|
|
585
|
+
});
|
|
586
|
+
} catch (error) {
|
|
587
|
+
if (controller.signal.aborted) return {
|
|
588
|
+
entries: [],
|
|
589
|
+
nextBefore: query.before ?? null,
|
|
590
|
+
nextAfter: null,
|
|
591
|
+
scannedBytes: 0,
|
|
592
|
+
done: false,
|
|
593
|
+
cancelled: true
|
|
594
|
+
};
|
|
595
|
+
throw error;
|
|
596
|
+
} finally {
|
|
597
|
+
if (requestId !== void 0) this.reads.delete(requestId);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
async cancelRead(input) {
|
|
601
|
+
const controller = this.reads.get(input.requestId);
|
|
602
|
+
controller?.abort();
|
|
603
|
+
return { cancelled: controller !== void 0 };
|
|
288
604
|
}
|
|
289
605
|
async shutdown() {
|
|
290
606
|
this.stopTimer();
|