@bhooai/nexus-cli 2.0.5 → 2.0.7
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/package.json +1 -1
- package/src/commands/add.ts +8 -3
- package/src/commands/dev.ts +18 -2
- package/src/commands/init.ts +3 -0
- package/src/devPanel.ts +138 -11
- package/src/devServiceManager.ts +210 -11
- package/src/index.ts +19 -0
- package/templates/base/apps/admin/package.json.ejs +1 -0
- package/templates/base/apps/admin/src/App.tsx +38 -4127
- package/templates/base/apps/admin/src/alertCenter.tsx +3 -2
- package/templates/base/apps/admin/src/api.ts +11 -11
- package/templates/base/apps/admin/src/components/ThemeCentre.tsx +201 -0
- package/templates/base/apps/admin/src/components/shell/AppearancePanel.tsx +37 -0
- package/templates/base/apps/admin/src/components/shell/Dashboard.tsx +441 -0
- package/templates/base/apps/admin/src/components/shell/Login.tsx +69 -0
- package/templates/base/apps/admin/src/components/shell/NotifyPanel.tsx +102 -0
- package/templates/base/apps/admin/src/components/shell/RailPanel.tsx +133 -0
- package/templates/base/apps/admin/src/components/shell/SettingsDialog.tsx +217 -0
- package/templates/base/apps/admin/src/components/ui/ConfirmDialog.tsx +38 -0
- package/templates/base/apps/admin/src/components/ui/LintChecks.tsx +54 -0
- package/templates/base/apps/admin/src/components/ui/PageHead.tsx +13 -0
- package/templates/base/apps/admin/src/components/ui/PageHero.tsx +15 -0
- package/templates/base/apps/admin/src/icons.tsx +104 -0
- package/templates/base/apps/admin/src/index.css +77 -19
- package/templates/base/apps/admin/src/lib/constants.ts +94 -0
- package/templates/base/apps/admin/src/lib/theme.ts +80 -0
- package/templates/base/apps/admin/src/lib/types.ts +39 -0
- package/templates/base/apps/admin/src/lib/utils.ts +147 -0
- package/templates/base/apps/admin/src/main.tsx.ejs +3 -2
- package/templates/base/apps/admin/src/pages/Config.tsx +108 -0
- package/templates/base/apps/admin/src/pages/Databases.tsx +122 -0
- package/templates/base/apps/admin/src/pages/Environment.tsx +69 -0
- package/templates/base/apps/admin/src/pages/Logs.tsx +270 -0
- package/templates/base/apps/admin/src/pages/Monitoring.tsx +88 -0
- package/templates/base/apps/admin/src/pages/Overview.tsx +108 -0
- package/templates/base/apps/admin/src/pages/Payments.tsx +68 -0
- package/templates/base/apps/admin/src/pages/Plugins.tsx +36 -0
- package/templates/base/apps/admin/src/pages/Processes.tsx +67 -0
- package/templates/base/apps/admin/src/pages/Schema.tsx +130 -0
- package/templates/base/apps/admin/src/pages/Users.tsx +144 -0
- package/templates/base/apps/admin/src/pages/ai/AddProviderDialog.tsx +150 -0
- package/templates/base/apps/admin/src/pages/ai/AiAgents.tsx +31 -0
- package/templates/base/apps/admin/src/pages/ai/AiChatPlayground.tsx +208 -0
- package/templates/base/apps/admin/src/pages/ai/AiProviders.tsx +276 -0
- package/templates/base/apps/admin/src/pages/ai/AiSettings.tsx +84 -0
- package/templates/base/apps/admin/src/pages/ai/ProviderCard.tsx +145 -0
- package/templates/base/apps/admin/src/pages/ai/ProviderGroup.tsx +50 -0
- package/templates/base/apps/admin/src/pages/ai/ProviderKeyDialog.tsx +66 -0
- package/templates/base/apps/admin/src/pages/monitoring/RequestSeriesChart.tsx +70 -0
- package/templates/base/apps/admin/src/pages/overview/TrafficChart.tsx +32 -0
- package/templates/base/apps/admin/src/pages/payments/OrdersTable.tsx +27 -0
- package/templates/base/apps/admin/src/pages/payments/PaymentKeysDialog.tsx +74 -0
- package/templates/base/apps/admin/src/pages/payments/TestConsole.tsx +173 -0
- package/templates/base/apps/admin/src/pages/payments/TransactionsTable.tsx +26 -0
- package/templates/base/apps/admin/src/style.css +6517 -0
- package/templates/base/apps/admin/vite.config.ts.ejs +13 -8
- package/templates/base/apps/ai-server/main.py.ejs +2 -2
- package/templates/base/apps/backend/src/routes/index.ts.ejs +1 -1
- package/templates/base/apps/frontend/src/App.tsx.ejs +10 -0
- package/templates/base/apps/frontend/src/main.tsx.ejs +2 -10
- package/templates/base/apps/frontend/vite.config.ts.ejs +9 -4
- package/templates/base/docker-compose.yml.ejs +2 -2
|
@@ -747,6 +747,7 @@ pointer-events: none;
|
|
|
747
747
|
background: transparent;
|
|
748
748
|
text-align: left;
|
|
749
749
|
font-size: 0.7rem;
|
|
750
|
+
line-height: 1;
|
|
750
751
|
transition: 160ms ease;
|
|
751
752
|
}
|
|
752
753
|
|
|
@@ -759,12 +760,24 @@ pointer-events: none;
|
|
|
759
760
|
}
|
|
760
761
|
|
|
761
762
|
.admin-nav-icon {
|
|
762
|
-
display:
|
|
763
|
-
|
|
763
|
+
display: inline-flex;
|
|
764
|
+
align-items: center;
|
|
765
|
+
justify-content: center;
|
|
766
|
+
width: 1.25rem;
|
|
767
|
+
height: 1.25rem;
|
|
768
|
+
flex: 0 0 1.25rem;
|
|
764
769
|
place-items: center;
|
|
765
770
|
color: #62bfff;
|
|
766
|
-
|
|
767
|
-
|
|
771
|
+
line-height: 0;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
.admin-search-icon {
|
|
775
|
+
display: inline-flex;
|
|
776
|
+
align-items: center;
|
|
777
|
+
justify-content: center;
|
|
778
|
+
flex: 0 0 auto;
|
|
779
|
+
line-height: 0;
|
|
780
|
+
color: #6e87a5;
|
|
768
781
|
}
|
|
769
782
|
|
|
770
783
|
.admin-nav button b {
|
|
@@ -981,14 +994,17 @@ pointer-events: none;
|
|
|
981
994
|
}
|
|
982
995
|
|
|
983
996
|
.admin-search-result-icon {
|
|
984
|
-
display:
|
|
985
|
-
|
|
997
|
+
display: inline-flex;
|
|
998
|
+
align-items: center;
|
|
999
|
+
justify-content: center;
|
|
986
1000
|
width: 1.4rem;
|
|
987
1001
|
height: 1.4rem;
|
|
1002
|
+
flex: 0 0 1.4rem;
|
|
1003
|
+
place-items: center;
|
|
988
1004
|
border-radius: 0.35rem;
|
|
989
1005
|
color: #8fb8e4;
|
|
990
1006
|
background: rgba(84, 183, 255, 0.1);
|
|
991
|
-
|
|
1007
|
+
line-height: 0;
|
|
992
1008
|
}
|
|
993
1009
|
|
|
994
1010
|
.admin-search-empty {
|
|
@@ -1032,12 +1048,14 @@ pointer-events: none;
|
|
|
1032
1048
|
.admin-icon-button,
|
|
1033
1049
|
.admin-user-avatar {
|
|
1034
1050
|
position: relative;
|
|
1035
|
-
display:
|
|
1036
|
-
|
|
1051
|
+
display: inline-flex;
|
|
1052
|
+
align-items: center;
|
|
1053
|
+
justify-content: center;
|
|
1037
1054
|
border: 1px solid rgba(112, 168, 228, 0.2);
|
|
1038
1055
|
border-radius: 0.5rem;
|
|
1039
1056
|
color: #9bb4d0;
|
|
1040
1057
|
background: rgba(25, 57, 95, 0.34);
|
|
1058
|
+
line-height: 0;
|
|
1041
1059
|
}
|
|
1042
1060
|
|
|
1043
1061
|
.admin-icon-button {
|
|
@@ -1424,7 +1442,7 @@ pointer-events: none;
|
|
|
1424
1442
|
border-radius: 50%;
|
|
1425
1443
|
}
|
|
1426
1444
|
|
|
1427
|
-
/* bars
|
|
1445
|
+
/* bars — three rising service bars */
|
|
1428
1446
|
.hero-art--bars {
|
|
1429
1447
|
color: #5ef0b3;
|
|
1430
1448
|
border: 1px solid rgba(94, 240, 179, 0.32);
|
|
@@ -1435,7 +1453,7 @@ pointer-events: none;
|
|
|
1435
1453
|
.hero-art--bars .ha-b { left: 43%; bottom: 30%; width: 6px; height: 46%; background: rgba(94, 240, 179, 0.7); border-radius: 3px 3px 0 0; }
|
|
1436
1454
|
.hero-art--bars .ha-c { left: 58%; bottom: 30%; width: 6px; height: 26%; background: rgba(94, 240, 179, 0.45); border-radius: 3px 3px 0 0; }
|
|
1437
1455
|
|
|
1438
|
-
/* stack
|
|
1456
|
+
/* stack — stacked plugin tiles */
|
|
1439
1457
|
.hero-art--stack {
|
|
1440
1458
|
color: #ffc46b;
|
|
1441
1459
|
border: 1px solid rgba(255, 196, 107, 0.32);
|
|
@@ -1449,7 +1467,7 @@ pointer-events: none;
|
|
|
1449
1467
|
.hero-art--stack .ha-b { bottom: 45%; left: 21%; width: 50%; background: rgba(255, 196, 107, 0.65); }
|
|
1450
1468
|
.hero-art--stack .ha-c { bottom: 60%; left: 21%; width: 34%; background: rgba(255, 196, 107, 0.9); box-shadow: 0 0 8px rgba(255, 196, 107, 0.6); }
|
|
1451
1469
|
|
|
1452
|
-
/* people
|
|
1470
|
+
/* people — clients ringed around a center */
|
|
1453
1471
|
.hero-art--people {
|
|
1454
1472
|
color: #c8b4ff;
|
|
1455
1473
|
border: 1px solid rgba(200, 180, 255, 0.32);
|
|
@@ -1460,7 +1478,7 @@ pointer-events: none;
|
|
|
1460
1478
|
.hero-art--people .ha-b { top: 66%; left: 22%; width: 12px; height: 12px; background: rgba(160, 130, 240, 0.8); box-shadow: 0 0 8px rgba(160, 130, 240, 0.7); }
|
|
1461
1479
|
.hero-art--people .ha-c { bottom: 22%; right: 24%; width: 8px; height: 8px; background: rgba(120, 200, 255, 0.8); box-shadow: 0 0 8px rgba(120, 200, 255, 0.7); }
|
|
1462
1480
|
|
|
1463
|
-
/* wave
|
|
1481
|
+
/* wave — pulsing heartbeat line */
|
|
1464
1482
|
.hero-art--wave {
|
|
1465
1483
|
color: #6fe8a8;
|
|
1466
1484
|
border: 1px solid rgba(111, 232, 168, 0.32);
|
|
@@ -1472,7 +1490,7 @@ pointer-events: none;
|
|
|
1472
1490
|
.hero-art--wave .ha-b { left: 44%; top: 26%; width: 8px; height: 26%; background: rgba(111, 232, 168, 0.55); transform: rotate(20deg); }
|
|
1473
1491
|
.hero-art--wave .ha-c { left: 44%; bottom: 26%; width: 8px; height: 26%; background: rgba(111, 232, 168, 0.85); box-shadow: 0 0 8px rgba(111, 232, 168, 0.6); transform: rotate(-20deg); }
|
|
1474
1492
|
|
|
1475
|
-
/* coins
|
|
1493
|
+
/* coins — stacked cylinder of payment */
|
|
1476
1494
|
.hero-art--coins {
|
|
1477
1495
|
color: #ffd479;
|
|
1478
1496
|
border: 1px solid rgba(255, 212, 121, 0.32);
|
|
@@ -1486,7 +1504,7 @@ pointer-events: none;
|
|
|
1486
1504
|
.hero-art--coins .ha-b { bottom: 43%; background: rgba(255, 212, 121, 0.75); }
|
|
1487
1505
|
.hero-art--coins .ha-c { bottom: 58%; background: rgba(255, 212, 121, 0.95); box-shadow: 0 0 8px rgba(255, 212, 121, 0.7); }
|
|
1488
1506
|
|
|
1489
|
-
/* cubes
|
|
1507
|
+
/* cubes — stacked database shelves */
|
|
1490
1508
|
.hero-art--cubes {
|
|
1491
1509
|
color: #6fd6ff;
|
|
1492
1510
|
border: 1px solid rgba(111, 214, 255, 0.32);
|
|
@@ -1500,7 +1518,7 @@ pointer-events: none;
|
|
|
1500
1518
|
.hero-art--cubes .ha-b { top: 42%; left: 27%; background: rgba(111, 214, 255, 0.75); transform: skew(-10deg); }
|
|
1501
1519
|
.hero-art--cubes .ha-c { top: 58%; left: 27%; background: rgba(111, 214, 255, 0.95); box-shadow: 0 0 8px rgba(111, 214, 255, 0.7); transform: skew(-10deg); }
|
|
1502
1520
|
|
|
1503
|
-
/* spark
|
|
1521
|
+
/* spark — four-petal bloom around a center */
|
|
1504
1522
|
.hero-art--spark {
|
|
1505
1523
|
color: #ff9fd6;
|
|
1506
1524
|
border: 1px solid rgba(255, 159, 214, 0.32);
|
|
@@ -2456,7 +2474,7 @@ pointer-events: none;
|
|
|
2456
2474
|
.account-divider { height: 1px; margin: 0.55rem 0; background: rgba(112, 168, 228, 0.14); }
|
|
2457
2475
|
.account-item { display: flex; align-items: center; gap: 0.6rem; width: 100%; padding: 0.5rem 0.5rem; border: 1px solid transparent; border-radius: 0.5rem; color: #9ab2ce; background: transparent; text-align: left; }
|
|
2458
2476
|
.account-item:hover { border-color: rgba(84, 183, 255, 0.26); color: #edf8ff; background: rgba(61, 139, 220, 0.14); }
|
|
2459
|
-
.account-item-icon { display:
|
|
2477
|
+
.account-item-icon { display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; flex: 0 0 24px; place-items: center; border-radius: 0.4rem; color: #61c7ff; background: rgba(40, 110, 178, 0.25); line-height: 0; }
|
|
2460
2478
|
.account-item > span:last-child { display: flex; flex-direction: column; gap: 0.1rem; }
|
|
2461
2479
|
.account-item b { color: inherit; font-size: 0.62rem; font-weight: 600; }
|
|
2462
2480
|
.account-item small { color: #6f89a7; font-size: 0.5rem; }
|
|
@@ -2487,7 +2505,7 @@ pointer-events: none;
|
|
|
2487
2505
|
}
|
|
2488
2506
|
|
|
2489
2507
|
.admin-tool-rail { position: fixed; top: 50%; right: 0.8rem; z-index: 4; display: flex; flex-direction: column; gap: 0.45rem; padding: 0.55rem 0.4rem; transform: translateY(-50%); border: 1px solid rgba(83, 177, 255, 0.35); border-radius: 999px; background: rgba(6, 19, 39, 0.78); box-shadow: 0 0 28px rgba(45, 125, 221, 0.18); backdrop-filter: blur(18px); }
|
|
2490
|
-
.admin-tool-rail button { position: relative; display:
|
|
2508
|
+
.admin-tool-rail button { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; flex: 0 0 auto; place-items: center; border: 0; border-radius: 50%; color: #9fc7ec; background: transparent; line-height: 0; cursor: pointer; }
|
|
2491
2509
|
.admin-tool-rail button:hover { color: white; background: rgba(71, 161, 247, 0.2); }
|
|
2492
2510
|
.admin-tool-rail button.is-active { color: white; background: linear-gradient(135deg, rgba(38, 130, 225, 0.55), rgba(90, 67, 183, 0.45)); box-shadow: 0 0 12px rgba(45, 125, 221, 0.3); }
|
|
2493
2511
|
.admin-tool-rail button[data-tip]::after {
|
|
@@ -2533,7 +2551,7 @@ pointer-events: none;
|
|
|
2533
2551
|
.rail-list--nav li { padding: 0; }
|
|
2534
2552
|
.rail-list--nav li button { display: flex; align-items: center; gap: 0.5rem; width: 100%; padding: 0.4rem 0.45rem; border: 0; border-radius: 0.4rem; color: #9ab2ce; background: transparent; text-align: left; cursor: pointer; }
|
|
2535
2553
|
.rail-list--nav li button:hover, .rail-list--nav li button.is-on { color: #edf8ff; background: rgba(61, 139, 220, 0.14); }
|
|
2536
|
-
.rail-fav-icon {
|
|
2554
|
+
.rail-fav-icon { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; flex: 0 0 20px; line-height: 0; }
|
|
2537
2555
|
.rail-fav-label { flex: 1; font-size: 0.66rem; }
|
|
2538
2556
|
.rail-fav-check { color: #61c7ff; font-size: 0.66rem; }
|
|
2539
2557
|
.rail-appearance { display: flex; flex-direction: column; gap: 0.3rem; margin-top: 0.4rem; }
|
|
@@ -3477,5 +3495,45 @@ pointer-events: none;
|
|
|
3477
3495
|
}
|
|
3478
3496
|
.safe-goto-link:hover { color: #bfdbfe; text-decoration: underline; }
|
|
3479
3497
|
|
|
3498
|
+
/* ===== Inline SVG icon system (see src/icons.tsx) ===== */
|
|
3499
|
+
.admin-shell svg,
|
|
3500
|
+
.admin-login svg {
|
|
3501
|
+
flex-shrink: 0;
|
|
3502
|
+
}
|
|
3503
|
+
|
|
3504
|
+
.admin-nav-icon svg,
|
|
3505
|
+
.admin-search-result-icon svg,
|
|
3506
|
+
.rail-fav-icon svg,
|
|
3507
|
+
.account-item-icon svg,
|
|
3508
|
+
.notify-head-icon svg,
|
|
3509
|
+
.notify-empty-icon svg,
|
|
3510
|
+
.log-ai-title svg,
|
|
3511
|
+
.provider-avatar svg {
|
|
3512
|
+
display: block;
|
|
3513
|
+
}
|
|
3514
|
+
|
|
3515
|
+
.hero-art strong svg,
|
|
3516
|
+
.hero-orbit-art strong svg,
|
|
3517
|
+
.env-lock-glyph svg {
|
|
3518
|
+
display: block;
|
|
3519
|
+
filter: drop-shadow(0 0 8px currentColor);
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
.surface-icon svg {
|
|
3523
|
+
width: 20px;
|
|
3524
|
+
height: 20px;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
.log-empty-glyph svg {
|
|
3528
|
+
display: block;
|
|
3529
|
+
opacity: 0.85;
|
|
3530
|
+
color: var(--admin-muted, #98aec9);
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
.admin-topbar .admin-icon-button svg,
|
|
3534
|
+
.admin-tool-rail button svg {
|
|
3535
|
+
display: block;
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3480
3538
|
|
|
3481
3539
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { AdminTheme, CustomTheme, ParticleConfig, ProviderPreset, ToggleStyle } from './types.js';
|
|
2
|
+
import type { IconName } from '../icons.js';
|
|
3
|
+
|
|
4
|
+
export const TOGGLE_STYLES: Array<{ id: ToggleStyle; label: string; hint: string }> = [
|
|
5
|
+
{ id: 'chip', label: 'Chip ON/OFF', hint: 'Default · glass chip' },
|
|
6
|
+
{ id: 'neon', label: 'Neon glass', hint: 'Blue→violet border' },
|
|
7
|
+
{ id: 'icon', label: 'Icon knob', hint: 'Power · bolt glow' },
|
|
8
|
+
{ id: 'orb', label: 'Orb dot', hint: 'Pulse halo' },
|
|
9
|
+
{ id: 'track', label: 'Gradient-track', hint: 'Sliding trail' },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
export const CUSTOM_THEME_KEY = 'nexus-admin-custom-theme';
|
|
13
|
+
export const THEME_PRESETS: Record<AdminTheme, Omit<CustomTheme, 'active'>> = {
|
|
14
|
+
aurora: { blue: '#54b7ff', violet: '#9f7bff', pink: '#f36eb8', bg: '#0b0d1a', ink: '#eef6ff', muted: '#98aec9', mesh: 0.32, glass: 0.35, radius: 13, toggle: 'chip' },
|
|
15
|
+
midnight: { blue: '#78bfff', violet: '#7da5d3', pink: '#b8c7db', bg: '#02050b', ink: '#eaf3ff', muted: '#8fa6c2', mesh: 0.2, glass: 0.25, radius: 11, toggle: 'chip' },
|
|
16
|
+
violet: { blue: '#b4a1ff', violet: '#d78bff', pink: '#ff8ed1', bg: '#0b071b', ink: '#f3eaff', muted: '#a592c9', mesh: 0.4, glass: 0.3, radius: 15, toggle: 'chip' },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const PARTICLES_KEY = 'nexus-admin-particles';
|
|
20
|
+
export const DEFAULT_PARTICLE_COLORS = ['#59d7ff', '#c68dff', '#ff8fd4', '#5ef0b3', '#d3a0ff', '#5fb6ff'];
|
|
21
|
+
export const DEFAULT_PARTICLES: ParticleConfig = { amount: 14, speed: 1, colors: [...DEFAULT_PARTICLE_COLORS] };
|
|
22
|
+
export const SWATCH_CPLAETTE = ['#59d7ff', '#c68dff', '#ff8fd4', '#5ef0b3', '#ffd479', '#f0995a', '#7fe3c8', '#f2a7ff'];
|
|
23
|
+
|
|
24
|
+
export const USER_KEY = 'nexus-admin-user';
|
|
25
|
+
|
|
26
|
+
export const CATEGORY_LABEL: Record<string, { label: string; hint: string }> = {
|
|
27
|
+
refused: { label: 'Connection refused', hint: 'the target address is reachable but nothing is listening — is the service running?' },
|
|
28
|
+
timeout: { label: 'Timed out', hint: 'the target did not respond in time — it may be down or filtering traffic.' },
|
|
29
|
+
dns: { label: 'DNS lookup failed', hint: 'the host could not be resolved.' },
|
|
30
|
+
ssl: { label: 'TLS handshake failed', hint: 'the target answered but the TLS handshake failed.' },
|
|
31
|
+
http: { label: 'HTTP error status', hint: 'the endpoint responded with an error status code.' },
|
|
32
|
+
engine_offline: { label: 'Diagnostics engine offline', hint: 'the Python engine is not running — start it with "python main.py" or "nexus dev".' },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** Preset for the "Add provider" dialog — popular providers with autofill data
|
|
36
|
+
* and a list of well-known models (the user can still edit everything after). */
|
|
37
|
+
export const POPULAR_PROVIDERS: ProviderPreset[] = [
|
|
38
|
+
{ id: 'ollama', label: 'Ollama (local)', baseUrl: 'http://localhost:11434', defaultModel: 'llama3:latest', models: ['llama3:latest', 'llama3.1:8b', 'mistral:latest', 'phi3:latest', 'qwen2.5:latest'] },
|
|
39
|
+
{ id: 'openai', label: 'OpenAI', baseUrl: 'https://api.openai.com/v1', defaultModel: 'gpt-4o-mini', models: ['gpt-4o-mini', 'gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo', 'o1-mini', 'o1-preview'] },
|
|
40
|
+
{ id: 'anthropic', label: 'Anthropic Claude', baseUrl: 'https://api.anthropic.com/v1', defaultModel: 'claude-sonnet-4-20250514', models: ['claude-sonnet-4-20250514', 'claude-3-7-sonnet-20250219', 'claude-3-5-haiku-20241022', 'claude-3-opus-20240229'] },
|
|
41
|
+
{ id: 'google', label: 'Google Gemini', baseUrl: 'https://generativelanguage.googleapis.com/v1', defaultModel: 'gemini-2.0-flash', models: ['gemini-2.0-flash', 'gemini-2.0-flash-lite', 'gemini-1.5-pro', 'gemini-1.5-flash'] },
|
|
42
|
+
{ id: 'groq', label: 'Groq', baseUrl: 'https://api.groq.com/openai/v1', defaultModel: 'llama-3.3-70b-versatile', models: ['llama-3.3-70b-versatile', 'llama-3.1-8b-instant', 'mixtral-8x7b-32768', 'gemma2-9b-it'] },
|
|
43
|
+
{ id: 'mistral', label: 'Mistral AI', baseUrl: 'https://api.mistral.ai/v1', defaultModel: 'mistral-large-latest', models: ['mistral-large-latest', 'mistral-small-latest', 'open-mixtral-8x22b', 'open-mistral-7b', 'codestral-latest'] },
|
|
44
|
+
{ id: 'cohere', label: 'Cohere', baseUrl: 'https://api.cohere.ai/v1', defaultModel: 'command-r-plus', models: ['command-r-plus', 'command-r', 'command', 'command-light'] },
|
|
45
|
+
{ id: 'together', label: 'Together AI', baseUrl: 'https://api.together.xyz/v1', defaultModel: 'meta-llama/Llama-3-70b-chat-hf', models: ['meta-llama/Llama-3-70b-chat-hf', 'meta-llama/Llama-3-8b-chat-hf', 'mistralai/Mixtral-8x7B-Instruct-v0.1', 'Qwen/Qwen2.5-72B-Instruct-Turbo'] },
|
|
46
|
+
{ id: 'fireworks', label: 'Fireworks AI', baseUrl: 'https://api.fireworks.ai/inference/v1', defaultModel: 'accounts/fireworks/models/llama-v3-70b-instruct', models: ['accounts/fireworks/models/llama-v3-70b-instruct', 'accounts/fireworks/models/llama-v3-8b-instruct', 'accounts/fireworks/models/mixtral-8x7b-instruct'] },
|
|
47
|
+
{ id: 'deepseek', label: 'DeepSeek', baseUrl: 'https://api.deepseek.com/v1', defaultModel: 'deepseek-chat', models: ['deepseek-chat', 'deepseek-reasoner', 'deepseek-coder'] },
|
|
48
|
+
{ id: 'perplexity', label: 'Perplexity', baseUrl: 'https://api.perplexity.ai', defaultModel: 'llama-3.1-sonar-large-128k-online', models: ['llama-3.1-sonar-large-128k-online', 'llama-3.1-sonar-small-128k-online', 'llama-3.1-sonar-huge-128k-online'] },
|
|
49
|
+
{ id: 'xai', label: 'xAI (Grok)', baseUrl: 'https://api.x.ai/v1', defaultModel: 'grok-2-latest', models: ['grok-2-latest', 'grok-2-1212', 'grok-beta', 'grok-vision-beta'] },
|
|
50
|
+
{ id: 'replicate', label: 'Replicate', baseUrl: 'https://api.replicate.com/v1', defaultModel: 'meta/llama-3-70b-instruct', models: ['meta/llama-3-70b-instruct', 'meta/llama-3-8b-instruct', 'mistralai/mixtral-8x7b-instruct-v0.1'] },
|
|
51
|
+
{ id: 'huggingface', label: 'Hugging Face', baseUrl: 'https://api-inference.huggingface.co/models', defaultModel: 'meta-llama/Llama-3-70b-chat-hf', models: ['meta-llama/Llama-3-70b-chat-hf', 'mistralai/Mistral-7B-Instruct-v0.3', 'google/gemma-2-9b'] },
|
|
52
|
+
{ id: 'nvidia', label: 'NVIDIA NIM', baseUrl: 'https://integrate.api.nvidia.com/v1', defaultModel: 'meta/llama-3.1-70b-instruct', models: ['meta/llama-3.1-70b-instruct', 'meta/llama-3.1-8b-instruct', 'nvidia/llama-3.1-nemotron-70b-instruct'] },
|
|
53
|
+
{ id: 'openrouter', label: 'OpenRouter', baseUrl: 'https://openrouter.ai/api/v1', defaultModel: 'openai/gpt-4o-mini', models: ['openai/gpt-4o-mini', 'openai/gpt-4o', 'anthropic/claude-3.5-sonnet', 'google/gemini-2.0-flash-exp', 'meta-llama/llama-3.3-70b-instruct'] },
|
|
54
|
+
{ id: 'lmstudio', label: 'LM Studio (local)', baseUrl: 'http://localhost:1234/v1', defaultModel: 'local-model', models: ['local-model', 'loaded-model'] },
|
|
55
|
+
{ id: 'alephalpha', label: 'Aleph Alpha', baseUrl: 'https://api.aleph-alpha.com/v1', defaultModel: 'luminous-supreme-control', models: ['luminous-supreme-control', 'luminous-supreme', 'luminous-base'] },
|
|
56
|
+
{ id: 'stability', label: 'Stability AI', baseUrl: 'https://api.stability.ai/v1', defaultModel: 'stable-diffusion-xl', models: ['stable-diffusion-xl', 'stable-diffusion-3', 'stable-image-core'] },
|
|
57
|
+
{ id: 'azure', label: 'Azure OpenAI', baseUrl: 'https://your-resource.openai.azure.com', defaultModel: 'gpt-4o-mini', models: ['gpt-4o-mini', 'gpt-4o', 'gpt-35-turbo', 'dall-e-3'] },
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/** Find a preset by id (returns undefined for "other"/custom). */
|
|
61
|
+
export const PRESET_BY_ID: Record<string, ProviderPreset> = Object.fromEntries(POPULAR_PROVIDERS.map((p) => [p.id, p]));
|
|
62
|
+
|
|
63
|
+
export const ALL_PROVIDERS = ['razorpay', 'paypal', 'payu', 'skrill', 'payoneer'];
|
|
64
|
+
|
|
65
|
+
/** Known built-in provider ids (from nexus-core defaults). Used to classify custom providers. */
|
|
66
|
+
export const BUILTIN_PROVIDER_IDS = new Set([
|
|
67
|
+
'ollama', 'openai', 'anthropic', 'google', 'groq', 'mistral', 'cohere', 'together',
|
|
68
|
+
'fireworks', 'deepseek', 'perplexity', 'xai', 'replicate', 'huggingface', 'nvidia',
|
|
69
|
+
'openrouter', 'lmstudio', 'alephalpha', 'stability', 'azure',
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
/** Display metadata for known providers: an icon + an HSL hue for the avatar accent. */
|
|
73
|
+
export const PROVIDER_META: Record<string, { glyph: IconName | string; hue: number }> = {
|
|
74
|
+
ollama: { glyph: 'bot', hue: 152 },
|
|
75
|
+
openai: { glyph: 'sparkles', hue: 165 },
|
|
76
|
+
anthropic: { glyph: 'message', hue: 265 },
|
|
77
|
+
google: { glyph: 'globe', hue: 217 },
|
|
78
|
+
groq: { glyph: 'zap', hue: 38 },
|
|
79
|
+
mistral: { glyph: 'wind', hue: 210 },
|
|
80
|
+
cohere: { glyph: 'layers', hue: 190 },
|
|
81
|
+
together: { glyph: 'users', hue: 230 },
|
|
82
|
+
fireworks: { glyph: 'flame', hue: 12 },
|
|
83
|
+
deepseek: { glyph: 'search', hue: 200 },
|
|
84
|
+
perplexity: { glyph: 'helpCircle', hue: 280 },
|
|
85
|
+
xai: { glyph: 'x', hue: 0 },
|
|
86
|
+
replicate: { glyph: 'repeat', hue: 160 },
|
|
87
|
+
huggingface: { glyph: 'smile', hue: 25 },
|
|
88
|
+
nvidia: { glyph: 'shield', hue: 120 },
|
|
89
|
+
openrouter: { glyph: 'gitFork', hue: 240 },
|
|
90
|
+
lmstudio: { glyph: 'monitor', hue: 180 },
|
|
91
|
+
alephalpha: { glyph: 'bookOpen', hue: 300 },
|
|
92
|
+
stability: { glyph: 'image', hue: 320 },
|
|
93
|
+
azure: { glyph: 'cloud', hue: 200 },
|
|
94
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { CustomTheme, ParticleConfig } from './types.js';
|
|
2
|
+
import { CUSTOM_THEME_KEY, DEFAULT_PARTICLE_COLORS, DEFAULT_PARTICLES, PARTICLES_KEY, THEME_PRESETS } from './constants.js';
|
|
3
|
+
|
|
4
|
+
export function loadCustomTheme(): CustomTheme {
|
|
5
|
+
const fallback: CustomTheme = { ...THEME_PRESETS.aurora, active: false };
|
|
6
|
+
if (typeof window === 'undefined') return fallback;
|
|
7
|
+
try {
|
|
8
|
+
const raw = window.localStorage.getItem(CUSTOM_THEME_KEY);
|
|
9
|
+
if (raw) {
|
|
10
|
+
const parsed = JSON.parse(raw) as Partial<CustomTheme>;
|
|
11
|
+
if (parsed && typeof parsed === 'object') {
|
|
12
|
+
return { ...THEME_PRESETS.aurora, ...parsed, active: Boolean(parsed.active) };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
} catch { /* ignore corrupt value */ }
|
|
16
|
+
return fallback;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function loadParticles(): ParticleConfig {
|
|
20
|
+
const fallback: ParticleConfig = { amount: 14, speed: 1, colors: [...DEFAULT_PARTICLE_COLORS] };
|
|
21
|
+
if (typeof window === 'undefined') return fallback;
|
|
22
|
+
try {
|
|
23
|
+
const raw = window.localStorage.getItem(PARTICLES_KEY);
|
|
24
|
+
if (raw) {
|
|
25
|
+
const p = JSON.parse(raw) as Partial<ParticleConfig>;
|
|
26
|
+
if (p && typeof p === 'object') {
|
|
27
|
+
const amount = Math.max(0, Math.min(48, Math.round(Number(p.amount) || 14)));
|
|
28
|
+
const speed = Math.min(3, Math.max(0.2, Number(p.speed) || 1));
|
|
29
|
+
const colors = Array.isArray(p.colors) ? p.colors.filter((c): c is string => typeof c === 'string' && /^#[0-9a-fA-F]{3,8}$/.test(c)) : [];
|
|
30
|
+
return { amount, speed, colors: colors.length ? colors : [...DEFAULT_PARTICLE_COLORS] };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
} catch { /* ignore corrupt value */ }
|
|
34
|
+
return fallback;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildParticleLayout(amount: number) {
|
|
38
|
+
const list: { left: number; size: number; duration: number; delay: number; dx: number; dx2: number; opacity: string }[] = [];
|
|
39
|
+
for (let i = 0; i < amount; i++) {
|
|
40
|
+
list.push({
|
|
41
|
+
left: 1 + Math.random() * 98,
|
|
42
|
+
size: 3 + Math.random() * 7,
|
|
43
|
+
duration: 18 + Math.random() * 18,
|
|
44
|
+
delay: Math.random() * 32,
|
|
45
|
+
dx: (Math.random() - 0.5) * 70,
|
|
46
|
+
dx2: (Math.random() - 0.5) * 70,
|
|
47
|
+
opacity: (0.45 + Math.random() * 0.4).toFixed(2),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return list;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function hslToHex(h: number, s: number, l: number): string {
|
|
54
|
+
const a = s * Math.min(l, 1 - l);
|
|
55
|
+
const f = (n: number) => {
|
|
56
|
+
const k = (n + h / 30) % 12;
|
|
57
|
+
const color = l - a * Math.max(-1, Math.min(k - 3, Math.min(9 - k, 1)));
|
|
58
|
+
return Math.round(255 * color).toString(16).padStart(2, '0');
|
|
59
|
+
};
|
|
60
|
+
return `#${f(0)}${f(8)}${f(4)}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function randomAccentPalette() {
|
|
64
|
+
const base = Math.round(Math.random() * 360);
|
|
65
|
+
return {
|
|
66
|
+
blue: hslToHex((base + 150) % 360, 72, 64),
|
|
67
|
+
violet: hslToHex((base + 60) % 360, 62, 68),
|
|
68
|
+
pink: hslToHex((base + 25) % 360, 80, 72),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function randomParticleColors(): string[] {
|
|
73
|
+
const base = Math.round(Math.random() * 360);
|
|
74
|
+
const count = 4 + Math.round(Math.random() * 3);
|
|
75
|
+
const colors: string[] = [];
|
|
76
|
+
for (let i = 0; i < count; i++) {
|
|
77
|
+
colors.push(hslToHex((base + i * 55) % 360, 74 + Math.round(Math.random() * 12), 60 + Math.round(Math.random() * 16)));
|
|
78
|
+
}
|
|
79
|
+
return colors;
|
|
80
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type Tab = 'overview' | 'processes' | 'logs' | 'config' | 'env' | 'plugins' | 'users' | 'monitoring' | 'payments' | 'databases' | 'schema' | 'ai' | 'theme';
|
|
2
|
+
export type AdminTheme = 'aurora' | 'midnight' | 'violet';
|
|
3
|
+
export type UiMode = 'workspace' | 'compact' | 'focus';
|
|
4
|
+
export type ToggleStyle = 'chip' | 'neon' | 'icon' | 'orb' | 'track';
|
|
5
|
+
|
|
6
|
+
export interface CustomTheme { active: boolean; blue: string; violet: string; pink: string; bg: string; ink: string; muted: string; mesh: number; glass: number; radius: number; toggle: ToggleStyle; }
|
|
7
|
+
|
|
8
|
+
export interface ParticleConfig { amount: number; speed: number; colors: string[]; }
|
|
9
|
+
|
|
10
|
+
export interface AdminUser { email?: string; name?: string; roles?: string[]; }
|
|
11
|
+
|
|
12
|
+
export type ConfigValueType = 'string' | 'number' | 'boolean' | 'null' | 'object' | 'array';
|
|
13
|
+
export type ConfigEntry = { key: string; value: string; type: ConfigValueType };
|
|
14
|
+
|
|
15
|
+
export interface ProviderPreset { id: string; label: string; baseUrl: string; defaultModel: string; models: string[]; }
|
|
16
|
+
|
|
17
|
+
export type ProviderFilter = 'all' | 'enabled' | 'disabled' | 'custom';
|
|
18
|
+
|
|
19
|
+
export interface PaymentOrder {
|
|
20
|
+
provider: string;
|
|
21
|
+
orderId: string;
|
|
22
|
+
reference: string;
|
|
23
|
+
status: string;
|
|
24
|
+
amount: number;
|
|
25
|
+
currency: string;
|
|
26
|
+
paymentId?: string;
|
|
27
|
+
createdAt?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface PaymentTransaction {
|
|
30
|
+
provider: string;
|
|
31
|
+
event: string;
|
|
32
|
+
verified: boolean;
|
|
33
|
+
orderId?: string;
|
|
34
|
+
paymentId?: string;
|
|
35
|
+
amount?: number;
|
|
36
|
+
currency?: string;
|
|
37
|
+
status?: string;
|
|
38
|
+
createdAt?: string;
|
|
39
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { AdminUser, ConfigEntry } from './types.js';
|
|
2
|
+
import { USER_KEY, CATEGORY_LABEL, PROVIDER_META } from './constants.js';
|
|
3
|
+
import type { IconName } from '../icons.js';
|
|
4
|
+
import type { PreflightCheck, RequestSeriesRange } from '../api.js';
|
|
5
|
+
|
|
6
|
+
export function storedPreference<T extends string>(key: string, fallback: T, allowed: readonly T[]): T {
|
|
7
|
+
const value = typeof window === 'undefined' ? null : window.localStorage.getItem(key);
|
|
8
|
+
return value && allowed.includes(value as T) ? value as T : fallback;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function formatProjectName(name: string): string {
|
|
12
|
+
const words = name.split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
13
|
+
if (!words.length) return name;
|
|
14
|
+
return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function readUser(): AdminUser | null {
|
|
18
|
+
if (typeof window === 'undefined') return null;
|
|
19
|
+
try { const raw = window.localStorage.getItem(USER_KEY); return raw ? JSON.parse(raw) as AdminUser : null; } catch { return null; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function formatUptime(seconds?: number): string {
|
|
23
|
+
if (!seconds || seconds <= 0) return '—';
|
|
24
|
+
const d = Math.floor(seconds / 86400);
|
|
25
|
+
const h = Math.floor((seconds % 86400) / 3600);
|
|
26
|
+
const m = Math.floor((seconds % 3600) / 60);
|
|
27
|
+
const s = Math.floor(seconds % 60);
|
|
28
|
+
if (d > 0) return `${d}d ${h}h ${m}m`;
|
|
29
|
+
if (h > 0) return `${h}h ${m}m ${s}s`;
|
|
30
|
+
if (m > 0) return `${m}m ${s}s`;
|
|
31
|
+
return `${s}s`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function flattenConfig(value: unknown, prefix = ''): ConfigEntry[] {
|
|
35
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
36
|
+
const rows = Object.entries(value as Record<string, unknown>);
|
|
37
|
+
if (!rows.length && prefix) return [{ key: prefix, value: '{}', type: 'object' }];
|
|
38
|
+
return rows.flatMap(([key, child]) => flattenConfig(child, prefix ? `${prefix}.${key}` : key));
|
|
39
|
+
}
|
|
40
|
+
if (!prefix) return [];
|
|
41
|
+
if (Array.isArray(value)) return [{ key: prefix, value: JSON.stringify(value), type: 'array' }];
|
|
42
|
+
if (value === null) return [{ key: prefix, value: '', type: 'null' }];
|
|
43
|
+
if (typeof value === 'number') return [{ key: prefix, value: String(value), type: 'number' }];
|
|
44
|
+
if (typeof value === 'boolean') return [{ key: prefix, value: String(value), type: 'boolean' }];
|
|
45
|
+
return [{ key: prefix, value: String(value), type: 'string' }];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function configValue(entry: ConfigEntry): unknown {
|
|
49
|
+
if (entry.type === 'null') return null;
|
|
50
|
+
if (entry.type === 'number') return Number(entry.value);
|
|
51
|
+
if (entry.type === 'boolean') return entry.value === 'true';
|
|
52
|
+
if (entry.type === 'object' || entry.type === 'array') return JSON.parse(entry.value);
|
|
53
|
+
return entry.value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function buildConfig(entries: ConfigEntry[]): Record<string, unknown> {
|
|
57
|
+
const root: Record<string, unknown> = {};
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
const parts = entry.key.trim().split('.').filter(Boolean);
|
|
60
|
+
if (!parts.length || parts.some((part) => ['__proto__', 'constructor', 'prototype'].includes(part))) continue;
|
|
61
|
+
const value = configValue(entry);
|
|
62
|
+
let node = root;
|
|
63
|
+
parts.forEach((part, index) => {
|
|
64
|
+
if (index === parts.length - 1) node[part] = value;
|
|
65
|
+
else {
|
|
66
|
+
const child = node[part];
|
|
67
|
+
if (!child || typeof child !== 'object' || Array.isArray(child)) node[part] = {};
|
|
68
|
+
node = node[part] as Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return root;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function topGroup(key: string): string {
|
|
76
|
+
const trimmed = key.trim();
|
|
77
|
+
if (!trimmed) return 'unsorted';
|
|
78
|
+
if (trimmed.startsWith('NEXUS_')) return trimmed.slice(6).split('_')[0].toLowerCase() || 'root';
|
|
79
|
+
return trimmed.split('.')[0] || 'root';
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function groupEntries<T extends { key: string }>(entries: T[]): Array<{ group: string; items: Array<{ index: number; entry: T }> }> {
|
|
83
|
+
const order: string[] = [];
|
|
84
|
+
const map = new Map<string, Array<{ index: number; entry: T }>>();
|
|
85
|
+
entries.forEach((entry, index) => {
|
|
86
|
+
const group = topGroup(entry.key);
|
|
87
|
+
if (!map.has(group)) { map.set(group, []); order.push(group); }
|
|
88
|
+
map.get(group)!.push({ index, entry });
|
|
89
|
+
});
|
|
90
|
+
return order.map((group) => ({ group, items: map.get(group)! }));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function friendlyPreflightError(error?: string, category?: string): string {
|
|
94
|
+
if (!error) return 'unreachable';
|
|
95
|
+
const meta = category ? CATEGORY_LABEL[category] : undefined;
|
|
96
|
+
if (!meta || error.toLowerCase().startsWith(meta.label.toLowerCase())) return error;
|
|
97
|
+
return `${meta.label} — ${error}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function preflightAddress(c: PreflightCheck): string {
|
|
101
|
+
if (c.kind === 'http' && c.url) return c.url;
|
|
102
|
+
if (c.kind === 'tcp' && c.host) return `${c.host}:${c.port}`;
|
|
103
|
+
return c.name;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Derive a deterministic HSL hue from an arbitrary string (for custom provider avatars). */
|
|
107
|
+
export function hashHue(s: string): number {
|
|
108
|
+
let h = 0;
|
|
109
|
+
for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) % 360;
|
|
110
|
+
return h;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function providerMeta(id: string): { glyph: IconName | string; hue: number } {
|
|
114
|
+
if (PROVIDER_META[id]) return PROVIDER_META[id]!;
|
|
115
|
+
const letter = id.charAt(0).toUpperCase() || '?';
|
|
116
|
+
return { glyph: letter, hue: hashHue(id) };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Returns true if the provider's baseUrl points to a local host. */
|
|
120
|
+
export function isLocalProvider(baseUrl: string): boolean {
|
|
121
|
+
try {
|
|
122
|
+
const u = new URL(baseUrl);
|
|
123
|
+
return u.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '0.0.0.0';
|
|
124
|
+
} catch { return false; }
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Derive the marketing website from an API base URL (strip /v1, /api, etc). */
|
|
128
|
+
export function websiteFromBaseUrl(baseUrl: string): string {
|
|
129
|
+
try {
|
|
130
|
+
const u = new URL(baseUrl);
|
|
131
|
+
// For local providers (localhost / 127.0.0.1 / 0.0.0.0) just return the origin.
|
|
132
|
+
const isLocal = u.hostname === 'localhost' || u.hostname === '127.0.0.1' || u.hostname === '0.0.0.0';
|
|
133
|
+
if (isLocal) return u.origin;
|
|
134
|
+
// For cloud providers, strip common API path segments and go to the root.
|
|
135
|
+
return `${u.protocol}//${u.hostname}`;
|
|
136
|
+
} catch {
|
|
137
|
+
return baseUrl;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function formatPointTime(t: number, range: RequestSeriesRange): string {
|
|
142
|
+
const d = new Date(t);
|
|
143
|
+
const time = d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
|
|
144
|
+
if (range === 'today' || range === '5d') return time;
|
|
145
|
+
if (range === 'week') return `${d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} ${time}`;
|
|
146
|
+
return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
|
|
147
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import './index.css';
|
|
2
|
-
import { App as NexusAdmin } from './App.js';
|
|
3
1
|
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import { App as NexusAdmin } from '@bhooai/nexus-admin';
|
|
4
|
+
import '@bhooai/nexus-admin/style.css';
|
|
4
5
|
import { createRoot } from 'react-dom/client';
|
|
5
6
|
|
|
6
7
|
const el = document.getElementById('root');
|