-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathnext.config.mjs
More file actions
170 lines (166 loc) · 8.72 KB
/
Copy pathnext.config.mjs
File metadata and controls
170 lines (166 loc) · 8.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
const securityHeaders = [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
// microphone=(self): chat voice-input (STT) uses getUserMedia, which the browser blocks entirely
// unless the origin is permitted here — `(self)` lets OUR page prompt for the mic (same-origin
// only, no third-party/iframe access), so the normal browser permission dialog appears.
// camera=(self) likewise for future voice/video. geolocation stays fully disabled.
{ key: 'Permissions-Policy', value: 'camera=(self), microphone=(self), geolocation=()' },
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
{
key: 'Content-Security-Policy',
value: [
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://us-assets.i.posthog.com",
"style-src 'self' 'unsafe-inline'",
// gateway.getoffgridai.co serves the SeaweedFS file store (images/video previews in Storage,
// knowledge-base files, artifacts) — allow it as an image/media/fetch source.
"img-src 'self' data: blob: https://gateway.getoffgridai.co https://us.i.posthog.com https://us-assets.i.posthog.com",
"media-src 'self' blob: https://gateway.getoffgridai.co",
// data: — fonts are embedded as base64 data: URIs (woff/woff2); without this they're CSP-blocked
// and text falls back to system fonts console-wide.
"font-src 'self' data:",
"connect-src 'self' https://gateway.getoffgridai.co https://us.i.posthog.com https://us-assets.i.posthog.com",
// cal.com booking widget embedded (iframe only) in the sign-in "Book a call" modal. No cal
// script is loaded — script-src stays tight; this only permits framing cal.com's booking page.
"frame-src 'self' https://cal.com https://app.cal.com",
"frame-ancestors 'none'",
].join('; '),
},
];
// ─── T6 RESTful URL hierarchy (task #177) ─────────────────────────────────────────────────────────
// Every route now lives under its section segment (workspace/build/gateway/data/governance/insights/
// operations). URLs are a contract — bookmarks, deep-links, and doc screenshots must keep working —
// so every OLD flat path 301-redirects to its new nested home, including the `/:id`/`/:path*` param
// forms so detail-page deep links survive. Pure config; the folder moves + registry own the live map.
//
// Each entry maps an old top-level segment to its new prefix. We emit two rules per segment: the bare
// path and a wildcard (`/:path*`) so children (detail ids, tab sub-paths, query strings) carry over.
const SECTION_REDIRECTS = [
// Workspace
['/chat', '/workspace/chat'],
['/knowledge', '/workspace/knowledge'],
['/storage', '/workspace/storage'],
['/projects', '/workspace/projects'],
['/prompts', '/workspace/prompts'],
['/artifacts', '/workspace/artifacts'],
// Build
['/studio', '/build/studio'],
['/apps', '/build/apps'],
['/agents', '/build/agents'],
['/agent-runs', '/build/agent-runs'],
['/tools', '/build/tools'],
['/brain', '/build/brain'],
['/evals', '/build/evals'],
['/sandbox', '/build/sandbox'],
['/pipelines', '/build/pipelines'],
// Gateway & Fleet (resolves the AI-Gateway / Gateways / Services IA overlap).
// NB: `/gateway` (the old aggregator page) had NO children AND `/gateway` is now the SECTION
// prefix for the live nested routes (/gateway/services, /gateway/registry, …). A wildcard here
// would hijack those live paths, so `/gateway` is bare-only (see bareOnly below).
['/services', '/gateway/services'],
['/gateway', '/gateway/ai', { bareOnly: true }],
['/gateways', '/gateway/registry'],
['/fleet', '/gateway/fleet'],
['/edge', '/gateway/edge'],
// Data
['/integrations', '/data/integrations'],
['/connectors', '/data/connectors'],
['/data-domains', '/data/domains'],
['/retrieval', '/data/retrieval'],
['/lineage', '/data/lineage'],
['/tool-catalog', '/data/tool-catalog'],
// Governance (landing rename: /control → /governance)
['/control', '/governance'],
['/policy', '/governance/policy'],
['/access', '/governance/access'],
['/guardrails', '/governance/guardrails'],
['/secrets', '/governance/secrets'],
['/regulatory', '/governance/regulatory'],
['/provenance', '/governance/provenance'],
// Insights (landing rename: /observability → /insights)
['/observability', '/insights'],
['/analytics', '/insights/analytics'],
['/drift', '/insights/drift'],
['/finops', '/insights/finops'],
['/accounting', '/insights/accounting'],
['/reports', '/insights/reports'],
['/siem', '/insights/siem'],
['/audit', '/insights/audit'],
// Operations
['/admin', '/operations/admin'],
['/config', '/operations/config'],
['/backups', '/operations/backups'],
['/api-docs', '/operations/api-docs'],
];
function urlRedirects() {
const rules = [];
for (const [from, to, opts] of SECTION_REDIRECTS) {
// bare path (e.g. /policy → /governance/policy)
rules.push({ source: from, destination: to, permanent: true });
// children: detail ids, tab sub-paths, and nested routes (e.g. /gateways/:id →
// /gateway/registry/:id, /pipelines/:id/policy → /build/pipelines/:id/policy). Skipped when the
// old segment now doubles as a live SECTION prefix (opts.bareOnly), so we never hijack a new route.
if (!opts?.bareOnly) {
rules.push({ source: `${from}/:path*`, destination: `${to}/:path*`, permanent: true });
}
}
return rules;
}
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: { ignoreDuringBuilds: true },
experimental: {
// Client-side Router Cache TTL. Next 15 defaults `dynamic` to 0s, so every re-visit to a
// (dynamic) route refetches its RSC payload from the server — brutal here because the console
// is served over a Cloudflare tunnel to an on-prem Mac, so each nav is a full round-trip. Caching
// the payload for a short window makes moving BACK to an already-seen page instant (no server
// hit), while still refreshing within the window so data never goes stale for long.
staleTimes: { dynamic: 30, static: 180 },
// Tree-shake big barrel packages so a page ships only the icons/components it uses, not the whole
// library. @phosphor-icons/react especially is thousands of modules imported by nearly every page.
optimizePackageImports: ['@phosphor-icons/react', '@phosphor-icons/react/dist/ssr', 'recharts', 'date-fns'],
},
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }];
},
async redirects() {
return urlRedirects();
},
// Native / vendored-binary packages — keep them out of the webpack bundle (require at runtime).
// c2pa-node ships a native binding + a vendored sharp; sigstore is required server-side only.
// @offgrid/gateway pulls in @temporalio/worker (swc/wasm native binaries) that webpack cannot
// bundle — keep it external so the node control API routes require it at runtime.
// @temporalio/client is bound only via a dynamic import in the durable agent-runtime adapter
// (src/lib/adapters/agentruntime.ts) — required at runtime on the server path, never bundled.
// @temporalio/worker + /workflow are used only by the standalone worker process (src/worker/,
// scripts/temporal-worker.mts), never by a Next route; external here as belt-and-suspenders so
// their swc/wasm native binaries are never pulled into the webpack bundle.
serverExternalPackages: [
'@lancedb/lancedb',
'c2pa-node',
'sigstore',
'@temporalio/client',
'@temporalio/worker',
'@temporalio/workflow',
// @react-pdf/renderer renders governance PDFs server-side (src/lib/reports/render.tsx). It ships
// native yoga/wasm and reads public/logo.png off disk at render time — keep it out of the webpack
// bundle so `next build` doesn't try to inline those and so the disk read resolves under next start.
'@react-pdf/renderer',
],
// Stable build id so the multiple console instances behind the edge LB produce identical
// asset hashes — otherwise /_next/static/* 404s when a request lands on the other instance.
generateBuildId: () => process.env.OFFGRID_BUILD_ID ?? 'offgrid-onprem',
webpack: (config) => {
// @offgrid/gateway (file: dep) transitively imports @temporalio/worker, which loads swc
// native/wasm binaries webpack cannot bundle. The console only uses gateway's HTTP client
// helpers (clusterModels), never the Temporal worker, so stub that unreachable branch out.
config.resolve.alias = {
...config.resolve.alias,
'@temporalio/worker': false,
};
return config;
},
};
export default nextConfig;