@aurostack/stacks 0.1.1 → 0.2.1
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/README.md +59 -0
- package/cli/src/generate.mjs +13 -1
- package/cli/stack.mjs +1 -1
- package/package.json +1 -1
- package/templates/nest-api/files/.bin/secrets.ts +15 -6
- package/templates/nest-api/files/.env.example +29 -2
- package/templates/nest-api/files/.env.test.example +1 -1
- package/templates/nest-api/files/_gitignore +1 -0
- package/templates/nest-api/files/compose.yml +16 -0
- package/templates/nest-api/files/package.json +18 -3
- package/templates/nest-api/files/src/app.module.ts +9 -1
- package/templates/nest-api/files/src/app.setup.ts +8 -7
- package/templates/nest-api/files/src/common/__tests__/temporal.service.spec.ts +66 -0
- package/templates/nest-api/files/src/common/common.module.ts +4 -3
- package/templates/nest-api/files/src/common/constants/pino.ts +14 -0
- package/templates/nest-api/files/src/common/controllers/index.ts +0 -1
- package/templates/nest-api/files/src/common/modules/index.ts +0 -1
- package/templates/nest-api/files/src/common/modules/logger.module.ts +5 -8
- package/templates/nest-api/files/src/common/services/config.service.ts +43 -5
- package/templates/nest-api/files/src/common/services/index.ts +1 -0
- package/templates/nest-api/files/src/common/services/temporal.service.ts +66 -0
- package/templates/nest-api/files/src/instrumentation.ts +117 -0
- package/templates/nest-api/files/src/main.ts +3 -1
- package/templates/nest-api/files/src/notifications/services/notifications.service.ts +4 -2
- package/templates/nest-api/files/src/telemetry/__tests__/config.spec.ts +67 -0
- package/templates/nest-api/files/src/telemetry/config.ts +62 -0
- package/templates/nest-api/template.json +54 -14
- package/templates/node-worker/files/.bin/secrets.ts +15 -6
- package/templates/node-worker/files/.env.example +27 -0
- package/templates/node-worker/files/Dockerfile +25 -2
- package/templates/node-worker/files/Dockerfile.dev +25 -2
- package/templates/node-worker/files/_gitignore +1 -0
- package/templates/node-worker/files/ecosystem.config.cjs +2 -0
- package/templates/node-worker/files/package.json +24 -4
- package/templates/node-worker/files/src/config.ts +23 -0
- package/templates/node-worker/files/src/index.ts +40 -4
- package/templates/node-worker/files/src/instrumentation.ts +110 -0
- package/templates/node-worker/files/src/logger.ts +14 -1
- package/templates/node-worker/files/src/telemetry/config.ts +61 -0
- package/templates/node-worker/files/src/temporal/activities/index.ts +14 -0
- package/templates/node-worker/files/src/temporal/worker.ts +84 -0
- package/templates/node-worker/files/src/temporal/workflows/index.ts +23 -0
- package/templates/node-worker/files/src/utils/worker.ts +10 -2
- package/templates/node-worker/template.json +43 -1
- package/templates/py-worker/files/.env.example +28 -0
- package/templates/py-worker/files/config.py +32 -1
- package/templates/py-worker/files/requirements.txt +16 -0
- package/templates/py-worker/files/temporal/__init__.py +0 -0
- package/templates/py-worker/files/temporal/activities.py +13 -0
- package/templates/py-worker/files/temporal/workflows.py +38 -0
- package/templates/py-worker/files/utils/db.py +20 -27
- package/templates/py-worker/files/utils/logger.py +9 -2
- package/templates/py-worker/files/utils/telemetry.py +154 -0
- package/templates/py-worker/files/utils/temporal.py +55 -0
- package/templates/py-worker/files/utils/worker.py +14 -3
- package/templates/py-worker/files/worker.py +16 -0
- package/templates/py-worker/template.json +36 -0
- package/templates/react-app/derive.sh +2 -1
- package/templates/react-app/files/.bin/secrets.js +8 -2
- package/templates/react-app/files/.env.example +11 -0
- package/templates/react-app/files/.forgejo/workflows/build-and-push.yml +3 -0
- package/templates/react-app/files/Dockerfile +10 -0
- package/templates/react-app/files/_gitignore +1 -0
- package/templates/react-app/files/build.sh +3 -1
- package/templates/react-app/files/package.json +2 -0
- package/templates/react-app/files/src/lib/env.ts +10 -0
- package/templates/react-app/files/src/main.tsx +4 -0
- package/templates/react-app/files/src/shared/auth/provider.tsx +2 -0
- package/templates/react-app/files/src/shared/layouts/error-boundary.tsx +2 -0
- package/templates/react-app/files/src/shared/telemetry/index.ts +80 -0
- package/templates/react-app/template.json +16 -2
- package/templates/react-monorepo/files/.forgejo/workflows/build-and-push.yml +15 -11
- package/templates/react-monorepo/files/Dockerfile +21 -7
- package/templates/react-monorepo/files/_gitignore +1 -0
- package/templates/react-monorepo/files/apps/admin/.bin/secrets.js +8 -3
- package/templates/react-monorepo/files/apps/admin/.env.example +11 -0
- package/templates/react-monorepo/files/apps/admin/package.json +1 -0
- package/templates/react-monorepo/files/apps/admin/src/app/router.tsx +11 -7
- package/templates/react-monorepo/files/apps/admin/src/lib/env.ts +10 -0
- package/templates/react-monorepo/files/apps/admin/src/main.tsx +4 -0
- package/templates/react-monorepo/files/apps/auth/.bin/secrets.js +8 -3
- package/templates/react-monorepo/files/apps/auth/.env.example +11 -0
- package/templates/react-monorepo/files/apps/auth/package.json +1 -0
- package/templates/react-monorepo/files/apps/auth/src/lib/env.ts +10 -0
- package/templates/react-monorepo/files/apps/auth/src/main.tsx +4 -0
- package/templates/react-monorepo/files/apps/client/.bin/secrets.js +8 -3
- package/templates/react-monorepo/files/apps/client/.env.example +11 -0
- package/templates/react-monorepo/files/apps/client/package.json +1 -0
- package/templates/react-monorepo/files/apps/client/src/lib/env.ts +10 -0
- package/templates/react-monorepo/files/apps/client/src/main.tsx +4 -0
- package/templates/react-monorepo/files/apps/client/src/vite-env.d.ts +1 -1
- package/templates/react-monorepo/files/apps/landing/.bin/secrets.js +8 -3
- package/templates/react-monorepo/files/apps/landing/.env.example +11 -0
- package/templates/react-monorepo/files/apps/landing/package.json +1 -0
- package/templates/react-monorepo/files/apps/landing/src/lib/env.ts +10 -0
- package/templates/react-monorepo/files/apps/landing/src/main.tsx +4 -0
- package/templates/react-monorepo/files/build.sh +3 -1
- package/templates/react-monorepo/files/packages/auth/package.json +1 -0
- package/templates/react-monorepo/files/packages/auth/src/provider.tsx +2 -0
- package/templates/react-monorepo/files/packages/layouts/package.json +1 -0
- package/templates/react-monorepo/files/packages/layouts/src/error-boundary.tsx +2 -0
- package/templates/react-monorepo/files/packages/telemetry/eslint.config.mjs +3 -0
- package/templates/react-monorepo/files/packages/telemetry/package.json +20 -0
- package/templates/react-monorepo/files/packages/telemetry/src/index.ts +80 -0
- package/templates/react-monorepo/files/packages/telemetry/tsconfig.json +4 -0
- package/templates/react-monorepo/files/packages/types/.bin/secrets.js +8 -3
- package/templates/react-monorepo/files/turbo.json +1 -1
- package/templates/react-monorepo/template.json +87 -9
- package/templates/nest-api/files/src/common/controllers/metrics.controller.ts +0 -21
- package/templates/nest-api/files/src/common/interceptors/index.ts +0 -1
- package/templates/nest-api/files/src/common/interceptors/metrics.interceptor.ts +0 -37
- package/templates/nest-api/files/src/common/modules/metrics.module.ts +0 -28
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { StrictMode } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { initTelemetry } from '@/shared/telemetry'; // @feature telemetry
|
|
4
|
+
import { API_URL, TELEMETRY } from '@/lib/env'; // @feature telemetry
|
|
3
5
|
import { RouterProvider } from 'react-router';
|
|
4
6
|
import '@/shared/ui/styles/globals.css';
|
|
5
7
|
import { Providers } from './app/providers';
|
|
@@ -10,6 +12,8 @@ import { router } from './app/router';
|
|
|
10
12
|
// evaluating and `providers` has already called `configureApi` at module scope,
|
|
11
13
|
// so the fetch races the session check instead of queueing behind it.
|
|
12
14
|
|
|
15
|
+
initTelemetry({ service: 'acme-app', ...TELEMETRY, apiUrls: [API_URL] }); // @feature telemetry
|
|
16
|
+
|
|
13
17
|
createRoot(document.getElementById('root')!).render(
|
|
14
18
|
<StrictMode>
|
|
15
19
|
<Providers>
|
|
@@ -3,6 +3,7 @@ import { useDispatch } from 'react-redux';
|
|
|
3
3
|
import { getAuthClient } from './client';
|
|
4
4
|
import { sessionCleared, sessionLoaded, sessionLoading, type AuthUser } from './slice';
|
|
5
5
|
import { AUTH_BROADCAST_CHANNEL } from './logout';
|
|
6
|
+
import { setTelemetryUser } from '@/shared/telemetry'; // @feature telemetry
|
|
6
7
|
|
|
7
8
|
function toAuthUser(user: Record<string, unknown> | null | undefined): AuthUser | null {
|
|
8
9
|
if (!user) return null;
|
|
@@ -33,6 +34,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|
|
33
34
|
}
|
|
34
35
|
const user = (session.data?.user as Record<string, unknown> | undefined) ?? null;
|
|
35
36
|
dispatch(sessionLoaded(toAuthUser(user)));
|
|
37
|
+
setTelemetryUser(user ? { id: String(user.id) } : null); // @feature telemetry
|
|
36
38
|
}, [session.isPending, session.data, dispatch]);
|
|
37
39
|
|
|
38
40
|
useEffect(() => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Component, type ErrorInfo, type ReactNode } from 'react';
|
|
2
2
|
import { Button } from '@/shared/ui';
|
|
3
3
|
import { StateScreen } from './states/state-screen';
|
|
4
|
+
import { reportError } from '@/shared/telemetry'; // @feature telemetry
|
|
4
5
|
|
|
5
6
|
type ErrorBoundaryProps = {
|
|
6
7
|
children: ReactNode;
|
|
@@ -21,6 +22,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|
|
21
22
|
|
|
22
23
|
componentDidCatch(error: Error, info: ErrorInfo): void {
|
|
23
24
|
console.error('ErrorBoundary caught an error', error, info);
|
|
25
|
+
reportError(error, { componentStack: info.componentStack }); // @feature telemetry
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
render(): ReactNode {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// @/shared/telemetry — browser monitoring (RUM) and logs, sent to OpenObserve.
|
|
2
|
+
//
|
|
3
|
+
// Page views, errors, slow resources and user actions, plus console errors as
|
|
4
|
+
// logs. Requests to the app's own API carry a W3C `traceparent` header, so a
|
|
5
|
+
// click links to the backend trace it caused. The SDK finishes starting a
|
|
6
|
+
// moment after init (its session store is async), so the first request or two
|
|
7
|
+
// of a page load (typically the session check) go out without the header.
|
|
8
|
+
// Off until connection settings are passed; everything here is then a no-op.
|
|
9
|
+
import { DefaultPrivacyLevel, openobserveRum } from '@openobserve/browser-rum';
|
|
10
|
+
import { openobserveLogs } from '@openobserve/browser-logs';
|
|
11
|
+
|
|
12
|
+
export interface TelemetryOptions {
|
|
13
|
+
/** Service name, e.g. `acme-client` — one per app. */
|
|
14
|
+
service: string;
|
|
15
|
+
/** OpenObserve base URL, e.g. `https://o2.example.com`. */
|
|
16
|
+
url?: string;
|
|
17
|
+
/** The project's organization identifier. */
|
|
18
|
+
org?: string;
|
|
19
|
+
/**
|
|
20
|
+
* A RUM client token (Ingestion → RUM). Write-only and built for shipping
|
|
21
|
+
* in a bundle — never the backend's ingestion token.
|
|
22
|
+
*/
|
|
23
|
+
clientToken?: string;
|
|
24
|
+
/** `development`, `production`, … — Vite's `import.meta.env.MODE`. */
|
|
25
|
+
environment?: string;
|
|
26
|
+
version?: string;
|
|
27
|
+
/** Backend origin(s) whose requests should carry trace headers. */
|
|
28
|
+
apiUrls?: string[];
|
|
29
|
+
/** Share of sessions to record for replay, 0–100. Off by default. */
|
|
30
|
+
sessionReplaySampleRate?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let started = false;
|
|
34
|
+
|
|
35
|
+
export function initTelemetry(options: TelemetryOptions): boolean {
|
|
36
|
+
const { url, org, clientToken } = options;
|
|
37
|
+
if (started || !url || !org || !clientToken) return false;
|
|
38
|
+
|
|
39
|
+
// The SDK takes a bare host (no scheme) plus an explicit plain-HTTP flag.
|
|
40
|
+
const { host, protocol } = new URL(url);
|
|
41
|
+
const common = {
|
|
42
|
+
clientToken,
|
|
43
|
+
site: host,
|
|
44
|
+
organizationIdentifier: org,
|
|
45
|
+
insecureHTTP: protocol === 'http:',
|
|
46
|
+
service: options.service,
|
|
47
|
+
env: options.environment,
|
|
48
|
+
version: options.version
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
openobserveRum.init({
|
|
52
|
+
...common,
|
|
53
|
+
applicationId: options.service,
|
|
54
|
+
sessionSampleRate: 100,
|
|
55
|
+
sessionReplaySampleRate: options.sessionReplaySampleRate ?? 0,
|
|
56
|
+
defaultPrivacyLevel: DefaultPrivacyLevel.MASK_USER_INPUT,
|
|
57
|
+
trackResources: true,
|
|
58
|
+
trackLongTasks: true,
|
|
59
|
+
trackUserInteractions: true,
|
|
60
|
+
allowedTracingUrls: (options.apiUrls ?? []).filter(Boolean).map((match) => ({
|
|
61
|
+
match,
|
|
62
|
+
propagatorTypes: ['tracecontext' as const]
|
|
63
|
+
}))
|
|
64
|
+
});
|
|
65
|
+
openobserveLogs.init({ ...common, forwardErrorsToLogs: true });
|
|
66
|
+
started = true;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Report a handled error — e.g. from an error boundary. */
|
|
71
|
+
export function reportError(error: unknown, context?: object): void {
|
|
72
|
+
if (started) openobserveRum.addError(error, context);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Tie the session to a signed-in user (id only), or clear it on logout. */
|
|
76
|
+
export function setTelemetryUser(user: { id: string } | null): void {
|
|
77
|
+
if (!started) return;
|
|
78
|
+
if (user) openobserveRum.setUser({ id: user.id });
|
|
79
|
+
else openobserveRum.clearUser();
|
|
80
|
+
}
|
|
@@ -127,7 +127,7 @@
|
|
|
127
127
|
},
|
|
128
128
|
"pwa": {
|
|
129
129
|
"title": "Installable PWA",
|
|
130
|
-
"description": "vite-plugin-pwa: app-shell offline caching and
|
|
130
|
+
"description": "vite-plugin-pwa: app-shell offline caching, and registerType 'prompt' so a new build waits for the user instead of swapping assets under a running app. The update prompt and install button are yours to build with the plugin's useRegisterSW.",
|
|
131
131
|
"packageJson": {
|
|
132
132
|
"devDependencies": [
|
|
133
133
|
"vite-plugin-pwa"
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
},
|
|
137
137
|
"charts": {
|
|
138
138
|
"title": "Charts",
|
|
139
|
-
"description": "ApexCharts
|
|
139
|
+
"description": "ApexCharts and react-apexcharts, with a theme-aware chart palette (the --chart-* tokens in the UI kit). No chart components ship.",
|
|
140
140
|
"packageJson": {
|
|
141
141
|
"dependencies": [
|
|
142
142
|
"apexcharts",
|
|
@@ -189,6 +189,20 @@
|
|
|
189
189
|
"secrets"
|
|
190
190
|
]
|
|
191
191
|
}
|
|
192
|
+
},
|
|
193
|
+
"telemetry": {
|
|
194
|
+
"title": "Browser monitoring → OpenObserve",
|
|
195
|
+
"description": "OpenObserve RUM and logs: page views, errors (including the error boundary's), slow resources, user actions, the signed-in user id, and traceparent headers on API calls so frontend and backend traces link up. Session replay is off unless a project turns it on. Off at runtime until VITE_OPENOBSERVE_* are set.",
|
|
196
|
+
"default": true,
|
|
197
|
+
"files": [
|
|
198
|
+
"src/shared/telemetry/**"
|
|
199
|
+
],
|
|
200
|
+
"packageJson": {
|
|
201
|
+
"dependencies": [
|
|
202
|
+
"@openobserve/browser-logs",
|
|
203
|
+
"@openobserve/browser-rum"
|
|
204
|
+
]
|
|
205
|
+
}
|
|
192
206
|
}
|
|
193
207
|
},
|
|
194
208
|
"hooks": [
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
# Builds and pushes
|
|
1
|
+
# Builds and pushes every app's image from one Dockerfile.
|
|
2
2
|
#
|
|
3
|
-
#
|
|
4
|
-
# tag is generated once in a separate job and shared by every matrix leg —
|
|
3
|
+
# The tag is generated once in a separate job and shared by every matrix leg;
|
|
5
4
|
# otherwise each app would get its own random tag and there would be no way to
|
|
6
5
|
# deploy a matching set.
|
|
7
6
|
#
|
|
8
7
|
# Vite compiles the VITE_* URLs into each bundle at build time, so these images
|
|
9
|
-
# are specific to one environment.
|
|
8
|
+
# are specific to one environment.
|
|
10
9
|
|
|
11
10
|
name: Build and Push
|
|
12
11
|
|
|
@@ -33,12 +32,10 @@ jobs:
|
|
|
33
32
|
DATE=$(date +'%y_%m_%d')
|
|
34
33
|
RAND=$(printf '%05d' "$((RANDOM % 100000))")
|
|
35
34
|
echo "TAG=${DATE}_${RAND}" >> "$GITHUB_OUTPUT"
|
|
36
|
-
# Derived from `github.repository`
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
# images land as <registry>/acme-corp/web-<app>, matching
|
|
41
|
-
# stack/frontend.compose.yml.
|
|
35
|
+
# Derived from `github.repository` rather than
|
|
36
|
+
# `github.repository_owner`, so this doesn't depend on a context
|
|
37
|
+
# variable Forgejo may not populate. Images land as
|
|
38
|
+
# <registry>/<owner>/web-<app>.
|
|
42
39
|
echo "OWNER=$(echo '${{ github.repository }}' | cut -d/ -f1)" >> "$GITHUB_OUTPUT"
|
|
43
40
|
|
|
44
41
|
build-and-push:
|
|
@@ -49,7 +46,11 @@ jobs:
|
|
|
49
46
|
# failure. Note this means a partial set can be pushed — re-run the job.
|
|
50
47
|
fail-fast: false
|
|
51
48
|
matrix:
|
|
52
|
-
app:
|
|
49
|
+
app:
|
|
50
|
+
- landing # @feature app-landing
|
|
51
|
+
- auth # @feature app-auth
|
|
52
|
+
- client # @feature app-client
|
|
53
|
+
- admin # @feature app-admin
|
|
53
54
|
|
|
54
55
|
steps:
|
|
55
56
|
- name: Checkout repository
|
|
@@ -83,6 +84,9 @@ jobs:
|
|
|
83
84
|
VITE_APP_LANDING=${{ secrets.VITE_APP_LANDING }}
|
|
84
85
|
VITE_APP_SUPPORT_EMAIL=${{ secrets.VITE_APP_SUPPORT_EMAIL }}
|
|
85
86
|
VITE_APP_SITE_URL=${{ secrets.VITE_APP_SITE_URL }}
|
|
87
|
+
VITE_OPENOBSERVE_URL=${{ secrets.VITE_OPENOBSERVE_URL }} # @feature telemetry
|
|
88
|
+
VITE_OPENOBSERVE_ORG=${{ secrets.VITE_OPENOBSERVE_ORG }} # @feature telemetry
|
|
89
|
+
VITE_OPENOBSERVE_CLIENT_TOKEN=${{ secrets.VITE_OPENOBSERVE_CLIENT_TOKEN }} # @feature telemetry
|
|
86
90
|
tags: |
|
|
87
91
|
${{ secrets.REGISTRY }}/${{ needs.meta.outputs.owner }}/web-${{ matrix.app }}:latest
|
|
88
92
|
${{ secrets.REGISTRY }}/${{ needs.meta.outputs.owner }}/web-${{ matrix.app }}:${{ needs.meta.outputs.tag }}
|
|
@@ -9,24 +9,28 @@
|
|
|
9
9
|
# IMPORTANT: Vite inlines `VITE_*` values into the JS bundle at BUILD time, so a
|
|
10
10
|
# built image is pinned to one environment. The URLs below are build args, not
|
|
11
11
|
# runtime env — setting them in compose does nothing. Build one image per
|
|
12
|
-
# environment
|
|
12
|
+
# environment.
|
|
13
13
|
|
|
14
14
|
# ---------- deps ----------
|
|
15
15
|
# Manifests only, so `yarn install` re-runs solely when a dependency changes.
|
|
16
16
|
FROM aurostack.dev/wesnetech/nodejs:24 AS deps
|
|
17
17
|
WORKDIR /repo
|
|
18
18
|
|
|
19
|
+
# One line per workspace: every workspace in the project must be here (yarn
|
|
20
|
+
# install --immutable fails on a lockfile naming a missing one), and nothing
|
|
21
|
+
# else (COPY of a missing file fails the build).
|
|
19
22
|
COPY package.json yarn.lock .yarnrc.yml turbo.json ./
|
|
20
|
-
COPY apps/admin/package.json apps/admin/
|
|
21
|
-
COPY apps/auth/package.json apps/auth/
|
|
22
|
-
COPY apps/client/package.json apps/client/
|
|
23
|
-
COPY apps/landing/package.json apps/landing/
|
|
23
|
+
COPY apps/admin/package.json apps/admin/ # @feature app-admin
|
|
24
|
+
COPY apps/auth/package.json apps/auth/ # @feature app-auth
|
|
25
|
+
COPY apps/client/package.json apps/client/ # @feature app-client
|
|
26
|
+
COPY apps/landing/package.json apps/landing/ # @feature app-landing
|
|
24
27
|
COPY packages/api/package.json packages/api/
|
|
25
28
|
COPY packages/auth/package.json packages/auth/
|
|
26
29
|
COPY packages/config/package.json packages/config/
|
|
27
|
-
COPY packages/hooks/package.json packages/hooks/
|
|
30
|
+
COPY packages/hooks/package.json packages/hooks/ # @feature hooks
|
|
28
31
|
COPY packages/layouts/package.json packages/layouts/
|
|
29
|
-
COPY packages/
|
|
32
|
+
COPY packages/telemetry/package.json packages/telemetry/ # @feature telemetry
|
|
33
|
+
COPY packages/types/package.json packages/types/ # @feature types
|
|
30
34
|
COPY packages/ui/package.json packages/ui/
|
|
31
35
|
|
|
32
36
|
RUN corepack enable && yarn install --immutable
|
|
@@ -56,6 +60,16 @@ ARG VITE_APP_SUPPORT_EMAIL
|
|
|
56
60
|
# generated sitemap.xml. Must be a build arg — .dockerignore excludes .env, so a
|
|
57
61
|
# local value never reaches the image.
|
|
58
62
|
ARG VITE_APP_SITE_URL
|
|
63
|
+
# @feature:start telemetry
|
|
64
|
+
# Browser monitoring. Leave unset and the bundle ships with telemetry off. The
|
|
65
|
+
# client token is a RUM token, write-only and meant to be public.
|
|
66
|
+
ARG VITE_OPENOBSERVE_URL
|
|
67
|
+
ARG VITE_OPENOBSERVE_ORG
|
|
68
|
+
ARG VITE_OPENOBSERVE_CLIENT_TOKEN
|
|
69
|
+
ENV VITE_OPENOBSERVE_URL=$VITE_OPENOBSERVE_URL \
|
|
70
|
+
VITE_OPENOBSERVE_ORG=$VITE_OPENOBSERVE_ORG \
|
|
71
|
+
VITE_OPENOBSERVE_CLIENT_TOKEN=$VITE_OPENOBSERVE_CLIENT_TOKEN
|
|
72
|
+
# @feature:end
|
|
59
73
|
ENV VITE_APP_API_URL=$VITE_APP_API_URL \
|
|
60
74
|
VITE_APP_AUTH_HOST=$VITE_APP_AUTH_HOST \
|
|
61
75
|
VITE_APP_CLIENT_HOST=$VITE_APP_CLIENT_HOST \
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
@@ -52,6 +52,11 @@ const env = normalizeEnv(parseEnvArg());
|
|
|
52
52
|
const envFile = ENV_FILES[env];
|
|
53
53
|
|
|
54
54
|
console.log(`Fetching secrets for '${slug}' (${env}) -> ${envFile}...`);
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// Export first and write only on success: a shell `>` redirect would empty
|
|
56
|
+
// the existing file before infisical had the chance to fail. execSync throws
|
|
57
|
+
// (and the process exits non-zero) when infisical fails.
|
|
58
|
+
const output = execSync(`infisical export --projectId="${PROJECT_ID}" --path="${secretPath}" --env=${env}`, {
|
|
59
|
+
stdio: ['inherit', 'pipe', 'inherit']
|
|
57
60
|
});
|
|
61
|
+
writeFileSync(envFile, output);
|
|
62
|
+
console.log(`Wrote ${envFile}`);
|
|
@@ -14,3 +14,14 @@ VITE_APP_ADMIN_HOST='http://localhost:3003'
|
|
|
14
14
|
|
|
15
15
|
# This app's own dev port.
|
|
16
16
|
PORT='3003'
|
|
17
|
+
|
|
18
|
+
# @feature:start telemetry
|
|
19
|
+
# OpenObserve browser monitoring (RUM): page views, errors, slow requests, and
|
|
20
|
+
# trace headers on API calls. Leave empty and it stays off. The client token is
|
|
21
|
+
# a RUM token (OpenObserve → Ingestion → RUM): write-only and meant to ship in
|
|
22
|
+
# the bundle — never the backend's ingestion token.
|
|
23
|
+
# The instance must list this app's origin in ZO_CORS_ALLOWED_ORIGINS.
|
|
24
|
+
VITE_OPENOBSERVE_URL='https://o2.aurostack.co'
|
|
25
|
+
VITE_OPENOBSERVE_ORG=''
|
|
26
|
+
VITE_OPENOBSERVE_CLIENT_TOKEN=''
|
|
27
|
+
# @feature:end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Suspense, lazy, type ReactNode } from 'react';
|
|
2
2
|
import { createBrowserRouter } from 'react-router';
|
|
3
|
-
import {
|
|
4
|
-
import { AppShell, AppShellSkeleton, ErrorBoundary, NotFound, RouteFallback } from '@acme/layouts';
|
|
3
|
+
import { RoleRoute, signOut, useAuth } from '@acme/auth';
|
|
4
|
+
import { AppShell, AppShellSkeleton, ErrorBoundary, Forbidden, NotFound, RouteFallback } from '@acme/layouts';
|
|
5
5
|
import { NAV_ITEMS } from './nav';
|
|
6
6
|
import { HomeRoute } from '../routes/home';
|
|
7
7
|
|
|
@@ -15,8 +15,8 @@ const lazyRoute = (element: ReactNode) => <Suspense fallback={<RouteFallback />}
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* The shell reads the session itself rather than taking it as a prop —
|
|
18
|
-
* `
|
|
19
|
-
* time this renders.
|
|
18
|
+
* `RoleRoute` has already established the user is signed in with an admin role
|
|
19
|
+
* by the time this renders.
|
|
20
20
|
*
|
|
21
21
|
* `ErrorBoundary` wraps the shell rather than being passed as the route's
|
|
22
22
|
* `errorElement`: it is a plain React error boundary taking `children`, so it
|
|
@@ -35,11 +35,15 @@ export const router = createBrowserRouter([
|
|
|
35
35
|
{
|
|
36
36
|
// `fallback` is required and must not be blank: the session round trip is
|
|
37
37
|
// cross-origin and takes most of a second, and rendering nothing for that
|
|
38
|
-
// long reads as a broken app.
|
|
38
|
+
// long reads as a broken app. Signed-out visitors go to the auth app;
|
|
39
|
+
// signed-in users without an admin role see Forbidden.
|
|
40
|
+
//
|
|
41
|
+
// The gate is UI, not security: every endpoint this console calls must
|
|
42
|
+
// enforce the same role on the API.
|
|
39
43
|
element: (
|
|
40
|
-
<
|
|
44
|
+
<RoleRoute allow={['admin', 'superuser']} fallback={<AppShellSkeleton />} forbidden={<Forbidden />}>
|
|
41
45
|
<Shell />
|
|
42
|
-
</
|
|
46
|
+
</RoleRoute>
|
|
43
47
|
),
|
|
44
48
|
children: [
|
|
45
49
|
{ index: true, element: <HomeRoute /> },
|
|
@@ -10,3 +10,13 @@ export const ADMIN_URL = env.VITE_APP_ADMIN_HOST as string;
|
|
|
10
10
|
export const CLIENT_URL = env.VITE_APP_CLIENT_HOST as string;
|
|
11
11
|
/** The public marketing site (logout destination). */
|
|
12
12
|
export const LANDING_URL = env.VITE_APP_LANDING as string;
|
|
13
|
+
|
|
14
|
+
// @feature:start telemetry
|
|
15
|
+
/** OpenObserve browser monitoring (see @acme/telemetry); unset keeps it off. */
|
|
16
|
+
export const TELEMETRY = {
|
|
17
|
+
url: env.VITE_OPENOBSERVE_URL as string | undefined,
|
|
18
|
+
org: env.VITE_OPENOBSERVE_ORG as string | undefined,
|
|
19
|
+
clientToken: env.VITE_OPENOBSERVE_CLIENT_TOKEN as string | undefined,
|
|
20
|
+
environment: env.MODE
|
|
21
|
+
};
|
|
22
|
+
// @feature:end
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { StrictMode } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { initTelemetry } from '@acme/telemetry'; // @feature telemetry
|
|
4
|
+
import { API_URL, TELEMETRY } from './lib/env'; // @feature telemetry
|
|
3
5
|
import { RouterProvider } from 'react-router';
|
|
4
6
|
import '@acme/ui/globals.css';
|
|
5
7
|
import { Providers } from './app/providers';
|
|
6
8
|
import { router } from './app/router';
|
|
7
9
|
|
|
10
|
+
initTelemetry({ service: 'acme-admin', ...TELEMETRY, apiUrls: [API_URL] }); // @feature telemetry
|
|
11
|
+
|
|
8
12
|
createRoot(document.getElementById('root')!).render(
|
|
9
13
|
<StrictMode>
|
|
10
14
|
<Providers>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
@@ -52,6 +52,11 @@ const env = normalizeEnv(parseEnvArg());
|
|
|
52
52
|
const envFile = ENV_FILES[env];
|
|
53
53
|
|
|
54
54
|
console.log(`Fetching secrets for '${slug}' (${env}) -> ${envFile}...`);
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// Export first and write only on success: a shell `>` redirect would empty
|
|
56
|
+
// the existing file before infisical had the chance to fail. execSync throws
|
|
57
|
+
// (and the process exits non-zero) when infisical fails.
|
|
58
|
+
const output = execSync(`infisical export --projectId="${PROJECT_ID}" --path="${secretPath}" --env=${env}`, {
|
|
59
|
+
stdio: ['inherit', 'pipe', 'inherit']
|
|
57
60
|
});
|
|
61
|
+
writeFileSync(envFile, output);
|
|
62
|
+
console.log(`Wrote ${envFile}`);
|
|
@@ -14,3 +14,14 @@ VITE_APP_ADMIN_HOST='http://localhost:3003'
|
|
|
14
14
|
|
|
15
15
|
# This app's own dev port.
|
|
16
16
|
PORT='3001'
|
|
17
|
+
|
|
18
|
+
# @feature:start telemetry
|
|
19
|
+
# OpenObserve browser monitoring (RUM): page views, errors, slow requests, and
|
|
20
|
+
# trace headers on API calls. Leave empty and it stays off. The client token is
|
|
21
|
+
# a RUM token (OpenObserve → Ingestion → RUM): write-only and meant to ship in
|
|
22
|
+
# the bundle — never the backend's ingestion token.
|
|
23
|
+
# The instance must list this app's origin in ZO_CORS_ALLOWED_ORIGINS.
|
|
24
|
+
VITE_OPENOBSERVE_URL='https://o2.aurostack.co'
|
|
25
|
+
VITE_OPENOBSERVE_ORG=''
|
|
26
|
+
VITE_OPENOBSERVE_CLIENT_TOKEN=''
|
|
27
|
+
# @feature:end
|
|
@@ -6,3 +6,13 @@ export const API_URL = env.VITE_APP_API_URL;
|
|
|
6
6
|
/** Terms and Privacy live on the marketing site, which is a different origin. */
|
|
7
7
|
export const LANDING_URL = env.VITE_APP_LANDING;
|
|
8
8
|
export const AUTH_URL = window.location.origin;
|
|
9
|
+
|
|
10
|
+
// @feature:start telemetry
|
|
11
|
+
/** OpenObserve browser monitoring (see @acme/telemetry); unset keeps it off. */
|
|
12
|
+
export const TELEMETRY = {
|
|
13
|
+
url: env.VITE_OPENOBSERVE_URL as string | undefined,
|
|
14
|
+
org: env.VITE_OPENOBSERVE_ORG as string | undefined,
|
|
15
|
+
clientToken: env.VITE_OPENOBSERVE_CLIENT_TOKEN as string | undefined,
|
|
16
|
+
environment: env.MODE
|
|
17
|
+
};
|
|
18
|
+
// @feature:end
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { StrictMode } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { initTelemetry } from '@acme/telemetry'; // @feature telemetry
|
|
4
|
+
import { API_URL, TELEMETRY } from './lib/env'; // @feature telemetry
|
|
3
5
|
import { RouterProvider } from 'react-router';
|
|
4
6
|
import '@acme/ui/globals.css';
|
|
5
7
|
import { Providers } from './app/providers';
|
|
6
8
|
import { router } from './app/router';
|
|
7
9
|
|
|
10
|
+
initTelemetry({ service: 'acme-auth', ...TELEMETRY, apiUrls: [API_URL] }); // @feature telemetry
|
|
11
|
+
|
|
8
12
|
createRoot(document.getElementById('root')!).render(
|
|
9
13
|
<StrictMode>
|
|
10
14
|
<Providers>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
@@ -52,6 +52,11 @@ const env = normalizeEnv(parseEnvArg());
|
|
|
52
52
|
const envFile = ENV_FILES[env];
|
|
53
53
|
|
|
54
54
|
console.log(`Fetching secrets for '${slug}' (${env}) -> ${envFile}...`);
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// Export first and write only on success: a shell `>` redirect would empty
|
|
56
|
+
// the existing file before infisical had the chance to fail. execSync throws
|
|
57
|
+
// (and the process exits non-zero) when infisical fails.
|
|
58
|
+
const output = execSync(`infisical export --projectId="${PROJECT_ID}" --path="${secretPath}" --env=${env}`, {
|
|
59
|
+
stdio: ['inherit', 'pipe', 'inherit']
|
|
57
60
|
});
|
|
61
|
+
writeFileSync(envFile, output);
|
|
62
|
+
console.log(`Wrote ${envFile}`);
|
|
@@ -14,3 +14,14 @@ VITE_APP_ADMIN_HOST='http://localhost:3003'
|
|
|
14
14
|
|
|
15
15
|
# This app's own dev port.
|
|
16
16
|
PORT='3002'
|
|
17
|
+
|
|
18
|
+
# @feature:start telemetry
|
|
19
|
+
# OpenObserve browser monitoring (RUM): page views, errors, slow requests, and
|
|
20
|
+
# trace headers on API calls. Leave empty and it stays off. The client token is
|
|
21
|
+
# a RUM token (OpenObserve → Ingestion → RUM): write-only and meant to ship in
|
|
22
|
+
# the bundle — never the backend's ingestion token.
|
|
23
|
+
# The instance must list this app's origin in ZO_CORS_ALLOWED_ORIGINS.
|
|
24
|
+
VITE_OPENOBSERVE_URL='https://o2.aurostack.co'
|
|
25
|
+
VITE_OPENOBSERVE_ORG=''
|
|
26
|
+
VITE_OPENOBSERVE_CLIENT_TOKEN=''
|
|
27
|
+
# @feature:end
|
|
@@ -10,3 +10,13 @@ export const CLIENT_URL = env.VITE_APP_CLIENT_HOST as string;
|
|
|
10
10
|
export const ADMIN_URL = env.VITE_APP_ADMIN_HOST as string;
|
|
11
11
|
/** The public marketing site (logout destination). */
|
|
12
12
|
export const LANDING_URL = env.VITE_APP_LANDING as string;
|
|
13
|
+
|
|
14
|
+
// @feature:start telemetry
|
|
15
|
+
/** OpenObserve browser monitoring (see @acme/telemetry); unset keeps it off. */
|
|
16
|
+
export const TELEMETRY = {
|
|
17
|
+
url: env.VITE_OPENOBSERVE_URL as string | undefined,
|
|
18
|
+
org: env.VITE_OPENOBSERVE_ORG as string | undefined,
|
|
19
|
+
clientToken: env.VITE_OPENOBSERVE_CLIENT_TOKEN as string | undefined,
|
|
20
|
+
environment: env.MODE
|
|
21
|
+
};
|
|
22
|
+
// @feature:end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { StrictMode } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { initTelemetry } from '@acme/telemetry'; // @feature telemetry
|
|
4
|
+
import { API_URL, TELEMETRY } from './lib/env'; // @feature telemetry
|
|
3
5
|
import { RouterProvider } from 'react-router';
|
|
4
6
|
import '@acme/ui/globals.css';
|
|
5
7
|
import { Providers } from './app/providers';
|
|
@@ -10,6 +12,8 @@ import { router } from './app/router';
|
|
|
10
12
|
// evaluating and `providers` has already called `configureApi` at module scope,
|
|
11
13
|
// so the fetch races the session check instead of queueing behind it.
|
|
12
14
|
|
|
15
|
+
initTelemetry({ service: 'acme-client', ...TELEMETRY, apiUrls: [API_URL] }); // @feature telemetry
|
|
16
|
+
|
|
13
17
|
createRoot(document.getElementById('root')!).render(
|
|
14
18
|
<StrictMode>
|
|
15
19
|
<Providers>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
|
|
@@ -52,6 +52,11 @@ const env = normalizeEnv(parseEnvArg());
|
|
|
52
52
|
const envFile = ENV_FILES[env];
|
|
53
53
|
|
|
54
54
|
console.log(`Fetching secrets for '${slug}' (${env}) -> ${envFile}...`);
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
// Export first and write only on success: a shell `>` redirect would empty
|
|
56
|
+
// the existing file before infisical had the chance to fail. execSync throws
|
|
57
|
+
// (and the process exits non-zero) when infisical fails.
|
|
58
|
+
const output = execSync(`infisical export --projectId="${PROJECT_ID}" --path="${secretPath}" --env=${env}`, {
|
|
59
|
+
stdio: ['inherit', 'pipe', 'inherit']
|
|
57
60
|
});
|
|
61
|
+
writeFileSync(envFile, output);
|
|
62
|
+
console.log(`Wrote ${envFile}`);
|
|
@@ -18,3 +18,14 @@ PORT='3000'
|
|
|
18
18
|
# Canonical public URL, used for sitemap/robots/OG tags at build time.
|
|
19
19
|
VITE_APP_SITE_URL='http://localhost:3000'
|
|
20
20
|
VITE_APP_SUPPORT_EMAIL='support@example.com'
|
|
21
|
+
|
|
22
|
+
# @feature:start telemetry
|
|
23
|
+
# OpenObserve browser monitoring (RUM): page views, errors, slow requests, and
|
|
24
|
+
# trace headers on API calls. Leave empty and it stays off. The client token is
|
|
25
|
+
# a RUM token (OpenObserve → Ingestion → RUM): write-only and meant to ship in
|
|
26
|
+
# the bundle — never the backend's ingestion token.
|
|
27
|
+
# The instance must list this app's origin in ZO_CORS_ALLOWED_ORIGINS.
|
|
28
|
+
VITE_OPENOBSERVE_URL='https://o2.aurostack.co'
|
|
29
|
+
VITE_OPENOBSERVE_ORG=''
|
|
30
|
+
VITE_OPENOBSERVE_CLIENT_TOKEN=''
|
|
31
|
+
# @feature:end
|
|
@@ -8,3 +8,13 @@ export const CLIENT_HOST = env.VITE_APP_CLIENT_HOST;
|
|
|
8
8
|
* actually posts. Falsy when the build arg is missing; the UI hides the block
|
|
9
9
|
* rather than rendering `mailto:undefined`. */
|
|
10
10
|
export const SUPPORT_EMAIL = env.VITE_APP_SUPPORT_EMAIL;
|
|
11
|
+
|
|
12
|
+
// @feature:start telemetry
|
|
13
|
+
/** OpenObserve browser monitoring (see @acme/telemetry); unset keeps it off. */
|
|
14
|
+
export const TELEMETRY = {
|
|
15
|
+
url: env.VITE_OPENOBSERVE_URL as string | undefined,
|
|
16
|
+
org: env.VITE_OPENOBSERVE_ORG as string | undefined,
|
|
17
|
+
clientToken: env.VITE_OPENOBSERVE_CLIENT_TOKEN as string | undefined,
|
|
18
|
+
environment: env.MODE
|
|
19
|
+
};
|
|
20
|
+
// @feature:end
|