@arcforgelabs/flow 2026.10.33 → 2026.10.35
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 +7 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/server/api/custom-fields.js +115 -0
- package/server/api/invoicing.js +43 -12
- package/server/api/work-moves.js +80 -0
- package/server/app.js +12 -0
- package/server/auth/people.js +35 -0
- package/server/auth/routes.js +14 -0
- package/server/core/custom-fields.js +308 -0
- package/server/core/job-board.js +4 -0
- package/server/core/ledger.js +82 -2
- package/server/core/tracing.js +64 -0
- package/server/db/migrations/0018_custom_fields.sql +78 -0
- package/server/db/migrations/0019_people_views.sql +1 -0
- package/server/db/migrations/meta/0018_snapshot.json +4289 -0
- package/server/db/migrations/meta/0019_snapshot.json +4295 -0
- package/server/db/migrations/meta/_journal.json +14 -0
- package/server/db/schema.js +64 -1
- package/server/index.js +6 -1
- package/server/integrations/port.js +8 -1
- package/server/integrations/seazona/index.js +60 -2
- package/server/integrations/seazona/migrations/0007_stage_moves.sql +18 -0
- package/server/integrations/seazona/migrations/meta/0007_snapshot.json +1519 -0
- package/server/integrations/seazona/migrations/meta/_journal.json +7 -0
- package/server/integrations/seazona/schema.js +26 -0
- package/server/integrations/seazona/settings.js +35 -0
- package/server/integrations/seazona/stage-moves.js +133 -0
- package/server/integrations/seazona/tables.js +2 -0
- package/server/integrations/sentry/index.js +61 -10
- package/server/release-channel.js +8 -6
- package/server/ui/app-page.js +1 -1
- package/server/ui/app.js +40 -2
- package/server/ui/custom-fields.js +190 -0
- package/server/ui/flow.css +128 -10
- package/server/ui/invoice-notes.js +1 -1
- package/server/ui/invoicing.js +185 -48
- package/server/ui/job-board.js +1 -1
- package/server/ui/job-change.js +78 -0
- package/server/ui/job-view.js +119 -0
- package/server/ui/jobs.js +482 -49
- package/server/ui/monitoring.js +44 -2
- package/server/ui/routes.js +1 -1
package/server/ui/monitoring.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
// Errors
|
|
2
|
-
// Sentry project (docs/SENTRY.md). Only while the Sentry
|
|
1
|
+
// Errors, problem reports and performance traces from the browser, to the
|
|
2
|
+
// installation's own Sentry project (docs/SENTRY.md). Only while the Sentry
|
|
3
|
+
// integration is on:
|
|
3
4
|
// GET /api/monitoring says so and gives the project's public DSN. The SDK
|
|
4
5
|
// comes from Sentry's CDN, pinned and checked against its integrity hash.
|
|
5
6
|
// With the integration off, nothing loads and there is no "Report a problem".
|
|
@@ -26,6 +27,36 @@ export function tagNotify(event) {
|
|
|
26
27
|
event.tags = { ...event.tags, notify: email ? 'yes' : 'no' };
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
// A path as a pattern: each id in it becomes ":id", so every invoice's page
|
|
31
|
+
// is one name in a trace. The server names its requests the same way when no
|
|
32
|
+
// route matched (server/core/tracing.js).
|
|
33
|
+
const ID_SEGMENT = /^(\d+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[0-9a-z_-]*\d[0-9a-z_-]{15,})$/i;
|
|
34
|
+
export function routePattern(path) {
|
|
35
|
+
const bare = String(path ?? '').replace(/[?#].*$/, '');
|
|
36
|
+
return bare.split('/').map((part) => (ID_SEGMENT.test(part) ? ':id' : part)).join('/');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// What a trace span keeps: timings, the route, the status. Never a query
|
|
40
|
+
// string (a search can hold a name), a header, a cookie, an address or an
|
|
41
|
+
// email. The person's Flow id stays, as on errors. Used here and by the
|
|
42
|
+
// server (server/integrations/sentry/index.js).
|
|
43
|
+
const DROPPED_ATTRIBUTE = /^(http\.(request|response)\.header\.|client\.|network\.(peer|local)\.|url\.(query|fragment)$|user\.(?!id$))/;
|
|
44
|
+
export function scrubSpan(span) {
|
|
45
|
+
if (!span) return span;
|
|
46
|
+
// "GET /api/search?q=…" loses its query; a database span's SQL keeps its "?".
|
|
47
|
+
const request = /^((?:GET|POST|PUT|PATCH|DELETE) \S*?)[?#]\S*/;
|
|
48
|
+
if (typeof span.name === 'string') span.name = span.name.replace(request, '$1');
|
|
49
|
+
if (typeof span.description === 'string') span.description = span.description.replace(request, '$1');
|
|
50
|
+
const attributes = span.attributes ?? span.data;
|
|
51
|
+
if (attributes) {
|
|
52
|
+
for (const key of Object.keys(attributes)) {
|
|
53
|
+
if (DROPPED_ATTRIBUTE.test(key)) delete attributes[key];
|
|
54
|
+
else if (typeof attributes[key] === 'string' && /(^|\.)url(\.full)?$|^http\.url$/.test(key)) attributes[key] = attributes[key].replace(/[?#].*$/, '');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return span;
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
/** Resolves to a function that opens the "Report a problem" form, or null. */
|
|
30
61
|
export async function startMonitoring({ flow, person }) {
|
|
31
62
|
const config = await flow('/api/monitoring');
|
|
@@ -51,12 +82,23 @@ export async function startMonitoring({ flow, person }) {
|
|
|
51
82
|
submitButtonLabel: 'Send report',
|
|
52
83
|
successMessageText: 'Thank you. Your report is sent.',
|
|
53
84
|
})] : [];
|
|
85
|
+
// Traces: page loads, moves between pages and each click's wait, with the
|
|
86
|
+
// calls to Flow's server joined to the server's own trace. Only when the
|
|
87
|
+
// installation set a share above 0 (SENTRY_TRACES_SAMPLE_RATE).
|
|
88
|
+
const tracesSampleRate = Number(config.tracesSampleRate) || 0;
|
|
89
|
+
if (tracesSampleRate > 0) {
|
|
90
|
+
integrations.push(Sentry.browserTracingIntegration({
|
|
91
|
+
beforeStartSpan: (options) => ({ ...options, name: routePattern(options.name) }),
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
54
94
|
Sentry.init({
|
|
55
95
|
dsn: config.dsn,
|
|
56
96
|
environment: config.environment,
|
|
57
97
|
release: config.release ?? undefined,
|
|
58
98
|
sendDefaultPii: false,
|
|
59
99
|
integrations,
|
|
100
|
+
tracesSampleRate,
|
|
101
|
+
beforeSendSpan: scrubSpan,
|
|
60
102
|
});
|
|
61
103
|
Sentry.getClient()?.on('beforeSendFeedback', tagNotify);
|
|
62
104
|
// Who reported or met the error, so the lab can follow up.
|
package/server/ui/routes.js
CHANGED
|
@@ -20,7 +20,7 @@ const themeDir = join(here, '..', 'theme');
|
|
|
20
20
|
const logoDir = join(here, 'logos');
|
|
21
21
|
const LOGO_FILES = new Set(['xero.svg', 'seazona.svg', 'sentry.svg']);
|
|
22
22
|
const THEME_FILES = new Set(['themes.js', 'color-contrast.js', 'openclaw-palettes.js', 'font-families.js']);
|
|
23
|
-
const UI_FILES = new Set(['flow.css', 'appearance.js', 'clerk-appearance.js', 'app.js', 'invoicing.js', 'invoice-notes.js', 'invoicing-settings.js', 'automations.js', 'invoice-rules.js', 'line-mappings.js', 'price-list.js', 'jobs.js', 'job-board.js', 'monitoring.js', 'search.js', 'alerts.js', 'flow-mark.svg']);
|
|
23
|
+
const UI_FILES = new Set(['flow.css', 'appearance.js', 'clerk-appearance.js', 'app.js', 'invoicing.js', 'invoice-notes.js', 'job-change.js', 'invoicing-settings.js', 'automations.js', 'invoice-rules.js', 'line-mappings.js', 'price-list.js', 'custom-fields.js', 'jobs.js', 'job-board.js', 'job-view.js', 'monitoring.js', 'search.js', 'alerts.js', 'flow-mark.svg']);
|
|
24
24
|
const MAX_AGE = '1h';
|
|
25
25
|
|
|
26
26
|
// Send one named file from dir. Passing dir as root limits the dotfile check
|