@burdenoff/microfe-admin 2026.608.1 → 2026.624.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/AdminRoutes.js +14 -10
- package/dist/admin/AdminRoutes.js.map +1 -1
- package/dist/admin/components/AccessDenied.js +2 -2
- package/dist/admin/components/AccessDenied.js.map +1 -1
- package/dist/admin/components/ActivityFeed.js +4 -4
- package/dist/admin/components/ActivityFeed.js.map +1 -1
- package/dist/admin/components/Charts.js +4 -4
- package/dist/admin/components/Charts.js.map +1 -1
- package/dist/admin/components/ConfirmDialog.js +44 -25
- package/dist/admin/components/ConfirmDialog.js.map +1 -1
- package/dist/admin/components/DataTable.js +7 -7
- package/dist/admin/components/DataTable.js.map +1 -1
- package/dist/admin/components/FormSheet.js +7 -7
- package/dist/admin/components/FormSheet.js.map +1 -1
- package/dist/admin/components/MetricCard.js +4 -4
- package/dist/admin/components/MetricCard.js.map +1 -1
- package/dist/admin/components/PageLayout.js +3 -3
- package/dist/admin/components/PageLayout.js.map +1 -1
- package/dist/admin/components/PermissionGuard.js +1 -1
- package/dist/admin/components/PermissionGuard.js.map +1 -1
- package/dist/admin/components/RowActions.js +2 -2
- package/dist/admin/components/RowActions.js.map +1 -1
- package/dist/admin/components/TenantMembersPanel.js +15 -15
- package/dist/admin/components/TenantMembersPanel.js.map +1 -1
- package/dist/admin/components/TenantSsoPanel.js +14 -14
- package/dist/admin/components/TenantSsoPanel.js.map +1 -1
- package/dist/admin/components/Toast.js +9 -9
- package/dist/admin/components/Toast.js.map +1 -1
- package/dist/admin/components/forms/SubscriptionForm.js +10 -10
- package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
- package/dist/admin/components/forms/TenantForm.js +8 -8
- package/dist/admin/components/forms/TenantForm.js.map +1 -1
- package/dist/admin/components/forms/TicketForm.js +2 -2
- package/dist/admin/components/forms/TicketForm.js.map +1 -1
- package/dist/admin/components/forms/UserForm.js +8 -8
- package/dist/admin/components/forms/UserForm.js.map +1 -1
- package/dist/admin/components/ui.js +16 -16
- package/dist/admin/components/ui.js.map +1 -1
- package/dist/admin/constants/index.js +8 -8
- package/dist/admin/constants/index.js.map +1 -1
- package/dist/admin/pages/AuditLogsPage.js +8 -8
- package/dist/admin/pages/AuditLogsPage.js.map +1 -1
- package/dist/admin/pages/CommunicationsPage.js +2 -2
- package/dist/admin/pages/CommunicationsPage.js.map +1 -1
- package/dist/admin/pages/ContentPagesPage.js +661 -0
- package/dist/admin/pages/ContentPagesPage.js.map +1 -0
- package/dist/admin/pages/FeatureFlagsPage.js +4 -4
- package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
- package/dist/admin/pages/OrganizationsListPage.js +6 -6
- package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
- package/dist/admin/pages/ProductsPage.js +7 -7
- package/dist/admin/pages/ProductsPage.js.map +1 -1
- package/dist/admin/pages/ReferralsPage.js +23 -23
- package/dist/admin/pages/ReferralsPage.js.map +1 -1
- package/dist/admin/pages/RolesPermissionsPage.js +8 -8
- package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
- package/dist/admin/pages/SREPage.js +8 -8
- package/dist/admin/pages/SREPage.js.map +1 -1
- package/dist/admin/pages/SandboxSettingsPage.js +20 -20
- package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage.js +20 -20
- package/dist/admin/pages/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SyncFailuresPage.js +11 -11
- package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
- package/dist/admin/pages/TenantDetailPage.js +22 -22
- package/dist/admin/pages/TenantDetailPage.js.map +1 -1
- package/dist/admin/pages/TenantFormRoutePage.js +2 -2
- package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
- package/dist/admin/pages/TenantsListPage.js +5 -5
- package/dist/admin/pages/TenantsListPage.js.map +1 -1
- package/dist/admin/pages/UsersListPage.js +6 -6
- package/dist/admin/pages/UsersListPage.js.map +1 -1
- package/dist/admin/pages/WaitlistPage.js +7 -7
- package/dist/admin/pages/WaitlistPage.js.map +1 -1
- package/dist/admin/pages/WorkflowsPage.js +3 -3
- package/dist/admin/pages/WorkflowsPage.js.map +1 -1
- package/dist/admin/pages/WorkspacesListPage.js +8 -8
- package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
- package/dist/admin/pages/index.js +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReferralsPage.js","names":[],"sources":["../../../src/admin/pages/ReferralsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Activity, Link2, RefreshCw, Users } from 'lucide-react';\n\nimport { PageLayout, EmptyState, PermissionGuard } from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { useAdmin } from '../providers/AdminProvider';\nimport { directActivityGraphqlRequest } from '../utils/directActivityGraphql';\nimport { formatRelativeTime } from '../utils/formatters';\n\ntype ReferralSummary = {\n totalCodes: number;\n totalVisits: number;\n totalJourneys: number;\n totalSignups: number;\n totalPaidConversions: number;\n totalStorePurchases: number;\n totalRevenueAmount: number;\n totalRewardAmount: number;\n topAffiliates: Array<{\n codeId: string;\n codeValue: string;\n ownerUserId: string;\n visits: number;\n signups: number;\n paidConversions: number;\n storePurchases: number;\n revenueAmount: number;\n rewardAmount: number;\n }>;\n};\n\ntype ReferralJourney = {\n id: string;\n codeValue: string;\n identifiedUserId?: string | null;\n productSlug: string;\n currentPath?: string | null;\n geographyCountry?: string | null;\n mode?: string | null;\n status: string;\n lastVisitedAt: string;\n};\n\ntype ReferralEvent = {\n id: string;\n type: string;\n valueAmount?: number | null;\n currency?: string | null;\n occurredAt: string;\n};\n\nexport function ReferralsPage() {\n const { canViewDashboard, isLoading: permissionsLoading } = useAdminPermissions();\n const { apiGatewayUrl, authToken, currentUser } = useAdmin();\n const canViewReferrals = canViewDashboard || currentUser?.role === 'PLATFORM_SUPERADMIN';\n\n const [summary, setSummary] = useState<ReferralSummary | null>(null);\n const [journeys, setJourneys] = useState<ReferralJourney[]>([]);\n const [selectedJourneyId, setSelectedJourneyId] = useState<string | null>(null);\n const [selectedJourneyEvents, setSelectedJourneyEvents] = useState<ReferralEvent[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n actorId: currentUser?.id,\n actorType: 'super_admin' as const,\n }),\n [apiGatewayUrl, authToken, currentUser?.id]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n const [summaryData, journeyData] = await Promise.all([\n directActivityGraphqlRequest<{ adminAffiliateAnalytics: ReferralSummary }>({\n ...requestContext,\n query: `query AdminAffiliateAnalytics {\n adminAffiliateAnalytics {\n totalCodes\n totalVisits\n totalJourneys\n totalSignups\n totalPaidConversions\n totalStorePurchases\n totalRevenueAmount\n totalRewardAmount\n topAffiliates {\n codeId\n codeValue\n ownerUserId\n visits\n signups\n paidConversions\n storePurchases\n revenueAmount\n rewardAmount\n }\n }\n }`,\n }),\n directActivityGraphqlRequest<{ affiliateJourneys: { items: ReferralJourney[] } }>({\n ...requestContext,\n query: `query AffiliateJourneys {\n affiliateJourneys(limit: 25, offset: 0) {\n items {\n id\n codeValue\n identifiedUserId\n productSlug\n currentPath\n geographyCountry\n mode\n status\n lastVisitedAt\n }\n }\n }`,\n }),\n ]);\n\n setSummary(summaryData.adminAffiliateAnalytics);\n setJourneys(journeyData.affiliateJourneys.items);\n } catch (loadError) {\n setError(\n loadError instanceof Error ? loadError.message : 'Failed to load referrals dashboard'\n );\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [requestContext]);\n\n useEffect(() => {\n if (!selectedJourneyId) {\n setSelectedJourneyEvents([]);\n return;\n }\n\n void directActivityGraphqlRequest<\n { affiliateJourneyEvents: ReferralEvent[] },\n { journeyId: string }\n >({\n ...requestContext,\n query: `query AffiliateJourneyEvents($journeyId: ID!) {\n affiliateJourneyEvents(journeyId: $journeyId) {\n id\n type\n valueAmount\n currency\n occurredAt\n }\n }`,\n variables: { journeyId: selectedJourneyId },\n })\n .then((response) => setSelectedJourneyEvents(response.affiliateJourneyEvents))\n .catch((loadError) => {\n setError(loadError instanceof Error ? loadError.message : 'Failed to load journey events');\n });\n }, [requestContext, selectedJourneyId]);\n\n const cards = summary\n ? [\n { title: 'Codes', value: summary.totalCodes, icon: <Link2 className=\"h-5 w-5\" /> },\n { title: 'Visits', value: summary.totalVisits, icon: <Activity className=\"h-5 w-5\" /> },\n { title: 'Signups', value: summary.totalSignups, icon: <Users className=\"h-5 w-5\" /> },\n {\n title: 'Paid conversions',\n value: summary.totalPaidConversions + summary.totalStorePurchases,\n icon: <Users className=\"h-5 w-5\" />,\n },\n ]\n : [];\n\n return (\n <PermissionGuard\n allowed={canViewReferrals}\n isLoading={permissionsLoading}\n pageTitle=\"Referrals\"\n denyMessage=\"You don't have permission to view referral analytics.\"\n >\n <PageLayout\n title=\"Referrals\"\n description=\"Inspect top affiliates, journey drilldowns, conversions, and revenue attribution.\"\n actions={\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border bg-card px-3 py-2 text-sm font-medium text-foreground\"\n >\n <RefreshCw className=\"h-4 w-4\" />\n Refresh\n </button>\n }\n >\n <div className=\"space-y-6\">\n {loading ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {[1, 2, 3, 4].map((item) => (\n <div\n key={item}\n className=\"h-28 animate-pulse rounded-lg border border-border bg-card\"\n />\n ))}\n </div>\n ) : summary ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {cards.map((card) => (\n <div key={card.title} className=\"rounded-lg border border-border bg-card p-4\">\n <div className=\"mb-3 flex items-center justify-between text-muted-foreground\">\n <span className=\"text-sm font-medium\">{card.title}</span>\n {card.icon}\n </div>\n <p className=\"text-2xl font-semibold text-foreground\">{card.value}</p>\n </div>\n ))}\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-border bg-card p-4 text-sm text-destructive\">\n {error}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 xl:grid-cols-[1.15fr,1.85fr]\">\n <section className=\"rounded-lg border border-border bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">Top affiliates</h2>\n {summary?.topAffiliates.length ? (\n <div className=\"space-y-3\">\n {summary.topAffiliates.map((affiliate) => (\n <div\n key={affiliate.codeId}\n className=\"rounded-lg border border-border bg-background p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-foreground\">\n {affiliate.codeValue}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n Owner: {affiliate.ownerUserId}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold text-foreground\">\n {affiliate.visits} visits\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {affiliate.paidConversions + affiliate.storePurchases} conversions\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Link2 className=\"h-8 w-8\" />}\n title=\"No affiliate performance yet\"\n description=\"Top-performing affiliate codes will appear here once journeys are recorded.\"\n />\n )}\n </section>\n\n <section className=\"space-y-4\">\n <div className=\"rounded-lg border border-border bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">Recent journeys</h2>\n {journeys.length ? (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border text-left text-muted-foreground\">\n <th className=\"px-3 py-2\">Code</th>\n <th className=\"px-3 py-2\">Product</th>\n <th className=\"px-3 py-2\">Path</th>\n <th className=\"px-3 py-2\">Country</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Visited</th>\n </tr>\n </thead>\n <tbody>\n {journeys.map((journey) => (\n <tr\n key={journey.id}\n className=\"cursor-pointer border-b border-border/70 text-foreground\"\n onClick={() => setSelectedJourneyId(journey.id)}\n >\n <td className=\"px-3 py-2 font-medium\">{journey.codeValue}</td>\n <td className=\"px-3 py-2\">{journey.productSlug}</td>\n <td className=\"px-3 py-2\">{journey.currentPath || '-'}</td>\n <td className=\"px-3 py-2\">{journey.geographyCountry || '-'}</td>\n <td className=\"px-3 py-2\">{journey.status}</td>\n <td className=\"px-3 py-2\">\n {formatRelativeTime(journey.lastVisitedAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : (\n <EmptyState\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"No journeys found\"\n description=\"Journey-level drilldowns will appear once referral traffic flows through product touchpoints.\"\n />\n )}\n </div>\n\n <div className=\"rounded-lg border border-border bg-card p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-foreground\">Journey events</h2>\n {selectedJourneyEvents.length ? (\n <div className=\"space-y-3\">\n {selectedJourneyEvents.map((event) => (\n <div\n key={event.id}\n className=\"rounded-lg border border-border bg-background p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-foreground\">{event.type}</p>\n <p className=\"text-xs text-muted-foreground\">\n {formatRelativeTime(event.occurredAt)}\n </p>\n </div>\n <p className=\"text-sm font-medium text-foreground\">\n {event.valueAmount\n ? `${event.valueAmount} ${event.currency || 'USD'}`\n : '—'}\n </p>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"Select a journey\"\n description=\"Choose a journey from the table above to inspect its critical events.\"\n />\n )}\n </div>\n </section>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ReferralsPage.js","names":[],"sources":["../../../src/admin/pages/ReferralsPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Activity, Link2, RefreshCw, Users } from 'lucide-react';\n\nimport { PageLayout, EmptyState, PermissionGuard } from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { useAdmin } from '../providers/AdminProvider';\nimport { directActivityGraphqlRequest } from '../utils/directActivityGraphql';\nimport { formatRelativeTime } from '../utils/formatters';\n\ntype ReferralSummary = {\n totalCodes: number;\n totalVisits: number;\n totalJourneys: number;\n totalSignups: number;\n totalPaidConversions: number;\n totalStorePurchases: number;\n totalRevenueAmount: number;\n totalRewardAmount: number;\n topAffiliates: Array<{\n codeId: string;\n codeValue: string;\n ownerUserId: string;\n visits: number;\n signups: number;\n paidConversions: number;\n storePurchases: number;\n revenueAmount: number;\n rewardAmount: number;\n }>;\n};\n\ntype ReferralJourney = {\n id: string;\n codeValue: string;\n identifiedUserId?: string | null;\n productSlug: string;\n currentPath?: string | null;\n geographyCountry?: string | null;\n mode?: string | null;\n status: string;\n lastVisitedAt: string;\n};\n\ntype ReferralEvent = {\n id: string;\n type: string;\n valueAmount?: number | null;\n currency?: string | null;\n occurredAt: string;\n};\n\nexport function ReferralsPage() {\n const { canViewDashboard, isLoading: permissionsLoading } = useAdminPermissions();\n const { apiGatewayUrl, authToken, currentUser } = useAdmin();\n const canViewReferrals = canViewDashboard || currentUser?.role === 'PLATFORM_SUPERADMIN';\n\n const [summary, setSummary] = useState<ReferralSummary | null>(null);\n const [journeys, setJourneys] = useState<ReferralJourney[]>([]);\n const [selectedJourneyId, setSelectedJourneyId] = useState<string | null>(null);\n const [selectedJourneyEvents, setSelectedJourneyEvents] = useState<ReferralEvent[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const requestContext = useMemo(\n () => ({\n apiGatewayUrl,\n authToken,\n actorId: currentUser?.id,\n actorType: 'super_admin' as const,\n }),\n [apiGatewayUrl, authToken, currentUser?.id]\n );\n\n const loadData = async () => {\n setLoading(true);\n setError(null);\n try {\n const [summaryData, journeyData] = await Promise.all([\n directActivityGraphqlRequest<{ adminAffiliateAnalytics: ReferralSummary }>({\n ...requestContext,\n query: `query AdminAffiliateAnalytics {\n adminAffiliateAnalytics {\n totalCodes\n totalVisits\n totalJourneys\n totalSignups\n totalPaidConversions\n totalStorePurchases\n totalRevenueAmount\n totalRewardAmount\n topAffiliates {\n codeId\n codeValue\n ownerUserId\n visits\n signups\n paidConversions\n storePurchases\n revenueAmount\n rewardAmount\n }\n }\n }`,\n }),\n directActivityGraphqlRequest<{ affiliateJourneys: { items: ReferralJourney[] } }>({\n ...requestContext,\n query: `query AffiliateJourneys {\n affiliateJourneys(limit: 25, offset: 0) {\n items {\n id\n codeValue\n identifiedUserId\n productSlug\n currentPath\n geographyCountry\n mode\n status\n lastVisitedAt\n }\n }\n }`,\n }),\n ]);\n\n setSummary(summaryData.adminAffiliateAnalytics);\n setJourneys(journeyData.affiliateJourneys.items);\n } catch (loadError) {\n setError(\n loadError instanceof Error ? loadError.message : 'Failed to load referrals dashboard'\n );\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n void loadData();\n }, [requestContext]);\n\n useEffect(() => {\n if (!selectedJourneyId) {\n setSelectedJourneyEvents([]);\n return;\n }\n\n void directActivityGraphqlRequest<\n { affiliateJourneyEvents: ReferralEvent[] },\n { journeyId: string }\n >({\n ...requestContext,\n query: `query AffiliateJourneyEvents($journeyId: ID!) {\n affiliateJourneyEvents(journeyId: $journeyId) {\n id\n type\n valueAmount\n currency\n occurredAt\n }\n }`,\n variables: { journeyId: selectedJourneyId },\n })\n .then((response) => setSelectedJourneyEvents(response.affiliateJourneyEvents))\n .catch((loadError) => {\n setError(loadError instanceof Error ? loadError.message : 'Failed to load journey events');\n });\n }, [requestContext, selectedJourneyId]);\n\n const cards = summary\n ? [\n { title: 'Codes', value: summary.totalCodes, icon: <Link2 className=\"h-5 w-5\" /> },\n { title: 'Visits', value: summary.totalVisits, icon: <Activity className=\"h-5 w-5\" /> },\n { title: 'Signups', value: summary.totalSignups, icon: <Users className=\"h-5 w-5\" /> },\n {\n title: 'Paid conversions',\n value: summary.totalPaidConversions + summary.totalStorePurchases,\n icon: <Users className=\"h-5 w-5\" />,\n },\n ]\n : [];\n\n return (\n <PermissionGuard\n allowed={canViewReferrals}\n isLoading={permissionsLoading}\n pageTitle=\"Referrals\"\n denyMessage=\"You don't have permission to view referral analytics.\"\n >\n <PageLayout\n title=\"Referrals\"\n description=\"Inspect top affiliates, journey drilldowns, conversions, and revenue attribution.\"\n actions={\n <button\n type=\"button\"\n onClick={() => void loadData()}\n className=\"inline-flex items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary\"\n >\n <RefreshCw className=\"h-4 w-4\" />\n Refresh\n </button>\n }\n >\n <div className=\"space-y-6\">\n {loading ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {[1, 2, 3, 4].map((item) => (\n <div\n key={item}\n className=\"h-28 animate-pulse rounded-lg border border-border-subtle bg-bg-surface\"\n />\n ))}\n </div>\n ) : summary ? (\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-4\">\n {cards.map((card) => (\n <div key={card.title} className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <div className=\"mb-3 flex items-center justify-between text-text-secondary\">\n <span className=\"text-sm font-medium\">{card.title}</span>\n {card.icon}\n </div>\n <p className=\"text-2xl font-semibold text-text-primary\">{card.value}</p>\n </div>\n ))}\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n\n <div className=\"grid gap-6 xl:grid-cols-[1.15fr,1.85fr]\">\n <section className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Top affiliates</h2>\n {summary?.topAffiliates.length ? (\n <div className=\"space-y-3\">\n {summary.topAffiliates.map((affiliate) => (\n <div\n key={affiliate.codeId}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">\n {affiliate.codeValue}\n </p>\n <p className=\"text-xs text-text-secondary\">\n Owner: {affiliate.ownerUserId}\n </p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-semibold text-text-primary\">\n {affiliate.visits} visits\n </p>\n <p className=\"text-xs text-text-secondary\">\n {affiliate.paidConversions + affiliate.storePurchases} conversions\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Link2 className=\"h-8 w-8\" />}\n title=\"No affiliate performance yet\"\n description=\"Top-performing affiliate codes will appear here once journeys are recorded.\"\n />\n )}\n </section>\n\n <section className=\"space-y-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Recent journeys</h2>\n {journeys.length ? (\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full text-sm\">\n <thead>\n <tr className=\"border-b border-border-subtle text-left text-text-secondary\">\n <th className=\"px-3 py-2\">Code</th>\n <th className=\"px-3 py-2\">Product</th>\n <th className=\"px-3 py-2\">Path</th>\n <th className=\"px-3 py-2\">Country</th>\n <th className=\"px-3 py-2\">Status</th>\n <th className=\"px-3 py-2\">Visited</th>\n </tr>\n </thead>\n <tbody>\n {journeys.map((journey) => (\n <tr\n key={journey.id}\n className=\"cursor-pointer border-b border-border-subtle/70 text-text-primary\"\n onClick={() => setSelectedJourneyId(journey.id)}\n >\n <td className=\"px-3 py-2 font-medium\">{journey.codeValue}</td>\n <td className=\"px-3 py-2\">{journey.productSlug}</td>\n <td className=\"px-3 py-2\">{journey.currentPath || '-'}</td>\n <td className=\"px-3 py-2\">{journey.geographyCountry || '-'}</td>\n <td className=\"px-3 py-2\">{journey.status}</td>\n <td className=\"px-3 py-2\">\n {formatRelativeTime(journey.lastVisitedAt)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n ) : (\n <EmptyState\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"No journeys found\"\n description=\"Journey-level drilldowns will appear once referral traffic flows through product touchpoints.\"\n />\n )}\n </div>\n\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Journey events</h2>\n {selectedJourneyEvents.length ? (\n <div className=\"space-y-3\">\n {selectedJourneyEvents.map((event) => (\n <div\n key={event.id}\n className=\"rounded-lg border border-border-subtle bg-bg-surface p-3\"\n >\n <div className=\"flex items-center justify-between gap-3\">\n <div>\n <p className=\"text-sm font-semibold text-text-primary\">{event.type}</p>\n <p className=\"text-xs text-text-secondary\">\n {formatRelativeTime(event.occurredAt)}\n </p>\n </div>\n <p className=\"text-sm font-medium text-text-primary\">\n {event.valueAmount\n ? `${event.valueAmount} ${event.currency || 'USD'}`\n : '—'}\n </p>\n </div>\n </div>\n ))}\n </div>\n ) : (\n <EmptyState\n icon={<Activity className=\"h-8 w-8\" />}\n title=\"Select a journey\"\n description=\"Choose a journey from the table above to inspect its critical events.\"\n />\n )}\n </div>\n </section>\n </div>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
@@ -27,7 +27,7 @@ function v() {
|
|
|
27
27
|
className: "flex items-center justify-between",
|
|
28
28
|
children: [/* @__PURE__ */ _("div", {
|
|
29
29
|
className: "relative flex-1 max-w-sm",
|
|
30
|
-
children: [/* @__PURE__ */ g(p, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-
|
|
30
|
+
children: [/* @__PURE__ */ g(p, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ g(r, {
|
|
31
31
|
placeholder: "Search roles...",
|
|
32
32
|
className: "pl-9",
|
|
33
33
|
value: v,
|
|
@@ -42,7 +42,7 @@ function v() {
|
|
|
42
42
|
})
|
|
43
43
|
})]
|
|
44
44
|
}), S ? /* @__PURE__ */ g(n, {
|
|
45
|
-
icon: /* @__PURE__ */ g(m, { className: "h-8 w-8 text-
|
|
45
|
+
icon: /* @__PURE__ */ g(m, { className: "h-8 w-8 text-status-error-text" }),
|
|
46
46
|
title: "Error loading roles",
|
|
47
47
|
description: S.message
|
|
48
48
|
}) : /* @__PURE__ */ g(a, {
|
|
@@ -55,7 +55,7 @@ function v() {
|
|
|
55
55
|
children: [/* @__PURE__ */ _("div", {
|
|
56
56
|
className: "flex items-center gap-2",
|
|
57
57
|
children: [/* @__PURE__ */ g("span", {
|
|
58
|
-
className: "font-semibold text-
|
|
58
|
+
className: "font-semibold text-text-primary",
|
|
59
59
|
children: t.name
|
|
60
60
|
}), t.isSystem && /* @__PURE__ */ g(e, {
|
|
61
61
|
variant: "secondary",
|
|
@@ -63,7 +63,7 @@ function v() {
|
|
|
63
63
|
children: "System"
|
|
64
64
|
})]
|
|
65
65
|
}), t.description && /* @__PURE__ */ g("span", {
|
|
66
|
-
className: "mt-0.5 line-clamp-1 text-xs text-
|
|
66
|
+
className: "mt-0.5 line-clamp-1 text-xs text-text-secondary",
|
|
67
67
|
children: t.description
|
|
68
68
|
})]
|
|
69
69
|
})
|
|
@@ -74,7 +74,7 @@ function v() {
|
|
|
74
74
|
align: "center",
|
|
75
75
|
width: "80px",
|
|
76
76
|
render: (e) => /* @__PURE__ */ g("span", {
|
|
77
|
-
className: "rounded bg-
|
|
77
|
+
className: "rounded bg-bg-sunken px-2 py-0.5 text-xs font-medium",
|
|
78
78
|
children: e.priority
|
|
79
79
|
})
|
|
80
80
|
},
|
|
@@ -85,16 +85,16 @@ function v() {
|
|
|
85
85
|
className: "flex max-w-md flex-wrap gap-1 text-left",
|
|
86
86
|
children: t.defaultPermissions && t.defaultPermissions.length > 0 ? t.defaultPermissions.map((t) => /* @__PURE__ */ _(e, {
|
|
87
87
|
variant: "outline",
|
|
88
|
-
className: "bg-
|
|
88
|
+
className: "bg-bg-surface/50 px-1 py-0 font-normal text-[10px]",
|
|
89
89
|
children: [/* @__PURE__ */ _("span", {
|
|
90
|
-
className: "mr-1 text-
|
|
90
|
+
className: "mr-1 text-text-secondary",
|
|
91
91
|
children: [t.resourceType, ":"]
|
|
92
92
|
}), /* @__PURE__ */ g("span", {
|
|
93
93
|
className: "font-medium",
|
|
94
94
|
children: t.level
|
|
95
95
|
})]
|
|
96
96
|
}, t.id)) : /* @__PURE__ */ g("span", {
|
|
97
|
-
className: "text-xs italic text-
|
|
97
|
+
className: "text-xs italic text-text-secondary",
|
|
98
98
|
children: "No permissions defined"
|
|
99
99
|
})
|
|
100
100
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RolesPermissionsPage.js","names":[],"sources":["../../../src/admin/pages/RolesPermissionsPage.tsx"],"sourcesContent":["import { Shield, Search, Plus, Pencil, Trash2 } from 'lucide-react';\nimport { useState, useMemo } from 'react';\nimport { PageLayout, Input, Button, EmptyState, DataTable, Badge, RowActions } from '../components';\nimport { useListRolesQuery, useGetResourceTypesQuery } from '../../generated/global-operations';\n\nexport function RolesPermissionsPage() {\n const [searchQuery, setSearchQuery] = useState('');\n\n const {\n data: rolesData,\n loading: rolesLoading,\n error: rolesError,\n } = useListRolesQuery({\n variables: { scopeId: 'global' },\n });\n\n const { data: resourceTypesData } = useGetResourceTypesQuery();\n\n const roles = useMemo(() => rolesData?.roles ?? [], [rolesData]);\n\n const filteredRoles = useMemo(() => {\n if (!searchQuery.trim()) return roles;\n const query = searchQuery.toLowerCase();\n return roles.filter(\n (role: any) =>\n role.name.toLowerCase().includes(query) || role.description?.toLowerCase().includes(query)\n );\n }, [roles, searchQuery]);\n\n const columns = [\n {\n key: 'name',\n header: 'Role Name',\n render: (role: any) => (\n <div className=\"flex flex-col text-left\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-semibold text-
|
|
1
|
+
{"version":3,"file":"RolesPermissionsPage.js","names":[],"sources":["../../../src/admin/pages/RolesPermissionsPage.tsx"],"sourcesContent":["import { Shield, Search, Plus, Pencil, Trash2 } from 'lucide-react';\nimport { useState, useMemo } from 'react';\nimport { PageLayout, Input, Button, EmptyState, DataTable, Badge, RowActions } from '../components';\nimport { useListRolesQuery, useGetResourceTypesQuery } from '../../generated/global-operations';\n\nexport function RolesPermissionsPage() {\n const [searchQuery, setSearchQuery] = useState('');\n\n const {\n data: rolesData,\n loading: rolesLoading,\n error: rolesError,\n } = useListRolesQuery({\n variables: { scopeId: 'global' },\n });\n\n const { data: resourceTypesData } = useGetResourceTypesQuery();\n\n const roles = useMemo(() => rolesData?.roles ?? [], [rolesData]);\n\n const filteredRoles = useMemo(() => {\n if (!searchQuery.trim()) return roles;\n const query = searchQuery.toLowerCase();\n return roles.filter(\n (role: any) =>\n role.name.toLowerCase().includes(query) || role.description?.toLowerCase().includes(query)\n );\n }, [roles, searchQuery]);\n\n const columns = [\n {\n key: 'name',\n header: 'Role Name',\n render: (role: any) => (\n <div className=\"flex flex-col text-left\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-semibold text-text-primary\">{role.name}</span>\n {role.isSystem && (\n <Badge variant=\"secondary\" className=\"h-4 px-1 text-[10px]\">\n System\n </Badge>\n )}\n </div>\n {role.description && (\n <span className=\"mt-0.5 line-clamp-1 text-xs text-text-secondary\">\n {role.description}\n </span>\n )}\n </div>\n ),\n },\n {\n key: 'priority',\n header: 'Priority',\n align: 'center' as const,\n width: '80px',\n render: (role: any) => (\n <span className=\"rounded bg-bg-sunken px-2 py-0.5 text-xs font-medium\">{role.priority}</span>\n ),\n },\n {\n key: 'permissions',\n header: 'Default Permissions',\n render: (role: any) => (\n <div className=\"flex max-w-md flex-wrap gap-1 text-left\">\n {role.defaultPermissions && role.defaultPermissions.length > 0 ? (\n role.defaultPermissions.map((perm: any) => (\n <Badge\n key={perm.id}\n variant=\"outline\"\n className=\"bg-bg-surface/50 px-1 py-0 font-normal text-[10px]\"\n >\n <span className=\"mr-1 text-text-secondary\">{perm.resourceType}:</span>\n <span className=\"font-medium\">{perm.level}</span>\n </Badge>\n ))\n ) : (\n <span className=\"text-xs italic text-text-secondary\">No permissions defined</span>\n )}\n </div>\n ),\n },\n {\n key: 'actions',\n header: '',\n align: 'right' as const,\n width: '50px',\n render: (role: any) => (\n <RowActions\n actions={[\n {\n key: 'edit',\n label: 'Edit Role',\n icon: <Pencil className=\"h-4 w-4\" />,\n onClick: () => console.log('Edit', role.id),\n disabled: role.isSystem,\n },\n {\n key: 'delete',\n label: 'Delete Role',\n icon: <Trash2 className=\"h-4 w-4\" />,\n onClick: () => console.log('Delete', role.id),\n destructive: true,\n disabled: role.isSystem,\n separator: true,\n },\n ]}\n />\n ),\n },\n ];\n\n return (\n <PageLayout\n title=\"Roles & Permissions\"\n description=\"Manage platform roles and global permissions\"\n actions={\n <Button size=\"sm\">\n <Plus className=\"mr-2 h-4 w-4\" />\n Create Role\n </Button>\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <div className=\"relative flex-1 max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search roles...\"\n className=\"pl-9\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n />\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"outline\" className=\"h-8\">\n {resourceTypesData?.resourceTypes?.length ?? 0} Resource Types\n </Badge>\n </div>\n </div>\n\n {rolesError ? (\n <EmptyState\n icon={<Shield className=\"h-8 w-8 text-status-error-text\" />}\n title=\"Error loading roles\"\n description={rolesError.message}\n />\n ) : (\n <DataTable\n columns={columns}\n data={filteredRoles}\n keyExtractor={(role) => role.id}\n loading={rolesLoading}\n emptyState={\n <EmptyState\n icon={<Shield className=\"h-8 w-8\" />}\n title=\"No roles found\"\n description={\n searchQuery\n ? `No roles match \"${searchQuery}\"`\n : 'Roles and permissions will appear here when they are created'\n }\n action={\n searchQuery ? (\n <Button variant=\"outline\" size=\"sm\" onClick={() => setSearchQuery('')}>\n Clear Search\n </Button>\n ) : undefined\n }\n />\n }\n />\n )}\n </div>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -170,9 +170,9 @@ var k = [
|
|
|
170
170
|
cache: /* @__PURE__ */ D(S, { className: "h-4 w-4" }),
|
|
171
171
|
queue: /* @__PURE__ */ D(E, { className: "h-4 w-4" })
|
|
172
172
|
}, N = {
|
|
173
|
-
critical: "bg-
|
|
173
|
+
critical: "bg-status-error-bg/10 text-status-error-text",
|
|
174
174
|
major: "bg-warning/10 text-warning",
|
|
175
|
-
minor: "bg-
|
|
175
|
+
minor: "bg-bg-sunken text-text-secondary"
|
|
176
176
|
};
|
|
177
177
|
function P() {
|
|
178
178
|
let { canViewSRE: b, isLoading: x } = e(), [S, E] = g(!1), P = k.filter((e) => e.status === "healthy").length, F = k.filter((e) => e.status === "degraded").length;
|
|
@@ -234,7 +234,7 @@ function P() {
|
|
|
234
234
|
}) }), /* @__PURE__ */ D(i, { children: /* @__PURE__ */ D("div", {
|
|
235
235
|
className: "space-y-3",
|
|
236
236
|
children: A.filter((e) => e.status !== "resolved").map((e) => /* @__PURE__ */ O("div", {
|
|
237
|
-
className: "flex items-start justify-between rounded-lg border border-border bg-
|
|
237
|
+
className: "flex items-start justify-between rounded-lg border border-border-subtle bg-bg-surface p-4",
|
|
238
238
|
children: [/* @__PURE__ */ O("div", {
|
|
239
239
|
className: "space-y-1",
|
|
240
240
|
children: [
|
|
@@ -249,7 +249,7 @@ function P() {
|
|
|
249
249
|
})]
|
|
250
250
|
}),
|
|
251
251
|
/* @__PURE__ */ O("div", {
|
|
252
|
-
className: "flex items-center gap-4 text-sm text-
|
|
252
|
+
className: "flex items-center gap-4 text-sm text-text-secondary",
|
|
253
253
|
children: [
|
|
254
254
|
/* @__PURE__ */ O("span", { children: ["Started ", d(e.startedAt)] }),
|
|
255
255
|
/* @__PURE__ */ D("span", { children: "•" }),
|
|
@@ -321,7 +321,7 @@ function P() {
|
|
|
321
321
|
width: "100px",
|
|
322
322
|
align: "right",
|
|
323
323
|
render: (e) => /* @__PURE__ */ O("span", {
|
|
324
|
-
className: e.errorRate > 1 ? "text-
|
|
324
|
+
className: e.errorRate > 1 ? "text-status-error-text" : "",
|
|
325
325
|
children: [e.errorRate.toFixed(2), "%"]
|
|
326
326
|
})
|
|
327
327
|
},
|
|
@@ -330,7 +330,7 @@ function P() {
|
|
|
330
330
|
header: "Region",
|
|
331
331
|
width: "120px",
|
|
332
332
|
render: (e) => /* @__PURE__ */ D("span", {
|
|
333
|
-
className: "text-
|
|
333
|
+
className: "text-text-secondary",
|
|
334
334
|
children: e.region
|
|
335
335
|
})
|
|
336
336
|
}
|
|
@@ -360,7 +360,7 @@ function P() {
|
|
|
360
360
|
header: "Version",
|
|
361
361
|
width: "140px",
|
|
362
362
|
render: (e) => /* @__PURE__ */ D("code", {
|
|
363
|
-
className: "text-xs bg-
|
|
363
|
+
className: "text-xs bg-bg-sunken px-1.5 py-0.5 rounded",
|
|
364
364
|
children: e.version
|
|
365
365
|
})
|
|
366
366
|
},
|
|
@@ -382,7 +382,7 @@ function P() {
|
|
|
382
382
|
header: "Deployed",
|
|
383
383
|
width: "140px",
|
|
384
384
|
render: (e) => /* @__PURE__ */ D("span", {
|
|
385
|
-
className: "text-
|
|
385
|
+
className: "text-text-secondary",
|
|
386
386
|
children: d(e.deployedAt)
|
|
387
387
|
})
|
|
388
388
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SREPage.js","names":[],"sources":["../../../src/admin/pages/SREPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Activity,\n Server,\n Database,\n Cpu,\n HardDrive,\n Wifi,\n AlertTriangle,\n CheckCircle2,\n RefreshCw,\n TrendingUp,\n} from 'lucide-react';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n MetricCard,\n MetricsGrid,\n Card,\n CardHeader,\n CardTitle,\n CardContent,\n Badge,\n Button,\n EmptyState,\n DataTable,\n HealthStatusBadge,\n DeploymentStatusBadge,\n PermissionGuard,\n} from '../components';\nimport { formatRelativeTime } from '../utils/formatters';\n\n/**\n * Service health entity\n */\ninterface ServiceHealth {\n id: string;\n name: string;\n type: 'api' | 'worker' | 'database' | 'cache' | 'queue';\n status: 'healthy' | 'degraded' | 'down';\n uptime: number; // percentage\n latency: number; // ms\n errorRate: number; // percentage\n lastChecked: string;\n region: string;\n}\n\n/**\n * Incident entity\n */\ninterface Incident {\n id: string;\n title: string;\n severity: 'critical' | 'major' | 'minor';\n status: 'active' | 'investigating' | 'resolved';\n affectedServices: string[];\n startedAt: string;\n resolvedAt?: string;\n}\n\n/**\n * Deployment entity\n */\ninterface Deployment {\n id: string;\n service: string;\n version: string;\n status: 'success' | 'failed' | 'in_progress' | 'rolled_back';\n deployedAt: string;\n deployedBy: string;\n duration: number; // seconds\n}\n\n// Mock data\nconst mockServices: ServiceHealth[] = [\n {\n id: 's1',\n name: 'wspace-public-gateway',\n type: 'api',\n status: 'healthy',\n uptime: 99.99,\n latency: 45,\n errorRate: 0.01,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's2',\n name: 'global-auth-svc',\n type: 'api',\n status: 'healthy',\n uptime: 99.95,\n latency: 32,\n errorRate: 0.02,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's3',\n name: 'wspace-tags-svc',\n type: 'api',\n status: 'healthy',\n uptime: 99.98,\n latency: 28,\n errorRate: 0.01,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's4',\n name: 'global-billing-svc',\n type: 'api',\n status: 'degraded',\n uptime: 98.5,\n latency: 250,\n errorRate: 2.5,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's5',\n name: 'wspace-calendar-svc',\n type: 'api',\n status: 'healthy',\n uptime: 99.97,\n latency: 35,\n errorRate: 0.03,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's6',\n name: 'postgresql-primary',\n type: 'database',\n status: 'healthy',\n uptime: 99.999,\n latency: 5,\n errorRate: 0,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's7',\n name: 'valkey-cluster',\n type: 'cache',\n status: 'healthy',\n uptime: 99.99,\n latency: 2,\n errorRate: 0,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's8',\n name: 'nats-cluster',\n type: 'queue',\n status: 'healthy',\n uptime: 99.98,\n latency: 8,\n errorRate: 0.01,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's9',\n name: 'temporal-worker',\n type: 'worker',\n status: 'healthy',\n uptime: 99.95,\n latency: 15,\n errorRate: 0.05,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n];\n\nconst mockIncidents: Incident[] = [\n {\n id: 'inc-1',\n title: 'Elevated latency on billing service',\n severity: 'major',\n status: 'investigating',\n affectedServices: ['global-billing-svc'],\n startedAt: new Date(Date.now() - 30 * 60 * 1000).toISOString(),\n },\n {\n id: 'inc-2',\n title: 'Intermittent database connection errors',\n severity: 'minor',\n status: 'resolved',\n affectedServices: ['postgresql-primary'],\n startedAt: new Date(Date.now() - 4 * 60 * 60 * 1000).toISOString(),\n resolvedAt: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(),\n },\n];\n\nconst mockDeployments: Deployment[] = [\n {\n id: 'd1',\n service: 'wspace-tags-svc',\n version: '2025.01.18.1',\n status: 'success',\n deployedAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),\n deployedBy: 'fluxcd',\n duration: 120,\n },\n {\n id: 'd2',\n service: 'global-auth-svc',\n version: '2025.01.18.0',\n status: 'success',\n deployedAt: new Date(Date.now() - 5 * 60 * 60 * 1000).toISOString(),\n deployedBy: 'fluxcd',\n duration: 95,\n },\n {\n id: 'd3',\n service: 'wspace-calendar-svc',\n version: '2025.01.17.2',\n status: 'rolled_back',\n deployedAt: new Date(Date.now() - 12 * 60 * 60 * 1000).toISOString(),\n deployedBy: 'fluxcd',\n duration: 180,\n },\n {\n id: 'd4',\n service: 'global-billing-svc',\n version: '2025.01.17.1',\n status: 'success',\n deployedAt: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),\n deployedBy: 'fluxcd',\n duration: 110,\n },\n];\n\nconst typeIcons: Record<string, React.ReactNode> = {\n api: <Server className=\"h-4 w-4\" />,\n worker: <Cpu className=\"h-4 w-4\" />,\n database: <Database className=\"h-4 w-4\" />,\n cache: <HardDrive className=\"h-4 w-4\" />,\n queue: <Wifi className=\"h-4 w-4\" />,\n};\n\nconst severityColors: Record<string, string> = {\n critical: 'bg-destructive/10 text-destructive',\n major: 'bg-warning/10 text-warning',\n minor: 'bg-muted text-muted-foreground',\n};\n\n/**\n * SRE Monitoring page\n */\nexport function SREPage() {\n const { canViewSRE, isLoading: permissionsLoading } = useAdminPermissions();\n\n const [refreshing, setRefreshing] = useState(false);\n\n // Calculate metrics\n const healthyServices = mockServices.filter((s) => s.status === 'healthy').length;\n const degradedServices = mockServices.filter((s) => s.status === 'degraded').length;\n // Note: downServices available for future use when we have down services\n const _downServices = mockServices.filter((s) => s.status === 'down').length;\n void _downServices;\n const avgUptime = mockServices.reduce((sum, s) => sum + s.uptime, 0) / mockServices.length;\n const avgLatency = mockServices.reduce((sum, s) => sum + s.latency, 0) / mockServices.length;\n const activeIncidents = mockIncidents.filter((i) => i.status !== 'resolved').length;\n\n const handleRefresh = () => {\n setRefreshing(true);\n setTimeout(() => setRefreshing(false), 1000);\n };\n\n const serviceColumns = [\n {\n key: 'name',\n header: 'Service',\n render: (service: ServiceHealth) => (\n <div className=\"flex items-center gap-2\">\n {typeIcons[service.type]}\n <span className=\"font-medium\">{service.name}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n width: '100px',\n render: (service: ServiceHealth) => <HealthStatusBadge status={service.status} />,\n },\n {\n key: 'uptime',\n header: 'Uptime',\n width: '100px',\n align: 'right' as const,\n render: (service: ServiceHealth) => (\n <span className={service.uptime < 99 ? 'text-warning' : 'text-success'}>\n {service.uptime.toFixed(2)}%\n </span>\n ),\n },\n {\n key: 'latency',\n header: 'Latency',\n width: '100px',\n align: 'right' as const,\n render: (service: ServiceHealth) => (\n <span className={service.latency > 100 ? 'text-warning' : ''}>{service.latency}ms</span>\n ),\n },\n {\n key: 'errorRate',\n header: 'Error Rate',\n width: '100px',\n align: 'right' as const,\n render: (service: ServiceHealth) => (\n <span className={service.errorRate > 1 ? 'text-destructive' : ''}>\n {service.errorRate.toFixed(2)}%\n </span>\n ),\n },\n {\n key: 'region',\n header: 'Region',\n width: '120px',\n render: (service: ServiceHealth) => (\n <span className=\"text-muted-foreground\">{service.region}</span>\n ),\n },\n ];\n\n const deploymentColumns = [\n {\n key: 'service',\n header: 'Service',\n render: (d: Deployment) => <span className=\"font-medium\">{d.service}</span>,\n },\n {\n key: 'version',\n header: 'Version',\n width: '140px',\n render: (d: Deployment) => (\n <code className=\"text-xs bg-muted px-1.5 py-0.5 rounded\">{d.version}</code>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n width: '120px',\n render: (d: Deployment) => <DeploymentStatusBadge status={d.status} />,\n },\n {\n key: 'duration',\n header: 'Duration',\n width: '100px',\n align: 'right' as const,\n render: (d: Deployment) => `${d.duration}s`,\n },\n {\n key: 'deployedAt',\n header: 'Deployed',\n width: '140px',\n render: (d: Deployment) => (\n <span className=\"text-muted-foreground\">{formatRelativeTime(d.deployedAt)}</span>\n ),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewSRE}\n isLoading={permissionsLoading}\n pageTitle=\"SRE Monitoring\"\n denyMessage=\"You don't have permission to view SRE monitoring.\"\n >\n <PageLayout\n title=\"SRE Monitoring\"\n description=\"Service health, incidents, and deployment monitoring\"\n actions={\n <Button variant=\"outline\" onClick={handleRefresh} disabled={refreshing}>\n <RefreshCw className={`mr-2 h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />\n Refresh\n </Button>\n }\n >\n <div className=\"space-y-6\">\n {/* Metrics */}\n <MetricsGrid>\n <MetricCard\n title=\"Healthy Services\"\n value={healthyServices}\n icon={<CheckCircle2 className=\"h-5 w-5 text-success\" />}\n subtitle={`of ${mockServices.length} total`}\n />\n <MetricCard\n title=\"Degraded\"\n value={degradedServices}\n icon={<AlertTriangle className=\"h-5 w-5 text-warning\" />}\n priority={degradedServices > 0 ? 'high' : 'normal'}\n />\n <MetricCard\n title=\"Avg Uptime\"\n value={`${avgUptime.toFixed(2)}%`}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <MetricCard\n title=\"Avg Latency\"\n value={`${avgLatency.toFixed(0)}ms`}\n icon={<Activity className=\"h-5 w-5\" />}\n />\n </MetricsGrid>\n\n {/* Active Incidents */}\n {activeIncidents > 0 && (\n <Card className=\"border-warning/50 bg-warning/5\">\n <CardHeader>\n <CardTitle className=\"text-lg flex items-center gap-2 text-warning\">\n <AlertTriangle className=\"h-5 w-5\" />\n Active Incidents ({activeIncidents})\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {mockIncidents\n .filter((i) => i.status !== 'resolved')\n .map((incident) => (\n <div\n key={incident.id}\n className=\"flex items-start justify-between rounded-lg border border-border bg-background p-4\"\n >\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <Badge className={severityColors[incident.severity]}>\n {incident.severity.toUpperCase()}\n </Badge>\n <span className=\"font-medium\">{incident.title}</span>\n </div>\n <div className=\"flex items-center gap-4 text-sm text-muted-foreground\">\n <span>Started {formatRelativeTime(incident.startedAt)}</span>\n <span>•</span>\n <span>Status: {incident.status}</span>\n </div>\n <div className=\"flex flex-wrap gap-1 mt-2\">\n {incident.affectedServices.map((svc) => (\n <Badge key={svc} variant=\"outline\" className=\"text-xs\">\n {svc}\n </Badge>\n ))}\n </div>\n </div>\n <Button size=\"sm\" variant=\"outline\">\n View\n </Button>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n )}\n\n {/* Service Health */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-lg flex items-center gap-2\">\n <Server className=\"h-5 w-5\" />\n Service Health\n </CardTitle>\n </CardHeader>\n <CardContent>\n <DataTable\n columns={serviceColumns}\n data={mockServices}\n keyExtractor={(s) => s.id}\n emptyState={\n <EmptyState icon={<Server className=\"h-8 w-8\" />} title=\"No services found\" />\n }\n />\n </CardContent>\n </Card>\n\n {/* Recent Deployments */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-lg flex items-center gap-2\">\n <RefreshCw className=\"h-5 w-5\" />\n Recent Deployments\n </CardTitle>\n </CardHeader>\n <CardContent>\n <DataTable\n columns={deploymentColumns}\n data={mockDeployments}\n keyExtractor={(d) => d.id}\n emptyState={\n <EmptyState\n icon={<RefreshCw className=\"h-8 w-8\" />}\n title=\"No recent deployments\"\n />\n }\n />\n </CardContent>\n </Card>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SREPage.js","names":[],"sources":["../../../src/admin/pages/SREPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Activity,\n Server,\n Database,\n Cpu,\n HardDrive,\n Wifi,\n AlertTriangle,\n CheckCircle2,\n RefreshCw,\n TrendingUp,\n} from 'lucide-react';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport {\n PageLayout,\n MetricCard,\n MetricsGrid,\n Card,\n CardHeader,\n CardTitle,\n CardContent,\n Badge,\n Button,\n EmptyState,\n DataTable,\n HealthStatusBadge,\n DeploymentStatusBadge,\n PermissionGuard,\n} from '../components';\nimport { formatRelativeTime } from '../utils/formatters';\n\n/**\n * Service health entity\n */\ninterface ServiceHealth {\n id: string;\n name: string;\n type: 'api' | 'worker' | 'database' | 'cache' | 'queue';\n status: 'healthy' | 'degraded' | 'down';\n uptime: number; // percentage\n latency: number; // ms\n errorRate: number; // percentage\n lastChecked: string;\n region: string;\n}\n\n/**\n * Incident entity\n */\ninterface Incident {\n id: string;\n title: string;\n severity: 'critical' | 'major' | 'minor';\n status: 'active' | 'investigating' | 'resolved';\n affectedServices: string[];\n startedAt: string;\n resolvedAt?: string;\n}\n\n/**\n * Deployment entity\n */\ninterface Deployment {\n id: string;\n service: string;\n version: string;\n status: 'success' | 'failed' | 'in_progress' | 'rolled_back';\n deployedAt: string;\n deployedBy: string;\n duration: number; // seconds\n}\n\n// Mock data\nconst mockServices: ServiceHealth[] = [\n {\n id: 's1',\n name: 'wspace-public-gateway',\n type: 'api',\n status: 'healthy',\n uptime: 99.99,\n latency: 45,\n errorRate: 0.01,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's2',\n name: 'global-auth-svc',\n type: 'api',\n status: 'healthy',\n uptime: 99.95,\n latency: 32,\n errorRate: 0.02,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's3',\n name: 'wspace-tags-svc',\n type: 'api',\n status: 'healthy',\n uptime: 99.98,\n latency: 28,\n errorRate: 0.01,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's4',\n name: 'global-billing-svc',\n type: 'api',\n status: 'degraded',\n uptime: 98.5,\n latency: 250,\n errorRate: 2.5,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's5',\n name: 'wspace-calendar-svc',\n type: 'api',\n status: 'healthy',\n uptime: 99.97,\n latency: 35,\n errorRate: 0.03,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's6',\n name: 'postgresql-primary',\n type: 'database',\n status: 'healthy',\n uptime: 99.999,\n latency: 5,\n errorRate: 0,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's7',\n name: 'valkey-cluster',\n type: 'cache',\n status: 'healthy',\n uptime: 99.99,\n latency: 2,\n errorRate: 0,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's8',\n name: 'nats-cluster',\n type: 'queue',\n status: 'healthy',\n uptime: 99.98,\n latency: 8,\n errorRate: 0.01,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n {\n id: 's9',\n name: 'temporal-worker',\n type: 'worker',\n status: 'healthy',\n uptime: 99.95,\n latency: 15,\n errorRate: 0.05,\n lastChecked: new Date().toISOString(),\n region: 'ap-south-1',\n },\n];\n\nconst mockIncidents: Incident[] = [\n {\n id: 'inc-1',\n title: 'Elevated latency on billing service',\n severity: 'major',\n status: 'investigating',\n affectedServices: ['global-billing-svc'],\n startedAt: new Date(Date.now() - 30 * 60 * 1000).toISOString(),\n },\n {\n id: 'inc-2',\n title: 'Intermittent database connection errors',\n severity: 'minor',\n status: 'resolved',\n affectedServices: ['postgresql-primary'],\n startedAt: new Date(Date.now() - 4 * 60 * 60 * 1000).toISOString(),\n resolvedAt: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(),\n },\n];\n\nconst mockDeployments: Deployment[] = [\n {\n id: 'd1',\n service: 'wspace-tags-svc',\n version: '2025.01.18.1',\n status: 'success',\n deployedAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),\n deployedBy: 'fluxcd',\n duration: 120,\n },\n {\n id: 'd2',\n service: 'global-auth-svc',\n version: '2025.01.18.0',\n status: 'success',\n deployedAt: new Date(Date.now() - 5 * 60 * 60 * 1000).toISOString(),\n deployedBy: 'fluxcd',\n duration: 95,\n },\n {\n id: 'd3',\n service: 'wspace-calendar-svc',\n version: '2025.01.17.2',\n status: 'rolled_back',\n deployedAt: new Date(Date.now() - 12 * 60 * 60 * 1000).toISOString(),\n deployedBy: 'fluxcd',\n duration: 180,\n },\n {\n id: 'd4',\n service: 'global-billing-svc',\n version: '2025.01.17.1',\n status: 'success',\n deployedAt: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(),\n deployedBy: 'fluxcd',\n duration: 110,\n },\n];\n\nconst typeIcons: Record<string, React.ReactNode> = {\n api: <Server className=\"h-4 w-4\" />,\n worker: <Cpu className=\"h-4 w-4\" />,\n database: <Database className=\"h-4 w-4\" />,\n cache: <HardDrive className=\"h-4 w-4\" />,\n queue: <Wifi className=\"h-4 w-4\" />,\n};\n\nconst severityColors: Record<string, string> = {\n critical: 'bg-status-error-bg/10 text-status-error-text',\n major: 'bg-warning/10 text-warning',\n minor: 'bg-bg-sunken text-text-secondary',\n};\n\n/**\n * SRE Monitoring page\n */\nexport function SREPage() {\n const { canViewSRE, isLoading: permissionsLoading } = useAdminPermissions();\n\n const [refreshing, setRefreshing] = useState(false);\n\n // Calculate metrics\n const healthyServices = mockServices.filter((s) => s.status === 'healthy').length;\n const degradedServices = mockServices.filter((s) => s.status === 'degraded').length;\n // Note: downServices available for future use when we have down services\n const _downServices = mockServices.filter((s) => s.status === 'down').length;\n void _downServices;\n const avgUptime = mockServices.reduce((sum, s) => sum + s.uptime, 0) / mockServices.length;\n const avgLatency = mockServices.reduce((sum, s) => sum + s.latency, 0) / mockServices.length;\n const activeIncidents = mockIncidents.filter((i) => i.status !== 'resolved').length;\n\n const handleRefresh = () => {\n setRefreshing(true);\n setTimeout(() => setRefreshing(false), 1000);\n };\n\n const serviceColumns = [\n {\n key: 'name',\n header: 'Service',\n render: (service: ServiceHealth) => (\n <div className=\"flex items-center gap-2\">\n {typeIcons[service.type]}\n <span className=\"font-medium\">{service.name}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n width: '100px',\n render: (service: ServiceHealth) => <HealthStatusBadge status={service.status} />,\n },\n {\n key: 'uptime',\n header: 'Uptime',\n width: '100px',\n align: 'right' as const,\n render: (service: ServiceHealth) => (\n <span className={service.uptime < 99 ? 'text-warning' : 'text-success'}>\n {service.uptime.toFixed(2)}%\n </span>\n ),\n },\n {\n key: 'latency',\n header: 'Latency',\n width: '100px',\n align: 'right' as const,\n render: (service: ServiceHealth) => (\n <span className={service.latency > 100 ? 'text-warning' : ''}>{service.latency}ms</span>\n ),\n },\n {\n key: 'errorRate',\n header: 'Error Rate',\n width: '100px',\n align: 'right' as const,\n render: (service: ServiceHealth) => (\n <span className={service.errorRate > 1 ? 'text-status-error-text' : ''}>\n {service.errorRate.toFixed(2)}%\n </span>\n ),\n },\n {\n key: 'region',\n header: 'Region',\n width: '120px',\n render: (service: ServiceHealth) => (\n <span className=\"text-text-secondary\">{service.region}</span>\n ),\n },\n ];\n\n const deploymentColumns = [\n {\n key: 'service',\n header: 'Service',\n render: (d: Deployment) => <span className=\"font-medium\">{d.service}</span>,\n },\n {\n key: 'version',\n header: 'Version',\n width: '140px',\n render: (d: Deployment) => (\n <code className=\"text-xs bg-bg-sunken px-1.5 py-0.5 rounded\">{d.version}</code>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n width: '120px',\n render: (d: Deployment) => <DeploymentStatusBadge status={d.status} />,\n },\n {\n key: 'duration',\n header: 'Duration',\n width: '100px',\n align: 'right' as const,\n render: (d: Deployment) => `${d.duration}s`,\n },\n {\n key: 'deployedAt',\n header: 'Deployed',\n width: '140px',\n render: (d: Deployment) => (\n <span className=\"text-text-secondary\">{formatRelativeTime(d.deployedAt)}</span>\n ),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewSRE}\n isLoading={permissionsLoading}\n pageTitle=\"SRE Monitoring\"\n denyMessage=\"You don't have permission to view SRE monitoring.\"\n >\n <PageLayout\n title=\"SRE Monitoring\"\n description=\"Service health, incidents, and deployment monitoring\"\n actions={\n <Button variant=\"outline\" onClick={handleRefresh} disabled={refreshing}>\n <RefreshCw className={`mr-2 h-4 w-4 ${refreshing ? 'animate-spin' : ''}`} />\n Refresh\n </Button>\n }\n >\n <div className=\"space-y-6\">\n {/* Metrics */}\n <MetricsGrid>\n <MetricCard\n title=\"Healthy Services\"\n value={healthyServices}\n icon={<CheckCircle2 className=\"h-5 w-5 text-success\" />}\n subtitle={`of ${mockServices.length} total`}\n />\n <MetricCard\n title=\"Degraded\"\n value={degradedServices}\n icon={<AlertTriangle className=\"h-5 w-5 text-warning\" />}\n priority={degradedServices > 0 ? 'high' : 'normal'}\n />\n <MetricCard\n title=\"Avg Uptime\"\n value={`${avgUptime.toFixed(2)}%`}\n icon={<TrendingUp className=\"h-5 w-5\" />}\n />\n <MetricCard\n title=\"Avg Latency\"\n value={`${avgLatency.toFixed(0)}ms`}\n icon={<Activity className=\"h-5 w-5\" />}\n />\n </MetricsGrid>\n\n {/* Active Incidents */}\n {activeIncidents > 0 && (\n <Card className=\"border-warning/50 bg-warning/5\">\n <CardHeader>\n <CardTitle className=\"text-lg flex items-center gap-2 text-warning\">\n <AlertTriangle className=\"h-5 w-5\" />\n Active Incidents ({activeIncidents})\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-3\">\n {mockIncidents\n .filter((i) => i.status !== 'resolved')\n .map((incident) => (\n <div\n key={incident.id}\n className=\"flex items-start justify-between rounded-lg border border-border-subtle bg-bg-surface p-4\"\n >\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <Badge className={severityColors[incident.severity]}>\n {incident.severity.toUpperCase()}\n </Badge>\n <span className=\"font-medium\">{incident.title}</span>\n </div>\n <div className=\"flex items-center gap-4 text-sm text-text-secondary\">\n <span>Started {formatRelativeTime(incident.startedAt)}</span>\n <span>•</span>\n <span>Status: {incident.status}</span>\n </div>\n <div className=\"flex flex-wrap gap-1 mt-2\">\n {incident.affectedServices.map((svc) => (\n <Badge key={svc} variant=\"outline\" className=\"text-xs\">\n {svc}\n </Badge>\n ))}\n </div>\n </div>\n <Button size=\"sm\" variant=\"outline\">\n View\n </Button>\n </div>\n ))}\n </div>\n </CardContent>\n </Card>\n )}\n\n {/* Service Health */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-lg flex items-center gap-2\">\n <Server className=\"h-5 w-5\" />\n Service Health\n </CardTitle>\n </CardHeader>\n <CardContent>\n <DataTable\n columns={serviceColumns}\n data={mockServices}\n keyExtractor={(s) => s.id}\n emptyState={\n <EmptyState icon={<Server className=\"h-8 w-8\" />} title=\"No services found\" />\n }\n />\n </CardContent>\n </Card>\n\n {/* Recent Deployments */}\n <Card>\n <CardHeader>\n <CardTitle className=\"text-lg flex items-center gap-2\">\n <RefreshCw className=\"h-5 w-5\" />\n Recent Deployments\n </CardTitle>\n </CardHeader>\n <CardContent>\n <DataTable\n columns={deploymentColumns}\n data={mockDeployments}\n keyExtractor={(d) => d.id}\n emptyState={\n <EmptyState\n icon={<RefreshCw className=\"h-8 w-8\" />}\n title=\"No recent deployments\"\n />\n }\n />\n </CardContent>\n </Card>\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AA0EA,IAAM,IAAgC;CACpC;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,WAAW;EACX,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,QAAQ;EACT;CACD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,WAAW;EACX,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,QAAQ;EACT;CACD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,WAAW;EACX,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,QAAQ;EACT;CACD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,WAAW;EACX,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,QAAQ;EACT;CACD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,WAAW;EACX,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,QAAQ;EACT;CACD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,WAAW;EACX,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,QAAQ;EACT;CACD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,WAAW;EACX,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,QAAQ;EACT;CACD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,WAAW;EACX,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,QAAQ;EACT;CACD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,QAAQ;EACR,SAAS;EACT,WAAW;EACX,8BAAa,IAAI,MAAM,EAAC,aAAa;EACrC,QAAQ;EACT;CACF,EAEK,IAA4B,CAChC;CACE,IAAI;CACJ,OAAO;CACP,UAAU;CACV,QAAQ;CACR,kBAAkB,CAAC,qBAAqB;CACxC,4BAAW,IAAI,KAAK,KAAK,KAAK,GAAG,OAAU,IAAK,EAAC,aAAa;CAC/D,EACD;CACE,IAAI;CACJ,OAAO;CACP,UAAU;CACV,QAAQ;CACR,kBAAkB,CAAC,qBAAqB;CACxC,4BAAW,IAAI,KAAK,KAAK,KAAK,GAAG,QAAc,IAAK,EAAC,aAAa;CAClE,6BAAY,IAAI,KAAK,KAAK,KAAK,GAAG,QAAc,IAAK,EAAC,aAAa;CACpE,CACF,EAEK,IAAgC;CACpC;EACE,IAAI;EACJ,SAAS;EACT,SAAS;EACT,QAAQ;EACR,6BAAY,IAAI,KAAK,KAAK,KAAK,GAAG,OAAc,IAAK,EAAC,aAAa;EACnE,YAAY;EACZ,UAAU;EACX;CACD;EACE,IAAI;EACJ,SAAS;EACT,SAAS;EACT,QAAQ;EACR,6BAAY,IAAI,KAAK,KAAK,KAAK,GAAG,MAAS,KAAK,IAAK,EAAC,aAAa;EACnE,YAAY;EACZ,UAAU;EACX;CACD;EACE,IAAI;EACJ,SAAS;EACT,SAAS;EACT,QAAQ;EACR,6BAAY,IAAI,KAAK,KAAK,KAAK,GAAG,MAAU,KAAK,IAAK,EAAC,aAAa;EACpE,YAAY;EACZ,UAAU;EACX;CACD;EACE,IAAI;EACJ,SAAS;EACT,SAAS;EACT,QAAQ;EACR,6BAAY,IAAI,KAAK,KAAK,KAAK,GAAG,OAAU,KAAK,IAAK,EAAC,aAAa;EACpE,YAAY;EACZ,UAAU;EACX;CACF,EAEK,IAA6C;CACjD,KAAK,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;CACnC,QAAQ,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;CACnC,UAAU,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;CAC1C,OAAO,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;CACxC,OAAO,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;CACpC,EAEK,IAAyC;CAC7C,UAAU;CACV,OAAO;CACP,OAAO;CACR;AAKD,SAAgB,IAAU;CACxB,IAAM,EAAE,eAAY,WAAW,MAAuB,GAAqB,EAErE,CAAC,GAAY,KAAiB,EAAS,GAAM,EAG7C,IAAkB,EAAa,QAAQ,MAAM,EAAE,WAAW,UAAU,CAAC,QACrE,IAAmB,EAAa,QAAQ,MAAM,EAAE,WAAW,WAAW,CAAC;AAEvD,GAAa,QAAQ,MAAM,EAAE,WAAW,OAAO,CAAC;CAEtE,IAAM,IAAY,EAAa,QAAQ,GAAK,MAAM,IAAM,EAAE,QAAQ,EAAE,GAAG,EAAa,QAC9E,IAAa,EAAa,QAAQ,GAAK,MAAM,IAAM,EAAE,SAAS,EAAE,GAAG,EAAa,QAChF,IAAkB,EAAc,QAAQ,MAAM,EAAE,WAAW,WAAW,CAAC;AAsG7E,QACE,kBAAC,GAAD;EACE,SAAS;EACT,WAAW;EACX,WAAU;EACV,aAAY;YAEZ,kBAAC,GAAD;GACE,OAAM;GACN,aAAY;GACZ,SACE,kBAAC,GAAD;IAAQ,SAAQ;IAAU,eA/GN;AAE1B,KADA,EAAc,GAAK,EACnB,iBAAiB,EAAc,GAAM,EAAE,IAAK;;IA6GY,UAAU;cAA5D,CACE,kBAAC,GAAD,EAAW,WAAW,gBAAgB,IAAa,iBAAiB,MAAQ,CAAA,EAAA,UAErE;;aAGX,kBAAC,OAAD;IAAK,WAAU;cAAf;KAEE,kBAAC,GAAD,EAAA,UAAA;MACE,kBAAC,GAAD;OACE,OAAM;OACN,OAAO;OACP,MAAM,kBAAC,GAAD,EAAc,WAAU,wBAAyB,CAAA;OACvD,UAAU,MAAM,EAAa,OAAO;OACpC,CAAA;MACF,kBAAC,GAAD;OACE,OAAM;OACN,OAAO;OACP,MAAM,kBAAC,GAAD,EAAe,WAAU,wBAAyB,CAAA;OACxD,UAAU,IAAmB,IAAI,SAAS;OAC1C,CAAA;MACF,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,GAAG,EAAU,QAAQ,EAAE,CAAC;OAC/B,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;OACxC,CAAA;MACF,kBAAC,GAAD;OACE,OAAM;OACN,OAAO,GAAG,EAAW,QAAQ,EAAE,CAAC;OAChC,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;OACtC,CAAA;MACU,EAAA,CAAA;KAGb,IAAkB,KACjB,kBAAC,GAAD;MAAM,WAAU;gBAAhB,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;OAAW,WAAU;iBAArB;QACE,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;;QAClB;QAAgB;QACzB;UACD,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;OAAK,WAAU;iBACZ,EACE,QAAQ,MAAM,EAAE,WAAW,WAAW,CACtC,KAAK,MACJ,kBAAC,OAAD;QAEE,WAAU;kBAFZ,CAIE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,GAAD;YAAO,WAAW,EAAe,EAAS;sBACvC,EAAS,SAAS,aAAa;YAC1B,CAAA,EACR,kBAAC,QAAD;YAAM,WAAU;sBAAe,EAAS;YAAa,CAAA,CACjD;;UACN,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,QAAD,EAAA,UAAA,CAAM,YAAS,EAAmB,EAAS,UAAU,CAAQ,EAAA,CAAA;YAC7D,kBAAC,QAAD,EAAA,UAAM,KAAQ,CAAA;YACd,kBAAC,QAAD,EAAA,UAAA,CAAM,YAAS,EAAS,OAAc,EAAA,CAAA;YAClC;;UACN,kBAAC,OAAD;WAAK,WAAU;qBACZ,EAAS,iBAAiB,KAAK,MAC9B,kBAAC,GAAD;YAAiB,SAAQ;YAAU,WAAU;sBAC1C;YACK,EAFI,EAEJ,CACR;WACE,CAAA;UACF;YACN,kBAAC,GAAD;SAAQ,MAAK;SAAK,SAAQ;mBAAU;SAE3B,CAAA,CACL;UA1BC,EAAS,GA0BV,CACN;OACA,CAAA,EACM,CAAA,CACT;;KAIT,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAW,WAAU;gBAArB,CACE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAAA,iBAEpB;SACD,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MACE,SArMS;OACrB;QACE,KAAK;QACL,QAAQ;QACR,SAAS,MACP,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACG,EAAU,EAAQ,OACnB,kBAAC,QAAD;UAAM,WAAU;oBAAe,EAAQ;UAAY,CAAA,CAC/C;;QAET;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MAA2B,kBAAC,GAAD,EAAmB,QAAQ,EAAQ,QAAU,CAAA;QAClF;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,OAAO;QACP,SAAS,MACP,kBAAC,QAAD;SAAM,WAAW,EAAQ,SAAS,KAAK,iBAAiB;mBAAxD,CACG,EAAQ,OAAO,QAAQ,EAAE,EAAC,IACtB;;QAEV;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,OAAO;QACP,SAAS,MACP,kBAAC,QAAD;SAAM,WAAW,EAAQ,UAAU,MAAM,iBAAiB;mBAA1D,CAA+D,EAAQ,SAAQ,KAAS;;QAE3F;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,OAAO;QACP,SAAS,MACP,kBAAC,QAAD;SAAM,WAAW,EAAQ,YAAY,IAAI,2BAA2B;mBAApE,CACG,EAAQ,UAAU,QAAQ,EAAE,EAAC,IACzB;;QAEV;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MACP,kBAAC,QAAD;SAAM,WAAU;mBAAuB,EAAQ;SAAc,CAAA;QAEhE;OACF;MA8Ia,MAAM;MACN,eAAe,MAAM,EAAE;MACvB,YACE,kBAAC,GAAD;OAAY,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;OAAE,OAAM;OAAsB,CAAA;MAEhF,CAAA,EACU,CAAA,CACT,EAAA,CAAA;KAGP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAW,WAAU;gBAArB,CACE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAAA,qBAEvB;SACD,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MACE,SA/JY;OACxB;QACE,KAAK;QACL,QAAQ;QACR,SAAS,MAAkB,kBAAC,QAAD;SAAM,WAAU;mBAAe,EAAE;SAAe,CAAA;QAC5E;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MACP,kBAAC,QAAD;SAAM,WAAU;mBAA8C,EAAE;SAAe,CAAA;QAElF;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MAAkB,kBAAC,GAAD,EAAuB,QAAQ,EAAE,QAAU,CAAA;QACvE;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,OAAO;QACP,SAAS,MAAkB,GAAG,EAAE,SAAS;QAC1C;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MACP,kBAAC,QAAD;SAAM,WAAU;mBAAuB,EAAmB,EAAE,WAAW;SAAQ,CAAA;QAElF;OACF;MA6Ha,MAAM;MACN,eAAe,MAAM,EAAE;MACvB,YACE,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;OACvC,OAAM;OACN,CAAA;MAEJ,CAAA,EACU,CAAA,CACT,EAAA,CAAA;KACH;;GACK,CAAA;EACG,CAAA"}
|
|
@@ -161,7 +161,7 @@ function j() {
|
|
|
161
161
|
L,
|
|
162
162
|
R,
|
|
163
163
|
z
|
|
164
|
-
]), Y = "h-10 w-full rounded-md border border-
|
|
164
|
+
]), Y = "h-10 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm";
|
|
165
165
|
return /* @__PURE__ */ T(m, {
|
|
166
166
|
allowed: j,
|
|
167
167
|
isLoading: P,
|
|
@@ -174,7 +174,7 @@ function j() {
|
|
|
174
174
|
className: "mb-6",
|
|
175
175
|
children: [/* @__PURE__ */ E(s, { children: [/* @__PURE__ */ E("div", {
|
|
176
176
|
className: "flex items-center gap-2",
|
|
177
|
-
children: [/* @__PURE__ */ T(v, { className: "h-5 w-5 text-
|
|
177
|
+
children: [/* @__PURE__ */ T(v, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ T(c, {
|
|
178
178
|
className: "text-lg",
|
|
179
179
|
children: "Sandbox Stats"
|
|
180
180
|
})]
|
|
@@ -205,7 +205,7 @@ function j() {
|
|
|
205
205
|
children: [
|
|
206
206
|
/* @__PURE__ */ E(i, { children: [/* @__PURE__ */ E(s, { children: [/* @__PURE__ */ E("div", {
|
|
207
207
|
className: "flex items-center gap-2",
|
|
208
|
-
children: [/* @__PURE__ */ T(C, { className: "h-5 w-5 text-
|
|
208
|
+
children: [/* @__PURE__ */ T(C, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ T(c, {
|
|
209
209
|
className: "text-lg",
|
|
210
210
|
children: "Time-to-Live (TTL)"
|
|
211
211
|
})]
|
|
@@ -230,7 +230,7 @@ function j() {
|
|
|
230
230
|
}, e.value))
|
|
231
231
|
}),
|
|
232
232
|
/* @__PURE__ */ T("p", {
|
|
233
|
-
className: "text-xs text-
|
|
233
|
+
className: "text-xs text-text-secondary",
|
|
234
234
|
children: "Default lifetime for new sandboxes"
|
|
235
235
|
})
|
|
236
236
|
]
|
|
@@ -251,7 +251,7 @@ function j() {
|
|
|
251
251
|
}, e.value))
|
|
252
252
|
}),
|
|
253
253
|
/* @__PURE__ */ T("p", {
|
|
254
|
-
className: "text-xs text-
|
|
254
|
+
className: "text-xs text-text-secondary",
|
|
255
255
|
children: "Maximum allowed TTL (must be >= default)"
|
|
256
256
|
})
|
|
257
257
|
]
|
|
@@ -260,31 +260,31 @@ function j() {
|
|
|
260
260
|
})] }),
|
|
261
261
|
/* @__PURE__ */ E(i, { children: [/* @__PURE__ */ E(s, { children: [/* @__PURE__ */ E("div", {
|
|
262
262
|
className: "flex items-center gap-2",
|
|
263
|
-
children: [/* @__PURE__ */ T(y, { className: "h-5 w-5 text-
|
|
263
|
+
children: [/* @__PURE__ */ T(y, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ T(c, {
|
|
264
264
|
className: "text-lg",
|
|
265
265
|
children: "Allowed Sandbox Types"
|
|
266
266
|
})]
|
|
267
267
|
}), /* @__PURE__ */ T(o, { children: "Select which sandbox types users are permitted to create" })] }), /* @__PURE__ */ T(a, { children: /* @__PURE__ */ T("div", {
|
|
268
268
|
className: "space-y-3",
|
|
269
269
|
children: D.map((e) => /* @__PURE__ */ E("label", {
|
|
270
|
-
className: "flex items-center gap-3 rounded-lg border border-border p-4 cursor-pointer hover:bg-
|
|
270
|
+
className: "flex items-center gap-3 rounded-lg border border-border-subtle p-4 cursor-pointer hover:bg-bg-sunken/50 transition-colors",
|
|
271
271
|
children: [/* @__PURE__ */ T("input", {
|
|
272
272
|
type: "checkbox",
|
|
273
273
|
checked: B.allowedSandboxTypes.includes(e),
|
|
274
274
|
onChange: () => q(e),
|
|
275
|
-
className: "h-4 w-4 rounded border-
|
|
275
|
+
className: "h-4 w-4 rounded border-border-default text-primary focus:ring-primary"
|
|
276
276
|
}), /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("div", {
|
|
277
277
|
className: "font-medium text-sm",
|
|
278
278
|
children: M(e)
|
|
279
279
|
}), /* @__PURE__ */ T("div", {
|
|
280
|
-
className: "text-xs text-
|
|
280
|
+
className: "text-xs text-text-secondary",
|
|
281
281
|
children: N(e)
|
|
282
282
|
})] })]
|
|
283
283
|
}, e))
|
|
284
284
|
}) })] }),
|
|
285
285
|
/* @__PURE__ */ E(i, { children: [/* @__PURE__ */ E(s, { children: [/* @__PURE__ */ E("div", {
|
|
286
286
|
className: "flex items-center gap-2",
|
|
287
|
-
children: [/* @__PURE__ */ T(b, { className: "h-5 w-5 text-
|
|
287
|
+
children: [/* @__PURE__ */ T(b, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ T(c, {
|
|
288
288
|
className: "text-lg",
|
|
289
289
|
children: "Default Resource Limits"
|
|
290
290
|
})]
|
|
@@ -306,7 +306,7 @@ function j() {
|
|
|
306
306
|
placeholder: "500m"
|
|
307
307
|
}),
|
|
308
308
|
/* @__PURE__ */ T("p", {
|
|
309
|
-
className: "text-xs text-
|
|
309
|
+
className: "text-xs text-text-secondary",
|
|
310
310
|
children: "Kubernetes CPU request (e.g. 500m, 1)"
|
|
311
311
|
})
|
|
312
312
|
]
|
|
@@ -323,7 +323,7 @@ function j() {
|
|
|
323
323
|
placeholder: "2"
|
|
324
324
|
}),
|
|
325
325
|
/* @__PURE__ */ T("p", {
|
|
326
|
-
className: "text-xs text-
|
|
326
|
+
className: "text-xs text-text-secondary",
|
|
327
327
|
children: "Kubernetes CPU limit (e.g. 1, 2, 4)"
|
|
328
328
|
})
|
|
329
329
|
]
|
|
@@ -345,7 +345,7 @@ function j() {
|
|
|
345
345
|
placeholder: "512Mi"
|
|
346
346
|
}),
|
|
347
347
|
/* @__PURE__ */ T("p", {
|
|
348
|
-
className: "text-xs text-
|
|
348
|
+
className: "text-xs text-text-secondary",
|
|
349
349
|
children: "Kubernetes memory request (e.g. 512Mi, 1Gi)"
|
|
350
350
|
})
|
|
351
351
|
]
|
|
@@ -362,7 +362,7 @@ function j() {
|
|
|
362
362
|
placeholder: "4Gi"
|
|
363
363
|
}),
|
|
364
364
|
/* @__PURE__ */ T("p", {
|
|
365
|
-
className: "text-xs text-
|
|
365
|
+
className: "text-xs text-text-secondary",
|
|
366
366
|
children: "Kubernetes memory limit (e.g. 2Gi, 4Gi, 8Gi)"
|
|
367
367
|
})
|
|
368
368
|
]
|
|
@@ -376,7 +376,7 @@ function j() {
|
|
|
376
376
|
children: [
|
|
377
377
|
/* @__PURE__ */ E(i, { children: [/* @__PURE__ */ T(s, { children: /* @__PURE__ */ E("div", {
|
|
378
378
|
className: "flex items-center gap-2",
|
|
379
|
-
children: [/* @__PURE__ */ T(w, { className: "h-5 w-5 text-
|
|
379
|
+
children: [/* @__PURE__ */ T(w, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ T(c, {
|
|
380
380
|
className: "text-lg",
|
|
381
381
|
children: "Concurrency"
|
|
382
382
|
})]
|
|
@@ -398,7 +398,7 @@ function j() {
|
|
|
398
398
|
onChange: (e) => K("maxConcurrentPerUser", Number(e.target.value))
|
|
399
399
|
}),
|
|
400
400
|
/* @__PURE__ */ T("p", {
|
|
401
|
-
className: "text-xs text-
|
|
401
|
+
className: "text-xs text-text-secondary",
|
|
402
402
|
children: "Maximum concurrent sandboxes per user"
|
|
403
403
|
})
|
|
404
404
|
]
|
|
@@ -419,7 +419,7 @@ function j() {
|
|
|
419
419
|
onChange: (e) => K("maxConcurrentPerWorkspace", Number(e.target.value))
|
|
420
420
|
}),
|
|
421
421
|
/* @__PURE__ */ T("p", {
|
|
422
|
-
className: "text-xs text-
|
|
422
|
+
className: "text-xs text-text-secondary",
|
|
423
423
|
children: "Maximum concurrent sandboxes per workspace"
|
|
424
424
|
})
|
|
425
425
|
]
|
|
@@ -428,7 +428,7 @@ function j() {
|
|
|
428
428
|
})] }),
|
|
429
429
|
/* @__PURE__ */ E(i, { children: [/* @__PURE__ */ T(s, { children: /* @__PURE__ */ E("div", {
|
|
430
430
|
className: "flex items-center gap-2",
|
|
431
|
-
children: [/* @__PURE__ */ T(x, { className: "h-5 w-5 text-
|
|
431
|
+
children: [/* @__PURE__ */ T(x, { className: "h-5 w-5 text-text-secondary" }), /* @__PURE__ */ T(c, {
|
|
432
432
|
className: "text-lg",
|
|
433
433
|
children: "Resource Info"
|
|
434
434
|
})]
|
|
@@ -436,12 +436,12 @@ function j() {
|
|
|
436
436
|
className: "space-y-3",
|
|
437
437
|
children: [
|
|
438
438
|
/* @__PURE__ */ T("p", {
|
|
439
|
-
className: "text-sm text-
|
|
439
|
+
className: "text-sm text-text-secondary",
|
|
440
440
|
children: "Resource defaults are applied to all new sandboxes. Individual sandboxes can override these within the max limits defined by the sandbox type."
|
|
441
441
|
}),
|
|
442
442
|
/* @__PURE__ */ T(u, {}),
|
|
443
443
|
/* @__PURE__ */ E("div", {
|
|
444
|
-
className: "space-y-2 text-xs text-
|
|
444
|
+
className: "space-y-2 text-xs text-text-secondary",
|
|
445
445
|
children: [/* @__PURE__ */ E("div", {
|
|
446
446
|
className: "flex justify-between",
|
|
447
447
|
children: [/* @__PURE__ */ T("span", { children: "CPU units" }), /* @__PURE__ */ T("span", {
|