@object-ui/app-shell 5.2.1 → 5.3.0
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/CHANGELOG.md +49 -0
- package/dist/chrome/ErrorBoundary.js +3 -0
- package/dist/console/marketplace/MarkdownText.d.ts +19 -0
- package/dist/console/marketplace/MarkdownText.js +141 -0
- package/dist/console/marketplace/MarketplacePackagePage.js +3 -2
- package/dist/console/marketplace/MarketplacePage.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/observability/index.d.ts +9 -0
- package/dist/observability/index.js +9 -0
- package/dist/observability/sentry.d.ts +46 -0
- package/dist/observability/sentry.js +120 -0
- package/package.json +26 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# @object-ui/app-shell — Changelog
|
|
2
2
|
|
|
3
|
+
## 5.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- efb4c00: feat(observability): Sentry integration + bundle splitting for production launch
|
|
8
|
+
|
|
9
|
+
**Sentry (opt-in via `VITE_SENTRY_DSN`)**
|
|
10
|
+
- New `initSentry()` / `captureError()` / `setSentryUser()` / `getSentry()`
|
|
11
|
+
helpers exported from `@object-ui/app-shell`.
|
|
12
|
+
- Dynamic-import design: when `VITE_SENTRY_DSN` is unset, `@sentry/react`
|
|
13
|
+
is **never fetched** — zero bundle cost for self-hosted users.
|
|
14
|
+
- `ErrorBoundary.componentDidCatch` now best-effort reports to Sentry.
|
|
15
|
+
- Console app calls `initSentry()` before React mount; never blocks first
|
|
16
|
+
paint.
|
|
17
|
+
- Configurable via:
|
|
18
|
+
- `VITE_SENTRY_DSN` — required to enable
|
|
19
|
+
- `VITE_SENTRY_ENVIRONMENT` — defaults to `MODE`
|
|
20
|
+
- `VITE_SENTRY_RELEASE` — defaults to `VITE_APP_VERSION`
|
|
21
|
+
- `VITE_SENTRY_TRACES_SAMPLE_RATE` — defaults to `0.1`
|
|
22
|
+
- `VITE_SENTRY_REPLAY=true` — opt-in to 10% on-error replay
|
|
23
|
+
- Sensitive URL params (`token`, `access_token`, `apiKey`, etc.) are
|
|
24
|
+
stripped from breadcrumb URLs before send.
|
|
25
|
+
|
|
26
|
+
**Bundle splitting**
|
|
27
|
+
- `plugin-dashboard` (8 component types) now lazy-registered via
|
|
28
|
+
`ComponentRegistry.registerLazy()` — only loads on dashboard pages.
|
|
29
|
+
- `plugin-dashboard` and `plugin-report` each get their own chunk
|
|
30
|
+
(previously merged into `plugins-views`).
|
|
31
|
+
- Net first-paint JS reduction: **~200 KB** when the user never visits a
|
|
32
|
+
dashboard or report page.
|
|
33
|
+
- New chunks: `plugin-dashboard` (119 K), `plugin-report` (92 K),
|
|
34
|
+
`vendor-sentry` (346 K raw / 97 K brotli, lazy).
|
|
35
|
+
- `plugins-views` shrinks 387 K → 180 K (now `plugin-list` + `plugin-detail` only).
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- @object-ui/types@5.3.0
|
|
40
|
+
- @object-ui/core@5.3.0
|
|
41
|
+
- @object-ui/i18n@5.3.0
|
|
42
|
+
- @object-ui/react@5.3.0
|
|
43
|
+
- @object-ui/components@5.3.0
|
|
44
|
+
- @object-ui/fields@5.3.0
|
|
45
|
+
- @object-ui/layout@5.3.0
|
|
46
|
+
- @object-ui/data-objectstack@5.3.0
|
|
47
|
+
- @object-ui/auth@5.3.0
|
|
48
|
+
- @object-ui/permissions@5.3.0
|
|
49
|
+
- @object-ui/collaboration@5.3.0
|
|
50
|
+
- @object-ui/providers@5.3.0
|
|
51
|
+
|
|
3
52
|
## 5.2.1
|
|
4
53
|
|
|
5
54
|
### Patch Changes
|
|
@@ -19,6 +19,7 @@ import { Component } from 'react';
|
|
|
19
19
|
import { Button, Empty, EmptyTitle, EmptyDescription } from '@object-ui/components';
|
|
20
20
|
import { AlertTriangle, RotateCcw, Home } from 'lucide-react';
|
|
21
21
|
import { useObjectTranslation } from '@object-ui/i18n';
|
|
22
|
+
import { captureError } from '../observability';
|
|
22
23
|
/** Inner fallback component that uses the i18n hook */
|
|
23
24
|
function DefaultErrorFallback({ error, onReset }) {
|
|
24
25
|
const { t } = useObjectTranslation();
|
|
@@ -42,6 +43,8 @@ export class ErrorBoundary extends Component {
|
|
|
42
43
|
}
|
|
43
44
|
componentDidCatch(error, errorInfo) {
|
|
44
45
|
console.error('[ErrorBoundary] Caught error:', error, errorInfo);
|
|
46
|
+
// Best-effort: report to Sentry if initialized. No-op when DSN absent.
|
|
47
|
+
captureError(error, { componentStack: errorInfo.componentStack });
|
|
45
48
|
this.props.onError?.(error, errorInfo);
|
|
46
49
|
}
|
|
47
50
|
render() {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny CommonMark-subset renderer for marketplace READMEs.
|
|
3
|
+
*
|
|
4
|
+
* Covers what app authors actually write in their README field:
|
|
5
|
+
* • `#`, `##`, `###` headings
|
|
6
|
+
* • Blank-line-separated paragraphs
|
|
7
|
+
* • `- ` / `* ` bulleted lists (single level)
|
|
8
|
+
* • `1. ` numbered lists (single level)
|
|
9
|
+
* • `**bold**`, `*italic*`, `` `code` ``, `[text](url)` inline
|
|
10
|
+
* • Triple-backtick fenced code blocks
|
|
11
|
+
*
|
|
12
|
+
* Deliberately NOT a full Markdown engine — we want zero new deps and
|
|
13
|
+
* predictable output. If a README really needs tables or images, the
|
|
14
|
+
* publisher can host their own docs and link via `homepage_url`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function MarkdownText({ source, className }: {
|
|
17
|
+
source: string;
|
|
18
|
+
className?: string;
|
|
19
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Tiny CommonMark-subset renderer for marketplace READMEs.
|
|
4
|
+
*
|
|
5
|
+
* Covers what app authors actually write in their README field:
|
|
6
|
+
* • `#`, `##`, `###` headings
|
|
7
|
+
* • Blank-line-separated paragraphs
|
|
8
|
+
* • `- ` / `* ` bulleted lists (single level)
|
|
9
|
+
* • `1. ` numbered lists (single level)
|
|
10
|
+
* • `**bold**`, `*italic*`, `` `code` ``, `[text](url)` inline
|
|
11
|
+
* • Triple-backtick fenced code blocks
|
|
12
|
+
*
|
|
13
|
+
* Deliberately NOT a full Markdown engine — we want zero new deps and
|
|
14
|
+
* predictable output. If a README really needs tables or images, the
|
|
15
|
+
* publisher can host their own docs and link via `homepage_url`.
|
|
16
|
+
*/
|
|
17
|
+
import { Fragment } from 'react';
|
|
18
|
+
function parseBlocks(src) {
|
|
19
|
+
const lines = src.replace(/\r\n/g, '\n').split('\n');
|
|
20
|
+
const blocks = [];
|
|
21
|
+
let i = 0;
|
|
22
|
+
while (i < lines.length) {
|
|
23
|
+
const line = lines[i];
|
|
24
|
+
// Fenced code block
|
|
25
|
+
if (line.startsWith('```')) {
|
|
26
|
+
const lang = line.slice(3).trim();
|
|
27
|
+
i++;
|
|
28
|
+
const buf = [];
|
|
29
|
+
while (i < lines.length && !lines[i].startsWith('```')) {
|
|
30
|
+
buf.push(lines[i]);
|
|
31
|
+
i++;
|
|
32
|
+
}
|
|
33
|
+
i++; // closing fence
|
|
34
|
+
blocks.push({ type: 'code', text: buf.join('\n'), lang });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
// Headings
|
|
38
|
+
const h = /^(#{1,3})\s+(.*)$/.exec(line);
|
|
39
|
+
if (h) {
|
|
40
|
+
const level = h[1].length;
|
|
41
|
+
blocks.push({ type: `h${level}`, text: h[2].trim() });
|
|
42
|
+
i++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
// Bullet list
|
|
46
|
+
if (/^[-*]\s+/.test(line)) {
|
|
47
|
+
const items = [];
|
|
48
|
+
while (i < lines.length && /^[-*]\s+/.test(lines[i])) {
|
|
49
|
+
items.push(lines[i].replace(/^[-*]\s+/, ''));
|
|
50
|
+
i++;
|
|
51
|
+
}
|
|
52
|
+
blocks.push({ type: 'ul', items });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// Numbered list
|
|
56
|
+
if (/^\d+\.\s+/.test(line)) {
|
|
57
|
+
const items = [];
|
|
58
|
+
while (i < lines.length && /^\d+\.\s+/.test(lines[i])) {
|
|
59
|
+
items.push(lines[i].replace(/^\d+\.\s+/, ''));
|
|
60
|
+
i++;
|
|
61
|
+
}
|
|
62
|
+
blocks.push({ type: 'ol', items });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Blank line → flush
|
|
66
|
+
if (!line.trim()) {
|
|
67
|
+
i++;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
// Paragraph (collect until blank / heading / list)
|
|
71
|
+
const buf = [];
|
|
72
|
+
while (i < lines.length &&
|
|
73
|
+
lines[i].trim() &&
|
|
74
|
+
!/^#{1,3}\s+/.test(lines[i]) &&
|
|
75
|
+
!/^[-*]\s+/.test(lines[i]) &&
|
|
76
|
+
!/^\d+\.\s+/.test(lines[i]) &&
|
|
77
|
+
!lines[i].startsWith('```')) {
|
|
78
|
+
buf.push(lines[i]);
|
|
79
|
+
i++;
|
|
80
|
+
}
|
|
81
|
+
blocks.push({ type: 'p', text: buf.join(' ') });
|
|
82
|
+
}
|
|
83
|
+
return blocks;
|
|
84
|
+
}
|
|
85
|
+
// Inline pass: **bold**, *italic*, `code`, [text](url). Order matters
|
|
86
|
+
// so we tokenize once and replace by position.
|
|
87
|
+
function renderInline(input) {
|
|
88
|
+
const out = [];
|
|
89
|
+
let remaining = input;
|
|
90
|
+
let key = 0;
|
|
91
|
+
const patterns = [
|
|
92
|
+
{ re: /`([^`]+)`/, render: (m) => _jsx("code", { className: "rounded bg-muted px-1.5 py-0.5 font-mono text-[0.85em]", children: m[1] }, `k${key++}`) },
|
|
93
|
+
{ re: /\*\*([^*]+)\*\*/, render: (m) => _jsx("strong", { className: "font-semibold text-foreground", children: m[1] }, `k${key++}`) },
|
|
94
|
+
{ re: /\*([^*]+)\*/, render: (m) => _jsx("em", { children: m[1] }, `k${key++}`) },
|
|
95
|
+
{ re: /\[([^\]]+)\]\(([^)]+)\)/, render: (m) => (_jsx("a", { href: m[2], target: "_blank", rel: "noopener noreferrer", className: "text-violet-600 underline-offset-2 hover:underline dark:text-violet-400", children: m[1] }, `k${key++}`)) },
|
|
96
|
+
];
|
|
97
|
+
while (remaining) {
|
|
98
|
+
let bestIdx = -1;
|
|
99
|
+
let bestMatch = null;
|
|
100
|
+
let bestPattern = null;
|
|
101
|
+
for (const p of patterns) {
|
|
102
|
+
const m = p.re.exec(remaining);
|
|
103
|
+
if (m && (bestIdx === -1 || m.index < bestIdx)) {
|
|
104
|
+
bestIdx = m.index;
|
|
105
|
+
bestMatch = m;
|
|
106
|
+
bestPattern = p;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (!bestMatch || !bestPattern) {
|
|
110
|
+
out.push(remaining);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
if (bestIdx > 0)
|
|
114
|
+
out.push(remaining.slice(0, bestIdx));
|
|
115
|
+
out.push(bestPattern.render(bestMatch));
|
|
116
|
+
remaining = remaining.slice(bestIdx + bestMatch[0].length);
|
|
117
|
+
}
|
|
118
|
+
return _jsx(_Fragment, { children: out.map((n, i) => _jsx(Fragment, { children: n }, i)) });
|
|
119
|
+
}
|
|
120
|
+
export function MarkdownText({ source, className }) {
|
|
121
|
+
const blocks = parseBlocks(source);
|
|
122
|
+
return (_jsx("div", { className: `flex flex-col gap-4 text-sm leading-relaxed text-foreground/90 ${className ?? ''}`, children: blocks.map((b, i) => {
|
|
123
|
+
switch (b.type) {
|
|
124
|
+
case 'h1':
|
|
125
|
+
return _jsx("h2", { className: "mt-2 text-xl font-bold tracking-tight", children: renderInline(b.text ?? '') }, i);
|
|
126
|
+
case 'h2':
|
|
127
|
+
return _jsx("h3", { className: "mt-3 text-base font-semibold tracking-tight", children: renderInline(b.text ?? '') }, i);
|
|
128
|
+
case 'h3':
|
|
129
|
+
return _jsx("h4", { className: "mt-2 text-sm font-semibold tracking-tight text-foreground/80", children: renderInline(b.text ?? '') }, i);
|
|
130
|
+
case 'p':
|
|
131
|
+
return _jsx("p", { children: renderInline(b.text ?? '') }, i);
|
|
132
|
+
case 'ul':
|
|
133
|
+
return (_jsx("ul", { className: "ml-1 flex list-none flex-col gap-1.5", children: (b.items ?? []).map((it, j) => (_jsxs("li", { className: "flex gap-2", children: [_jsx("span", { "aria-hidden": "true", className: "mt-1.5 inline-block h-1.5 w-1.5 shrink-0 rounded-full bg-violet-500/70" }), _jsx("span", { children: renderInline(it) })] }, j))) }, i));
|
|
134
|
+
case 'ol':
|
|
135
|
+
return (_jsx("ol", { className: "ml-5 flex list-decimal flex-col gap-1.5 marker:text-muted-foreground", children: (b.items ?? []).map((it, j) => (_jsx("li", { className: "pl-1", children: renderInline(it) }, j))) }, i));
|
|
136
|
+
case 'code':
|
|
137
|
+
return (_jsx("pre", { className: "overflow-x-auto rounded-lg border bg-muted/40 p-3 text-xs leading-relaxed", children: _jsx("code", { className: "font-mono", children: b.text }) }, i));
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}) }));
|
|
141
|
+
}
|
|
@@ -10,6 +10,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
|
|
10
10
|
import { Button, Badge, Card, CardContent, CardHeader, CardTitle, Skeleton, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Label, Checkbox, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@object-ui/components';
|
|
11
11
|
import { ArrowLeft, ExternalLink, Download, AlertCircle, Package, Trash2 } from 'lucide-react';
|
|
12
12
|
import { PackageIcon } from './PackageIcon';
|
|
13
|
+
import { MarkdownText } from './MarkdownText';
|
|
13
14
|
import { getMarketplacePackage, installPackage, installLocal, uninstallLocal, listLocalInstalls, listCloudEnvironments, listInstallableOrgIds, cloudInstallDeepLink, } from './marketplaceApi';
|
|
14
15
|
export function MarketplacePackagePage() {
|
|
15
16
|
const navigate = useNavigate();
|
|
@@ -190,10 +191,10 @@ export function MarketplacePackagePage() {
|
|
|
190
191
|
const pkg = data.package;
|
|
191
192
|
const latestVersion = pkg.latest_version?.version ?? data.versions[0]?.version ?? null;
|
|
192
193
|
const localInstall = localInstalls.find((i) => i.manifestId === pkg.manifest_id) ?? null;
|
|
193
|
-
return (_jsxs("div", { className: "flex flex-col gap-6 p-4 sm:p-6 max-w-5xl", children: [_jsxs(Button, { variant: "ghost", size: "sm", className: "self-start", onClick: () => navigate(`${basePath}/system/marketplace`), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Back to marketplace"] }), _jsxs("div", { className: "flex items-start gap-
|
|
194
|
+
return (_jsxs("div", { className: "flex flex-col gap-6 p-4 sm:p-6 max-w-5xl", children: [_jsxs(Button, { variant: "ghost", size: "sm", className: "self-start", onClick: () => navigate(`${basePath}/system/marketplace`), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Back to marketplace"] }), _jsxs("div", { className: "flex items-start gap-5 flex-wrap rounded-2xl border bg-gradient-to-br from-primary/5 via-background to-background p-6", children: [_jsx(PackageIcon, { iconUrl: pkg.icon_url, displayName: pkg.display_name, manifestId: pkg.manifest_id, className: "h-20 w-20 rounded-2xl shadow-sm ring-1 ring-border", initialClassName: "text-3xl font-bold" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("h1", { className: "text-3xl font-bold tracking-tight truncate", children: pkg.display_name || pkg.manifest_id }), _jsxs("div", { className: "text-sm text-muted-foreground mt-2 flex flex-wrap items-center gap-2", children: [_jsx("code", { className: "font-mono text-xs px-1.5 py-0.5 rounded bg-muted", children: pkg.manifest_id }), latestVersion && (_jsxs(Badge, { variant: "outline", children: ["v", latestVersion] })), pkg.publisher && pkg.publisher !== 'private' && (_jsx(Badge, { variant: pkg.publisher === 'objectstack' ? 'default' : 'secondary', children: pkg.publisher })), pkg.category && _jsx(Badge, { variant: "outline", children: pkg.category }), pkg.license && _jsx(Badge, { variant: "outline", className: "font-normal", children: pkg.license }), localInstall && (_jsxs(Badge, { variant: "default", className: "bg-green-600 hover:bg-green-600", children: ["Installed \u00B7 v", localInstall.version] }))] }), pkg.description && (_jsx("p", { className: "text-base text-foreground/80 mt-4 max-w-2xl leading-relaxed", children: pkg.description }))] }), _jsxs("div", { className: "flex flex-col gap-2 shrink-0 min-w-[14rem]", children: [_jsxs(Button, { onClick: doInstallLocal, disabled: !latestVersion || installingLocal, size: "lg", children: [_jsx(Download, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), installingLocal
|
|
194
195
|
? 'Working…'
|
|
195
196
|
: localInstall
|
|
196
197
|
? `Reinstall to this runtime`
|
|
197
|
-
: 'Install to this runtime'] }), localInstall && (_jsxs(Button, { variant: "outline", onClick: doUninstallLocal, disabled: installingLocal, children: [_jsx(Trash2, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Uninstall from this runtime"] })), _jsxs(Button, { variant: "
|
|
198
|
+
: 'Install to this runtime'] }), localInstall && (_jsxs(Button, { variant: "outline", onClick: doUninstallLocal, disabled: installingLocal, children: [_jsx(Trash2, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Uninstall from this runtime"] })), _jsxs(Button, { variant: "ghost", onClick: openInstall, disabled: !latestVersion, size: "sm", children: [_jsx(Download, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Install to cloud environment\u2026"] }), pkg.homepage_url && (_jsx("a", { href: pkg.homepage_url, target: "_blank", rel: "noopener noreferrer", children: _jsxs(Button, { variant: "ghost", size: "sm", className: "w-full", children: [_jsx(ExternalLink, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Homepage"] }) })), localResult && (_jsx("div", { className: `rounded-md border p-2 text-xs whitespace-pre-wrap ${localResult.ok ? 'border-green-500/30 bg-green-500/5 text-green-700 dark:text-green-400' : 'border-destructive/30 bg-destructive/5 text-destructive'}`, children: localResult.message }))] })] }), _jsxs("div", { className: "grid gap-4 lg:grid-cols-3", children: [_jsx("div", { className: "lg:col-span-2 space-y-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "About" }) }), _jsx(CardContent, { children: pkg.readme ? (_jsx(MarkdownText, { source: pkg.readme })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "No readme provided." })) })] }) }), _jsx("div", { className: "space-y-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Versions" }) }), _jsx(CardContent, { children: data.versions.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "No approved versions." })) : (_jsx("ul", { className: "space-y-2", children: data.versions.map((v) => (_jsxs("li", { className: "flex items-center justify-between gap-2 text-sm", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx(Package, { className: "h-3.5 w-3.5 text-muted-foreground", "aria-hidden": "true" }), _jsxs("code", { className: "font-mono", children: ["v", v.version] }), v.is_prerelease && _jsx(Badge, { variant: "outline", className: "text-xs", children: "pre" })] }), _jsx("span", { className: "text-xs text-muted-foreground", children: v.published_at ? new Date(v.published_at).toLocaleDateString() : '—' })] }, v.id))) })) })] }) })] }), _jsx(Dialog, { open: installOpen, onOpenChange: (o) => { setInstallOpen(o); if (!o)
|
|
198
199
|
setInstallResult(null); }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Install ", pkg.display_name || pkg.manifest_id] }), _jsx(DialogDescription, { children: "Choose an environment to install this app into. You need to be signed into ObjectStack Cloud." })] }), envsLoading ? (_jsx(Skeleton, { className: "h-10 w-full" })) : envsError ? (_jsxs("div", { className: "rounded-md border border-amber-500/30 bg-amber-500/5 p-3 text-sm space-y-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx(AlertCircle, { className: "h-4 w-4 mt-0.5 text-amber-600", "aria-hidden": "true" }), _jsx("div", { className: "flex-1", children: envsError })] }), _jsx("a", { href: cloudInstallDeepLink(pkg.id), target: "_blank", rel: "noopener noreferrer", children: _jsxs(Button, { variant: "outline", size: "sm", className: "w-full", children: [_jsx(ExternalLink, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Open on cloud"] }) })] })) : envs.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "No environments found in your active organization." })) : (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "env-select", children: "Environment" }), _jsxs(Select, { value: selectedEnv, onValueChange: setSelectedEnv, children: [_jsx(SelectTrigger, { id: "env-select", children: _jsx(SelectValue, { placeholder: "Pick an environment" }) }), _jsx(SelectContent, { children: envs.map((e) => (_jsxs(SelectItem, { value: e.id, children: [e.display_name || e.hostname || e.id, e.plan && _jsxs("span", { className: "text-muted-foreground", children: [" \u00B7 ", e.plan] })] }, e.id))) })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: "seed", checked: seedSampleData, onCheckedChange: (c) => setSeedSampleData(c === true) }), _jsx(Label, { htmlFor: "seed", className: "text-sm font-normal cursor-pointer", children: "Include sample data" })] })] })), installResult && (_jsx("div", { className: `rounded-md border p-3 text-sm ${installResult.ok ? 'border-green-500/30 bg-green-500/5 text-green-700' : 'border-destructive/30 bg-destructive/5 text-destructive'}`, children: installResult.message })), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setInstallOpen(false), children: "Close" }), !envsError && (_jsx(Button, { onClick: doInstall, disabled: !selectedEnv || installing || installResult?.ok === true, children: installing ? 'Installing…' : 'Install' }))] })] }) })] }));
|
|
199
200
|
}
|
|
@@ -82,7 +82,7 @@ export function MarketplacePage() {
|
|
|
82
82
|
return hay.includes(q);
|
|
83
83
|
});
|
|
84
84
|
}, [items, query, category]);
|
|
85
|
-
return (_jsxs("div", { className: "flex flex-col gap-6 p-4 sm:p-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Store, { className: "h-6 w-6 text-primary", "aria-hidden": "true" }), _jsx("h1", { className: "text-xl sm:text-2xl font-bold tracking-tight", children: "App Marketplace" })] }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Browse approved apps published to the ObjectStack catalog. Click an app to view details and install it into one of your environments." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => navigate(`${basePath}/system/marketplace/installed`), children: [_jsx(Settings, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Installed", installed.length > 0 ? ` (${installed.length})` : ''] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void load(), disabled: loading, children: [_jsx(RefreshCcw, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Refresh"] })] })] }), _jsxs("div", { className: "flex flex-col sm:flex-row gap-3", children: [_jsxs("div", { className: "relative flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground", "aria-hidden": "true" }), _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search apps by name or manifest ID\u2026", className: "pl-9", "aria-label": "Search marketplace apps" })] }), categories.length > 0 && (_jsxs("div", { className: "flex flex-wrap gap-1.5", children: [_jsx(Button, { size: "sm", variant: category === '' ? 'default' : 'outline', onClick: () => setCategory(''), children: "All" }), categories.map((cat) => (_jsx(Button, { size: "sm", variant: category === cat ? 'default' : 'outline', onClick: () => setCategory(cat), children: cat }, cat)))] }))] }), error && (_jsxs("div", { className: "flex items-start gap-3 rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 mt-0.5 text-destructive", "aria-hidden": "true" }), _jsxs("div", { children: [_jsx("div", { className: "font-medium text-destructive", children: "Failed to load marketplace" }), _jsx("div", { className: "text-muted-foreground mt-1", children: error }), _jsxs("div", { className: "text-xs text-muted-foreground mt-2", children: ["
|
|
85
|
+
return (_jsxs("div", { className: "flex flex-col gap-6 p-4 sm:p-6", children: [_jsxs("div", { className: "flex items-start justify-between gap-4 flex-wrap", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Store, { className: "h-6 w-6 text-primary", "aria-hidden": "true" }), _jsx("h1", { className: "text-xl sm:text-2xl font-bold tracking-tight", children: "App Marketplace" })] }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Browse approved apps published to the ObjectStack catalog. Click an app to view details and install it into one of your environments." })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => navigate(`${basePath}/system/marketplace/installed`), children: [_jsx(Settings, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Installed", installed.length > 0 ? ` (${installed.length})` : ''] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void load(), disabled: loading, children: [_jsx(RefreshCcw, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Refresh"] })] })] }), _jsxs("div", { className: "flex flex-col sm:flex-row gap-3", children: [_jsxs("div", { className: "relative flex-1", children: [_jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground", "aria-hidden": "true" }), _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search apps by name or manifest ID\u2026", className: "pl-9", "aria-label": "Search marketplace apps" })] }), categories.length > 0 && (_jsxs("div", { className: "flex flex-wrap gap-1.5", children: [_jsx(Button, { size: "sm", variant: category === '' ? 'default' : 'outline', onClick: () => setCategory(''), children: "All" }), categories.map((cat) => (_jsx(Button, { size: "sm", variant: category === cat ? 'default' : 'outline', onClick: () => setCategory(cat), children: cat }, cat)))] }))] }), error && (_jsxs("div", { className: "flex items-start gap-3 rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 mt-0.5 text-destructive", "aria-hidden": "true" }), _jsxs("div", { children: [_jsx("div", { className: "font-medium text-destructive", children: "Failed to load marketplace" }), _jsx("div", { className: "text-muted-foreground mt-1", children: error }), _jsxs("div", { className: "text-xs text-muted-foreground mt-2", children: ["By default this runtime points at the public ObjectStack cloud. Check the runtime is online, or override ", _jsx("code", { className: "font-mono", children: "OS_CLOUD_URL" }), " to point at a self-hosted control plane."] })] })] })), loading && items.length === 0 ? (_jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, i) => (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "pb-2", children: [_jsx(Skeleton, { className: "h-10 w-10 rounded-lg mb-2" }), _jsx(Skeleton, { className: "h-5 w-3/4" }), _jsx(Skeleton, { className: "h-4 w-1/2 mt-1" })] }), _jsx(CardContent, { children: _jsx(Skeleton, { className: "h-12 w-full" }) })] }, i))) })) : filtered.length === 0 ? (_jsx("div", { className: "text-center py-12 text-sm text-muted-foreground", children: items.length === 0 ? 'No apps have been approved for the marketplace yet.' : 'No apps match your filters.' })) : (_jsx("div", { className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3", children: filtered.map((pkg) => {
|
|
86
86
|
const localEntry = installedByManifestId.get(pkg.manifest_id);
|
|
87
87
|
return (_jsxs(Card, { className: "cursor-pointer transition-colors hover:bg-accent/50 flex flex-col", onClick: () => navigate(`${basePath}/system/marketplace/${pkg.id}`), role: "link", tabIndex: 0, onKeyDown: (e) => {
|
|
88
88
|
if (e.key === 'Enter' || e.key === ' ') {
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export { ConsoleShell, ConnectedShell, RequireOrganization, AuthenticatedRoute,
|
|
|
18
18
|
export { ConsoleLayout, AppHeader, AppSidebar, UnifiedSidebar, AppSwitcher, ConnectionStatus, ActivityFeed, LocaleSwitcher, ModeToggle, AuthPageLayout, } from './layout';
|
|
19
19
|
export type { ActivityItem } from './layout';
|
|
20
20
|
export { CommandPalette, KeyboardShortcutsDialog, OnboardingWalkthrough, ConditionalAuthWrapper, ConsoleToaster, RouteFader, toastWithUndo, type ToastWithUndoOptions, ErrorBoundary, LoadingScreen, ThemeProvider, useTheme, } from './chrome';
|
|
21
|
+
export { initSentry, captureError, setSentryUser, getSentry } from './observability';
|
|
21
22
|
export { ObjectView, RecordDetailView, RecordFormPage, DashboardView, PageView, ReportView, SearchResultsPage, ViewConfigPanel, } from './views';
|
|
22
23
|
export type { RecordFormPageProps } from './views';
|
|
23
24
|
export { useFavorites, useMetadataService, useNavPins, useNavigationSync, NavigationSyncEffect, addNavigationItem, removeNavigationItems, renameNavigationItems, navigationEqual, generateNavId, useResponsiveSidebar, } from './hooks';
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,8 @@ export { ConsoleShell, ConnectedShell, RequireOrganization, AuthenticatedRoute,
|
|
|
20
20
|
export { ConsoleLayout, AppHeader, AppSidebar, UnifiedSidebar, AppSwitcher, ConnectionStatus, ActivityFeed, LocaleSwitcher, ModeToggle, AuthPageLayout, } from './layout';
|
|
21
21
|
// Top-level chrome (dialogs, providers, error boundaries)
|
|
22
22
|
export { CommandPalette, KeyboardShortcutsDialog, OnboardingWalkthrough, ConditionalAuthWrapper, ConsoleToaster, RouteFader, toastWithUndo, ErrorBoundary, LoadingScreen, ThemeProvider, useTheme, } from './chrome';
|
|
23
|
+
// Observability — Sentry integration, opt-in via VITE_SENTRY_DSN
|
|
24
|
+
export { initSentry, captureError, setSentryUser, getSentry } from './observability';
|
|
23
25
|
// Standard inner-SPA views
|
|
24
26
|
export { ObjectView, RecordDetailView, RecordFormPage, DashboardView, PageView, ReportView, SearchResultsPage, ViewConfigPanel, } from './views';
|
|
25
27
|
// Hooks
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability primitives — Sentry integration.
|
|
3
|
+
*
|
|
4
|
+
* All exports are no-op safe when no DSN is configured. See sentry.ts for
|
|
5
|
+
* configuration via `VITE_SENTRY_*` envvars.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export { initSentry, captureError, setSentryUser, getSentry } from './sentry';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability primitives — Sentry integration.
|
|
3
|
+
*
|
|
4
|
+
* All exports are no-op safe when no DSN is configured. See sentry.ts for
|
|
5
|
+
* configuration via `VITE_SENTRY_*` envvars.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export { initSentry, captureError, setSentryUser, getSentry } from './sentry';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentry integration — opt-in via `VITE_SENTRY_DSN`.
|
|
3
|
+
*
|
|
4
|
+
* Design goals:
|
|
5
|
+
* - **Zero cost when disabled.** `@sentry/react` is dynamically imported only
|
|
6
|
+
* when a DSN is configured, so apps without Sentry pay zero bundle bytes.
|
|
7
|
+
* - **Graceful degradation.** If init fails (network, CSP, etc.) we log a
|
|
8
|
+
* warning and continue — the host app must still render.
|
|
9
|
+
* - **Sensible defaults.** 10% transaction sampling, no session replay,
|
|
10
|
+
* `release` + `environment` pulled from Vite envvars.
|
|
11
|
+
*
|
|
12
|
+
* Env vars consumed (all optional):
|
|
13
|
+
* - `VITE_SENTRY_DSN` — DSN; absent disables the integration entirely
|
|
14
|
+
* - `VITE_SENTRY_ENVIRONMENT` — defaults to `MODE` (production/development)
|
|
15
|
+
* - `VITE_SENTRY_RELEASE` — defaults to `VITE_APP_VERSION` or `unknown`
|
|
16
|
+
* - `VITE_SENTRY_TRACES_SAMPLE_RATE` — defaults to `0.1`
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
type SentryModule = typeof import('@sentry/react');
|
|
21
|
+
/**
|
|
22
|
+
* Returns the loaded Sentry module, or `null` if Sentry was never initialized
|
|
23
|
+
* (e.g. DSN missing). Callers must handle the null case.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getSentry(): SentryModule | null;
|
|
26
|
+
/**
|
|
27
|
+
* Initializes Sentry if `VITE_SENTRY_DSN` is configured. Safe to call multiple
|
|
28
|
+
* times — only the first invocation runs.
|
|
29
|
+
*
|
|
30
|
+
* @returns `true` if Sentry was initialized, `false` if disabled or failed.
|
|
31
|
+
*/
|
|
32
|
+
export declare function initSentry(): Promise<boolean>;
|
|
33
|
+
/**
|
|
34
|
+
* Reports an error to Sentry if initialized; otherwise no-op. Use this from
|
|
35
|
+
* ErrorBoundary or any catch block where you want best-effort reporting.
|
|
36
|
+
*/
|
|
37
|
+
export declare function captureError(error: unknown, context?: Record<string, unknown>): void;
|
|
38
|
+
/**
|
|
39
|
+
* Sets the active user context for subsequent events. Pass `null` on logout.
|
|
40
|
+
*/
|
|
41
|
+
export declare function setSentryUser(user: {
|
|
42
|
+
id?: string;
|
|
43
|
+
email?: string;
|
|
44
|
+
username?: string;
|
|
45
|
+
} | null): void;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sentry integration — opt-in via `VITE_SENTRY_DSN`.
|
|
3
|
+
*
|
|
4
|
+
* Design goals:
|
|
5
|
+
* - **Zero cost when disabled.** `@sentry/react` is dynamically imported only
|
|
6
|
+
* when a DSN is configured, so apps without Sentry pay zero bundle bytes.
|
|
7
|
+
* - **Graceful degradation.** If init fails (network, CSP, etc.) we log a
|
|
8
|
+
* warning and continue — the host app must still render.
|
|
9
|
+
* - **Sensible defaults.** 10% transaction sampling, no session replay,
|
|
10
|
+
* `release` + `environment` pulled from Vite envvars.
|
|
11
|
+
*
|
|
12
|
+
* Env vars consumed (all optional):
|
|
13
|
+
* - `VITE_SENTRY_DSN` — DSN; absent disables the integration entirely
|
|
14
|
+
* - `VITE_SENTRY_ENVIRONMENT` — defaults to `MODE` (production/development)
|
|
15
|
+
* - `VITE_SENTRY_RELEASE` — defaults to `VITE_APP_VERSION` or `unknown`
|
|
16
|
+
* - `VITE_SENTRY_TRACES_SAMPLE_RATE` — defaults to `0.1`
|
|
17
|
+
*
|
|
18
|
+
* @module
|
|
19
|
+
*/
|
|
20
|
+
let sentryModule = null;
|
|
21
|
+
let initPromise = null;
|
|
22
|
+
/**
|
|
23
|
+
* Returns the loaded Sentry module, or `null` if Sentry was never initialized
|
|
24
|
+
* (e.g. DSN missing). Callers must handle the null case.
|
|
25
|
+
*/
|
|
26
|
+
export function getSentry() {
|
|
27
|
+
return sentryModule;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Initializes Sentry if `VITE_SENTRY_DSN` is configured. Safe to call multiple
|
|
31
|
+
* times — only the first invocation runs.
|
|
32
|
+
*
|
|
33
|
+
* @returns `true` if Sentry was initialized, `false` if disabled or failed.
|
|
34
|
+
*/
|
|
35
|
+
export function initSentry() {
|
|
36
|
+
if (initPromise)
|
|
37
|
+
return initPromise;
|
|
38
|
+
initPromise = (async () => {
|
|
39
|
+
const env = import.meta.env ?? {};
|
|
40
|
+
const dsn = env.VITE_SENTRY_DSN;
|
|
41
|
+
if (!dsn)
|
|
42
|
+
return false;
|
|
43
|
+
try {
|
|
44
|
+
const Sentry = (await import('@sentry/react'));
|
|
45
|
+
const tracesSampleRate = Number(env.VITE_SENTRY_TRACES_SAMPLE_RATE ?? '0.1');
|
|
46
|
+
Sentry.init({
|
|
47
|
+
dsn,
|
|
48
|
+
environment: env.VITE_SENTRY_ENVIRONMENT || env.MODE || 'production',
|
|
49
|
+
release: env.VITE_SENTRY_RELEASE || env.VITE_APP_VERSION || 'unknown',
|
|
50
|
+
tracesSampleRate: Number.isFinite(tracesSampleRate) ? tracesSampleRate : 0.1,
|
|
51
|
+
// Send IP address + user agent on events. Sentry's recommended default
|
|
52
|
+
// for production. Disable by setting VITE_SENTRY_SEND_DEFAULT_PII=false.
|
|
53
|
+
sendDefaultPii: env.VITE_SENTRY_SEND_DEFAULT_PII !== 'false',
|
|
54
|
+
// Replay is opt-in via VITE_SENTRY_REPLAY=true to keep payload small.
|
|
55
|
+
// When enabled, only 10% of error sessions are recorded.
|
|
56
|
+
replaysSessionSampleRate: 0,
|
|
57
|
+
replaysOnErrorSampleRate: env.VITE_SENTRY_REPLAY === 'true' ? 0.1 : 0,
|
|
58
|
+
// Browser tracing — captures pageloads + navigation transactions.
|
|
59
|
+
integrations: [Sentry.browserTracingIntegration()],
|
|
60
|
+
// Strip query strings + Authorization from breadcrumbs before send.
|
|
61
|
+
beforeBreadcrumb(breadcrumb) {
|
|
62
|
+
if (breadcrumb.category === 'fetch' || breadcrumb.category === 'xhr') {
|
|
63
|
+
if (breadcrumb.data?.url && typeof breadcrumb.data.url === 'string') {
|
|
64
|
+
breadcrumb.data.url = stripSensitive(breadcrumb.data.url);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return breadcrumb;
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
sentryModule = Sentry;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
console.warn('[sentry] init failed; continuing without observability:', err);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
return initPromise;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Reports an error to Sentry if initialized; otherwise no-op. Use this from
|
|
82
|
+
* ErrorBoundary or any catch block where you want best-effort reporting.
|
|
83
|
+
*/
|
|
84
|
+
export function captureError(error, context) {
|
|
85
|
+
if (!sentryModule)
|
|
86
|
+
return;
|
|
87
|
+
try {
|
|
88
|
+
sentryModule.captureException(error, context ? { extra: context } : undefined);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// never let observability break the host app
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Sets the active user context for subsequent events. Pass `null` on logout.
|
|
96
|
+
*/
|
|
97
|
+
export function setSentryUser(user) {
|
|
98
|
+
if (!sentryModule)
|
|
99
|
+
return;
|
|
100
|
+
try {
|
|
101
|
+
sentryModule.setUser(user);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
/* swallow */
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function stripSensitive(url) {
|
|
108
|
+
try {
|
|
109
|
+
const u = new URL(url, 'http://localhost');
|
|
110
|
+
// Drop common token-shaped query params before sending to Sentry.
|
|
111
|
+
for (const key of ['token', 'access_token', 'id_token', 'apiKey', 'api_key', 'password']) {
|
|
112
|
+
if (u.searchParams.has(key))
|
|
113
|
+
u.searchParams.set(key, '[redacted]');
|
|
114
|
+
}
|
|
115
|
+
return u.pathname + (u.searchParams.toString() ? '?' + u.searchParams.toString() : '');
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return url;
|
|
119
|
+
}
|
|
120
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/app-shell",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Minimal application shell for ObjectUI - framework-agnostic rendering engine",
|
|
@@ -25,37 +25,38 @@
|
|
|
25
25
|
"./styles.css": "./src/styles.css"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
+
"@sentry/react": "^8.55.2",
|
|
28
29
|
"lucide-react": "^1.16.0",
|
|
29
30
|
"sonner": "^2.0.7",
|
|
30
|
-
"@object-ui/auth": "5.
|
|
31
|
-
"@object-ui/collaboration": "5.
|
|
32
|
-
"@object-ui/components": "5.
|
|
33
|
-
"@object-ui/core": "5.
|
|
34
|
-
"@object-ui/data-objectstack": "5.
|
|
35
|
-
"@object-ui/fields": "5.
|
|
36
|
-
"@object-ui/i18n": "5.
|
|
37
|
-
"@object-ui/layout": "5.
|
|
38
|
-
"@object-ui/permissions": "5.
|
|
39
|
-
"@object-ui/providers": "5.
|
|
40
|
-
"@object-ui/react": "5.
|
|
41
|
-
"@object-ui/types": "5.
|
|
31
|
+
"@object-ui/auth": "5.3.0",
|
|
32
|
+
"@object-ui/collaboration": "5.3.0",
|
|
33
|
+
"@object-ui/components": "5.3.0",
|
|
34
|
+
"@object-ui/core": "5.3.0",
|
|
35
|
+
"@object-ui/data-objectstack": "5.3.0",
|
|
36
|
+
"@object-ui/fields": "5.3.0",
|
|
37
|
+
"@object-ui/i18n": "5.3.0",
|
|
38
|
+
"@object-ui/layout": "5.3.0",
|
|
39
|
+
"@object-ui/permissions": "5.3.0",
|
|
40
|
+
"@object-ui/providers": "5.3.0",
|
|
41
|
+
"@object-ui/react": "5.3.0",
|
|
42
|
+
"@object-ui/types": "5.3.0"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
45
|
"react": "^18.0.0 || ^19.0.0",
|
|
45
46
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
46
47
|
"react-router-dom": "^6.0.0 || ^7.0.0",
|
|
47
|
-
"@object-ui/plugin-calendar": "^5.
|
|
48
|
-
"@object-ui/plugin-charts": "^5.
|
|
49
|
-
"@object-ui/plugin-chatbot": "^5.
|
|
50
|
-
"@object-ui/plugin-dashboard": "^5.
|
|
51
|
-
"@object-ui/plugin-designer": "^5.
|
|
52
|
-
"@object-ui/plugin-detail": "^5.
|
|
53
|
-
"@object-ui/plugin-form": "^5.
|
|
54
|
-
"@object-ui/plugin-grid": "^5.
|
|
55
|
-
"@object-ui/plugin-kanban": "^5.
|
|
56
|
-
"@object-ui/plugin-list": "^5.
|
|
57
|
-
"@object-ui/plugin-report": "^5.
|
|
58
|
-
"@object-ui/plugin-view": "^5.
|
|
48
|
+
"@object-ui/plugin-calendar": "^5.3.0",
|
|
49
|
+
"@object-ui/plugin-charts": "^5.3.0",
|
|
50
|
+
"@object-ui/plugin-chatbot": "^5.3.0",
|
|
51
|
+
"@object-ui/plugin-dashboard": "^5.3.0",
|
|
52
|
+
"@object-ui/plugin-designer": "^5.3.0",
|
|
53
|
+
"@object-ui/plugin-detail": "^5.3.0",
|
|
54
|
+
"@object-ui/plugin-form": "^5.3.0",
|
|
55
|
+
"@object-ui/plugin-grid": "^5.3.0",
|
|
56
|
+
"@object-ui/plugin-kanban": "^5.3.0",
|
|
57
|
+
"@object-ui/plugin-list": "^5.3.0",
|
|
58
|
+
"@object-ui/plugin-report": "^5.3.0",
|
|
59
|
+
"@object-ui/plugin-view": "^5.3.0"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"@types/node": "^25.9.0",
|