mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 20:53:06 +00:00
re-org tenant settings
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "./TenantConfigContext";
|
||||
|
||||
export function TenantNotifications() {
|
||||
const { error, successMessage } = useTenantConfig();
|
||||
|
||||
if (!error && !successMessage) return null;
|
||||
|
||||
return (
|
||||
<div className="space-y-2 mb-6">
|
||||
{successMessage && (
|
||||
<div className="bg-green-50 border border-green-200 rounded-lg p-4 flex items-center space-x-3">
|
||||
<svg className="w-5 h-5 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span className="text-green-800 font-medium">{successMessage}</span>
|
||||
</div>
|
||||
)}
|
||||
{error && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 flex items-center space-x-3">
|
||||
<svg className="w-5 h-5 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span className="text-red-800">{error}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import { usePageSections } from "../../../hooks/usePageSections";
|
||||
import { PageSectionItem } from "../../../contexts/SidebarContext";
|
||||
import { ACCESS_NAV_SECTIONS } from "./accessNavSections";
|
||||
import { UsersIcon, KeyIcon } from "../../../lib/sidebarIcons";
|
||||
import { useTenantConfig } from "../TenantConfigContext";
|
||||
|
||||
const SECTION_ICONS: Record<string, React.ReactNode> = {
|
||||
"admin-management": <UsersIcon />,
|
||||
"bootstrap-sessions": <KeyIcon />,
|
||||
};
|
||||
|
||||
export function AccessSidebar({ children }: { children: React.ReactNode }) {
|
||||
const { config, user } = useTenantConfig();
|
||||
|
||||
// Filter: hide bootstrap-sessions if not enabled; show admin-management only for admins
|
||||
const visibleSections = ACCESS_NAV_SECTIONS.filter((s) => {
|
||||
if (s.id === "bootstrap-sessions" && !config?.bootstrapTokenEnabled) return false;
|
||||
if (s.id === "admin-management" && !user?.isTenantAdmin && !user?.isGalacticAdmin) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
const accessItems: PageSectionItem[] = visibleSections.map((s) => ({
|
||||
id: s.id,
|
||||
label: s.label,
|
||||
icon: SECTION_ICONS[s.id],
|
||||
href: `/settings/access/${s.id}`,
|
||||
}));
|
||||
|
||||
usePageSections(accessItems, "Access", "route");
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="bg-white shadow">
|
||||
<div className="py-6 px-4 sm:px-6 lg:px-8">
|
||||
<h1 className="text-2xl font-normal text-gray-900">Tenant Configuration</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">Admin management and bootstrap sessions</p>
|
||||
</div>
|
||||
</header>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useParams } from "next/navigation";
|
||||
import { notFound } from "next/navigation";
|
||||
import { type AccessSectionId } from "../accessNavSections";
|
||||
import { SectionAdminManagement } from "../sections/SectionAdminManagement";
|
||||
import { SectionBootstrapSessions } from "../sections/SectionBootstrapSessions";
|
||||
|
||||
const SECTION_COMPONENTS: Record<AccessSectionId, React.ComponentType> = {
|
||||
"admin-management": SectionAdminManagement,
|
||||
"bootstrap-sessions": SectionBootstrapSessions,
|
||||
};
|
||||
|
||||
export default function AccessSectionPage() {
|
||||
const params = useParams();
|
||||
const section = params.section as string;
|
||||
const SectionContent = SECTION_COMPONENTS[section as AccessSectionId];
|
||||
if (!SectionContent) notFound();
|
||||
return <SectionContent />;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export const ACCESS_NAV_SECTIONS = [
|
||||
{ id: "admin-management", label: "Admin Management", description: "Manage tenant admins and operators" },
|
||||
{ id: "bootstrap-sessions", label: "Bootstrap Sessions", description: "Create and manage bootstrap tokens" },
|
||||
] as const;
|
||||
|
||||
export type AccessSectionId = (typeof ACCESS_NAV_SECTIONS)[number]["id"];
|
||||
@@ -0,0 +1,5 @@
|
||||
import { AccessSidebar } from "./AccessSidebar";
|
||||
|
||||
export default function AccessLayout({ children }: { children: React.ReactNode }) {
|
||||
return <AccessSidebar>{children}</AccessSidebar>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function AccessPage() {
|
||||
redirect("/settings/access/admin-management");
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "../../TenantConfigContext";
|
||||
import { TenantNotifications } from "../../TenantNotifications";
|
||||
import AdminManagementSection from "../../components/AdminManagementSection";
|
||||
|
||||
export function SectionAdminManagement() {
|
||||
const {
|
||||
admins, loadingAdmins,
|
||||
newAdminEmail, setNewAdminEmail,
|
||||
newMemberRole, setNewMemberRole,
|
||||
addingAdmin, removingAdmin, togglingAdmin,
|
||||
adminSearchQuery, setAdminSearchQuery,
|
||||
currentAdminPage, setCurrentAdminPage,
|
||||
user,
|
||||
handleAddAdmin, handleRemoveAdmin,
|
||||
handleToggleTenantAdmin, handleUpdatePermissions,
|
||||
} = useTenantConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TenantNotifications />
|
||||
<AdminManagementSection
|
||||
admins={admins}
|
||||
loadingAdmins={loadingAdmins}
|
||||
newAdminEmail={newAdminEmail}
|
||||
setNewAdminEmail={setNewAdminEmail}
|
||||
newMemberRole={newMemberRole}
|
||||
setNewMemberRole={setNewMemberRole}
|
||||
addingAdmin={addingAdmin}
|
||||
removingAdmin={removingAdmin}
|
||||
togglingAdmin={togglingAdmin}
|
||||
adminSearchQuery={adminSearchQuery}
|
||||
setAdminSearchQuery={setAdminSearchQuery}
|
||||
currentAdminPage={currentAdminPage}
|
||||
setCurrentAdminPage={setCurrentAdminPage}
|
||||
user={user}
|
||||
onAddAdmin={handleAddAdmin}
|
||||
onRemoveAdmin={handleRemoveAdmin}
|
||||
onToggleAdmin={handleToggleTenantAdmin}
|
||||
onUpdatePermissions={handleUpdatePermissions}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "../../TenantConfigContext";
|
||||
import { TenantNotifications } from "../../TenantNotifications";
|
||||
import BootstrapSessionsSection from "../../components/BootstrapSessionsSection";
|
||||
|
||||
export function SectionBootstrapSessions() {
|
||||
const {
|
||||
config,
|
||||
bootstrapSessions, bootstrapLoading,
|
||||
fetchBootstrapSessions, revokeBootstrapSession, createBootstrapSession,
|
||||
} = useTenantConfig();
|
||||
|
||||
if (!config?.bootstrapTokenEnabled) {
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-8 text-center">
|
||||
<p className="text-gray-500">Bootstrap Sessions are not available for this tenant.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TenantNotifications />
|
||||
<BootstrapSessionsSection
|
||||
sessions={bootstrapSessions}
|
||||
loading={bootstrapLoading}
|
||||
onRefresh={fetchBootstrapSessions}
|
||||
onRevoke={revokeBootstrapSession}
|
||||
onCreate={createBootstrapSession}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { usePageSections } from "../../../hooks/usePageSections";
|
||||
import { PageSectionItem } from "../../../contexts/SidebarContext";
|
||||
import { AGENT_NAV_SECTIONS } from "./agentNavSections";
|
||||
import { GearIcon, MagnifyingGlassIcon, LockClosedIcon } from "../../../lib/sidebarIcons";
|
||||
import { useTenantConfig } from "../TenantConfigContext";
|
||||
|
||||
const SECTION_ICONS: Record<string, React.ReactNode> = {
|
||||
"settings": <GearIcon />,
|
||||
"analyzers": <MagnifyingGlassIcon />,
|
||||
"unrestricted-mode": <LockClosedIcon />,
|
||||
};
|
||||
|
||||
export function AgentSidebar({ children }: { children: React.ReactNode }) {
|
||||
const { config } = useTenantConfig();
|
||||
|
||||
// Filter out unrestricted-mode if not enabled
|
||||
const visibleSections = AGENT_NAV_SECTIONS.filter(
|
||||
(s) => s.id !== "unrestricted-mode" || config?.unrestrictedModeEnabled
|
||||
);
|
||||
|
||||
const agentItems: PageSectionItem[] = visibleSections.map((s) => ({
|
||||
id: s.id,
|
||||
label: s.label,
|
||||
icon: SECTION_ICONS[s.id],
|
||||
href: `/settings/agent/${s.id}`,
|
||||
}));
|
||||
|
||||
usePageSections(agentItems, "Agent", "route");
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="bg-white shadow">
|
||||
<div className="py-6 px-4 sm:px-6 lg:px-8">
|
||||
<h1 className="text-2xl font-normal text-gray-900">Tenant Configuration</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">Agent collector and behavior configuration</p>
|
||||
</div>
|
||||
</header>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import { useParams } from "next/navigation";
|
||||
import { notFound } from "next/navigation";
|
||||
import { type AgentSectionId } from "../agentNavSections";
|
||||
import { SectionAgentSettings } from "../sections/SectionAgentSettings";
|
||||
import { SectionAgentAnalyzers } from "../sections/SectionAgentAnalyzers";
|
||||
import { SectionUnrestrictedMode } from "../sections/SectionUnrestrictedMode";
|
||||
|
||||
const SECTION_COMPONENTS: Record<AgentSectionId, React.ComponentType> = {
|
||||
"settings": SectionAgentSettings,
|
||||
"analyzers": SectionAgentAnalyzers,
|
||||
"unrestricted-mode": SectionUnrestrictedMode,
|
||||
};
|
||||
|
||||
export default function AgentSectionPage() {
|
||||
const params = useParams();
|
||||
const section = params.section as string;
|
||||
const SectionContent = SECTION_COMPONENTS[section as AgentSectionId];
|
||||
if (!SectionContent) notFound();
|
||||
return <SectionContent />;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
export const AGENT_NAV_SECTIONS = [
|
||||
{ id: "settings", label: "Agent Settings", description: "Agent collector and behavior configuration" },
|
||||
{ id: "analyzers", label: "Agent Analyzers", description: "Local admin and software inventory analyzers" },
|
||||
{ id: "unrestricted-mode", label: "Unrestricted Mode", description: "Unrestricted mode configuration" },
|
||||
] as const;
|
||||
|
||||
export type AgentSectionId = (typeof AGENT_NAV_SECTIONS)[number]["id"];
|
||||
@@ -0,0 +1,5 @@
|
||||
import { AgentSidebar } from "./AgentSidebar";
|
||||
|
||||
export default function AgentLayout({ children }: { children: React.ReactNode }) {
|
||||
return <AgentSidebar>{children}</AgentSidebar>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function AgentPage() {
|
||||
redirect("/settings/agent/settings");
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "../../TenantConfigContext";
|
||||
import { TenantNotifications } from "../../TenantNotifications";
|
||||
import AgentAnalyzersSection from "../../components/AgentAnalyzersSection";
|
||||
|
||||
export function SectionAgentAnalyzers() {
|
||||
const {
|
||||
enableLocalAdminAnalyzer, setEnableLocalAdminAnalyzer,
|
||||
localAdminAllowedAccounts, setLocalAdminAllowedAccounts,
|
||||
newAllowedAccount, setNewAllowedAccount,
|
||||
enableSoftwareInventoryAnalyzer, setEnableSoftwareInventoryAnalyzer,
|
||||
handleSaveAgentAnalyzers, handleResetAgentAnalyzers,
|
||||
savingSection,
|
||||
} = useTenantConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TenantNotifications />
|
||||
<AgentAnalyzersSection
|
||||
enableLocalAdminAnalyzer={enableLocalAdminAnalyzer}
|
||||
setEnableLocalAdminAnalyzer={setEnableLocalAdminAnalyzer}
|
||||
localAdminAllowedAccounts={localAdminAllowedAccounts}
|
||||
setLocalAdminAllowedAccounts={setLocalAdminAllowedAccounts}
|
||||
newAllowedAccount={newAllowedAccount}
|
||||
setNewAllowedAccount={setNewAllowedAccount}
|
||||
enableSoftwareInventoryAnalyzer={enableSoftwareInventoryAnalyzer}
|
||||
setEnableSoftwareInventoryAnalyzer={setEnableSoftwareInventoryAnalyzer}
|
||||
onSave={handleSaveAgentAnalyzers}
|
||||
onReset={handleResetAgentAnalyzers}
|
||||
saving={savingSection === "agentAnalyzers"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "../../TenantConfigContext";
|
||||
import { TenantNotifications } from "../../TenantNotifications";
|
||||
import AgentSettingsSection from "../../components/AgentSettingsSection";
|
||||
|
||||
export function SectionAgentSettings() {
|
||||
const {
|
||||
enablePerformanceCollector, setEnablePerformanceCollector,
|
||||
performanceCollectorInterval, setPerformanceCollectorInterval,
|
||||
helloWaitTimeoutSeconds, setHelloWaitTimeoutSeconds,
|
||||
selfDestructOnComplete, setSelfDestructOnComplete,
|
||||
keepLogFile, setKeepLogFile,
|
||||
rebootOnComplete, setRebootOnComplete,
|
||||
rebootDelaySeconds, setRebootDelaySeconds,
|
||||
enableGeoLocation, setEnableGeoLocation,
|
||||
enableImeMatchLog, setEnableImeMatchLog,
|
||||
logLevel, setLogLevel,
|
||||
showScriptOutput, setShowScriptOutput,
|
||||
showEnrollmentSummary, setShowEnrollmentSummary,
|
||||
enrollmentSummaryTimeoutSeconds, setEnrollmentSummaryTimeoutSeconds,
|
||||
enrollmentSummaryBrandingImageUrl, setEnrollmentSummaryBrandingImageUrl,
|
||||
enrollmentSummaryLaunchRetrySeconds, setEnrollmentSummaryLaunchRetrySeconds,
|
||||
handleSaveAgentSettings, handleResetAgentSettings,
|
||||
savingSection,
|
||||
} = useTenantConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TenantNotifications />
|
||||
<AgentSettingsSection
|
||||
enablePerformanceCollector={enablePerformanceCollector}
|
||||
setEnablePerformanceCollector={setEnablePerformanceCollector}
|
||||
performanceCollectorInterval={performanceCollectorInterval}
|
||||
setPerformanceCollectorInterval={setPerformanceCollectorInterval}
|
||||
helloWaitTimeoutSeconds={helloWaitTimeoutSeconds}
|
||||
setHelloWaitTimeoutSeconds={setHelloWaitTimeoutSeconds}
|
||||
selfDestructOnComplete={selfDestructOnComplete}
|
||||
setSelfDestructOnComplete={setSelfDestructOnComplete}
|
||||
keepLogFile={keepLogFile}
|
||||
setKeepLogFile={setKeepLogFile}
|
||||
rebootOnComplete={rebootOnComplete}
|
||||
setRebootOnComplete={setRebootOnComplete}
|
||||
rebootDelaySeconds={rebootDelaySeconds}
|
||||
setRebootDelaySeconds={setRebootDelaySeconds}
|
||||
enableGeoLocation={enableGeoLocation}
|
||||
setEnableGeoLocation={setEnableGeoLocation}
|
||||
enableImeMatchLog={enableImeMatchLog}
|
||||
setEnableImeMatchLog={setEnableImeMatchLog}
|
||||
logLevel={logLevel}
|
||||
setLogLevel={setLogLevel}
|
||||
showScriptOutput={showScriptOutput}
|
||||
setShowScriptOutput={setShowScriptOutput}
|
||||
showEnrollmentSummary={showEnrollmentSummary}
|
||||
setShowEnrollmentSummary={setShowEnrollmentSummary}
|
||||
enrollmentSummaryTimeoutSeconds={enrollmentSummaryTimeoutSeconds}
|
||||
setEnrollmentSummaryTimeoutSeconds={setEnrollmentSummaryTimeoutSeconds}
|
||||
enrollmentSummaryBrandingImageUrl={enrollmentSummaryBrandingImageUrl}
|
||||
setEnrollmentSummaryBrandingImageUrl={setEnrollmentSummaryBrandingImageUrl}
|
||||
enrollmentSummaryLaunchRetrySeconds={enrollmentSummaryLaunchRetrySeconds}
|
||||
setEnrollmentSummaryLaunchRetrySeconds={setEnrollmentSummaryLaunchRetrySeconds}
|
||||
onSave={handleSaveAgentSettings}
|
||||
onReset={handleResetAgentSettings}
|
||||
saving={savingSection === "agentSettings"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "../../TenantConfigContext";
|
||||
import { TenantNotifications } from "../../TenantNotifications";
|
||||
import UnrestrictedModeSection from "../../components/UnrestrictedModeSection";
|
||||
|
||||
export function SectionUnrestrictedMode() {
|
||||
const {
|
||||
config,
|
||||
unrestrictedMode, setUnrestrictedMode,
|
||||
handleSaveUnrestrictedMode,
|
||||
savingSection,
|
||||
} = useTenantConfig();
|
||||
|
||||
if (!config?.unrestrictedModeEnabled) {
|
||||
return (
|
||||
<div className="bg-white rounded-lg shadow p-8 text-center">
|
||||
<p className="text-gray-500">Unrestricted Mode is not available for this tenant.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TenantNotifications />
|
||||
<UnrestrictedModeSection
|
||||
unrestrictedMode={unrestrictedMode}
|
||||
setUnrestrictedMode={setUnrestrictedMode}
|
||||
onSave={handleSaveUnrestrictedMode}
|
||||
saving={savingSection === "unrestrictedMode"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { usePageSections } from "../../../hooks/usePageSections";
|
||||
import { PageSectionItem } from "../../../contexts/SidebarContext";
|
||||
import { INTEGRATIONS_NAV_SECTIONS } from "./integrationsNavSections";
|
||||
import { BellIcon, CloudArrowUpIcon } from "../../../lib/sidebarIcons";
|
||||
|
||||
const SECTION_ICONS: Record<string, React.ReactNode> = {
|
||||
"notifications": <BellIcon />,
|
||||
"diagnostics": <CloudArrowUpIcon />,
|
||||
};
|
||||
|
||||
const integrationsItems: PageSectionItem[] = INTEGRATIONS_NAV_SECTIONS.map((s) => ({
|
||||
id: s.id,
|
||||
label: s.label,
|
||||
icon: SECTION_ICONS[s.id],
|
||||
href: `/settings/integrations/${s.id}`,
|
||||
}));
|
||||
|
||||
export function IntegrationsSidebar({ children }: { children: React.ReactNode }) {
|
||||
usePageSections(integrationsItems, "Integrations", "route");
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="bg-white shadow">
|
||||
<div className="py-6 px-4 sm:px-6 lg:px-8">
|
||||
<h1 className="text-2xl font-normal text-gray-900">Tenant Configuration</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">Webhook notifications and diagnostics upload</p>
|
||||
</div>
|
||||
</header>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useParams } from "next/navigation";
|
||||
import { notFound } from "next/navigation";
|
||||
import { type IntegrationsSectionId } from "../integrationsNavSections";
|
||||
import { SectionNotifications } from "../sections/SectionNotifications";
|
||||
import { SectionDiagnostics } from "../sections/SectionDiagnostics";
|
||||
|
||||
const SECTION_COMPONENTS: Record<IntegrationsSectionId, React.ComponentType> = {
|
||||
"notifications": SectionNotifications,
|
||||
"diagnostics": SectionDiagnostics,
|
||||
};
|
||||
|
||||
export default function IntegrationsSectionPage() {
|
||||
const params = useParams();
|
||||
const section = params.section as string;
|
||||
const SectionContent = SECTION_COMPONENTS[section as IntegrationsSectionId];
|
||||
if (!SectionContent) notFound();
|
||||
return <SectionContent />;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export const INTEGRATIONS_NAV_SECTIONS = [
|
||||
{ id: "notifications", label: "Notifications", description: "Webhook notification configuration" },
|
||||
{ id: "diagnostics", label: "Diagnostics", description: "Diagnostics upload and log path configuration" },
|
||||
] as const;
|
||||
|
||||
export type IntegrationsSectionId = (typeof INTEGRATIONS_NAV_SECTIONS)[number]["id"];
|
||||
@@ -0,0 +1,5 @@
|
||||
import { IntegrationsSidebar } from "./IntegrationsSidebar";
|
||||
|
||||
export default function IntegrationsLayout({ children }: { children: React.ReactNode }) {
|
||||
return <IntegrationsSidebar>{children}</IntegrationsSidebar>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function IntegrationsPage() {
|
||||
redirect("/settings/integrations/notifications");
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "../../TenantConfigContext";
|
||||
import { TenantNotifications } from "../../TenantNotifications";
|
||||
import DiagnosticsSection from "../../components/DiagnosticsSection";
|
||||
|
||||
export function SectionDiagnostics() {
|
||||
const {
|
||||
diagnosticsBlobSasUrl, setDiagnosticsBlobSasUrl,
|
||||
diagnosticsUploadMode, setDiagnosticsUploadMode,
|
||||
tenantDiagPaths, setTenantDiagPaths,
|
||||
globalDiagPaths,
|
||||
newDiagPath, setNewDiagPath,
|
||||
newDiagDesc, setNewDiagDesc,
|
||||
unrestrictedMode,
|
||||
handleSaveDiagnostics, handleResetDiagnostics,
|
||||
savingSection,
|
||||
} = useTenantConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TenantNotifications />
|
||||
<DiagnosticsSection
|
||||
diagnosticsBlobSasUrl={diagnosticsBlobSasUrl}
|
||||
setDiagnosticsBlobSasUrl={setDiagnosticsBlobSasUrl}
|
||||
diagnosticsUploadMode={diagnosticsUploadMode}
|
||||
setDiagnosticsUploadMode={setDiagnosticsUploadMode}
|
||||
tenantDiagPaths={tenantDiagPaths}
|
||||
setTenantDiagPaths={setTenantDiagPaths}
|
||||
globalDiagPaths={globalDiagPaths}
|
||||
newDiagPath={newDiagPath}
|
||||
setNewDiagPath={setNewDiagPath}
|
||||
newDiagDesc={newDiagDesc}
|
||||
setNewDiagDesc={setNewDiagDesc}
|
||||
unrestrictedMode={unrestrictedMode}
|
||||
onSave={handleSaveDiagnostics}
|
||||
onReset={handleResetDiagnostics}
|
||||
saving={savingSection === "diagnostics"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "../../TenantConfigContext";
|
||||
import { TenantNotifications } from "../../TenantNotifications";
|
||||
import NotificationsSection from "../../components/NotificationsSection";
|
||||
|
||||
export function SectionNotifications() {
|
||||
const {
|
||||
webhookProviderType, setWebhookProviderType,
|
||||
webhookUrl, setWebhookUrl,
|
||||
webhookNotifyOnSuccess, setWebhookNotifyOnSuccess,
|
||||
webhookNotifyOnFailure, setWebhookNotifyOnFailure,
|
||||
handleTestWebhook, testingWebhook, testWebhookResult,
|
||||
handleSaveNotifications, handleResetNotifications,
|
||||
savingSection,
|
||||
} = useTenantConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TenantNotifications />
|
||||
<NotificationsSection
|
||||
webhookProviderType={webhookProviderType}
|
||||
setWebhookProviderType={setWebhookProviderType}
|
||||
webhookUrl={webhookUrl}
|
||||
setWebhookUrl={setWebhookUrl}
|
||||
webhookNotifyOnSuccess={webhookNotifyOnSuccess}
|
||||
setWebhookNotifyOnSuccess={setWebhookNotifyOnSuccess}
|
||||
webhookNotifyOnFailure={webhookNotifyOnFailure}
|
||||
setWebhookNotifyOnFailure={setWebhookNotifyOnFailure}
|
||||
onTestWebhook={handleTestWebhook}
|
||||
testingWebhook={testingWebhook}
|
||||
testWebhookResult={testWebhookResult}
|
||||
onSave={handleSaveNotifications}
|
||||
onReset={handleResetNotifications}
|
||||
saving={savingSection === "notifications"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { ProtectedRoute } from "../../components/ProtectedRoute";
|
||||
import { useAuth } from "../../contexts/AuthContext";
|
||||
import { TenantConfigProvider } from "./TenantConfigContext";
|
||||
|
||||
export default function SettingsLayout({ children }: { children: React.ReactNode }) {
|
||||
const { user } = useAuth();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
if (!user) return;
|
||||
|
||||
// Regular users (not admin, not operator) → redirect to progress portal
|
||||
if (!user.isTenantAdmin && !user.isGalacticAdmin && user.role !== "Operator") {
|
||||
router.replace("/progress");
|
||||
return;
|
||||
}
|
||||
|
||||
// Operator without bootstrap permission → no settings access
|
||||
if (user.role === "Operator" && !user.isTenantAdmin && !user.isGalacticAdmin && !user.canManageBootstrapTokens) {
|
||||
router.replace("/dashboard");
|
||||
}
|
||||
}, [user, router]);
|
||||
|
||||
// Don't render until we know user is allowed
|
||||
if (!user) return null;
|
||||
if (!user.isTenantAdmin && !user.isGalacticAdmin && user.role !== "Operator") return null;
|
||||
if (user.role === "Operator" && !user.isTenantAdmin && !user.isGalacticAdmin && !user.canManageBootstrapTokens) return null;
|
||||
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<TenantConfigProvider>
|
||||
<div className="min-h-screen bg-gray-50">
|
||||
{children}
|
||||
</div>
|
||||
</TenantConfigProvider>
|
||||
</ProtectedRoute>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { usePageSections } from "../../../hooks/usePageSections";
|
||||
import { PageSectionItem } from "../../../contexts/SidebarContext";
|
||||
import { MANAGEMENT_NAV_SECTIONS } from "./managementNavSections";
|
||||
import { CircleStackIcon, ArrowRightOnRectangleIcon } from "../../../lib/sidebarIcons";
|
||||
|
||||
const SECTION_ICONS: Record<string, React.ReactNode> = {
|
||||
"data": <CircleStackIcon />,
|
||||
"offboarding": <ArrowRightOnRectangleIcon />,
|
||||
};
|
||||
|
||||
const managementItems: PageSectionItem[] = MANAGEMENT_NAV_SECTIONS.map((s) => ({
|
||||
id: s.id,
|
||||
label: s.label,
|
||||
icon: SECTION_ICONS[s.id],
|
||||
href: `/settings/management/${s.id}`,
|
||||
}));
|
||||
|
||||
export function ManagementSidebar({ children }: { children: React.ReactNode }) {
|
||||
usePageSections(managementItems, "Management", "route");
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="bg-white shadow">
|
||||
<div className="py-6 px-4 sm:px-6 lg:px-8">
|
||||
<h1 className="text-2xl font-normal text-gray-900">Tenant Configuration</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">Data retention and tenant offboarding</p>
|
||||
</div>
|
||||
</header>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useParams } from "next/navigation";
|
||||
import { notFound } from "next/navigation";
|
||||
import { type ManagementSectionId } from "../managementNavSections";
|
||||
import { SectionDataManagement } from "../sections/SectionDataManagement";
|
||||
import { SectionOffboarding } from "../sections/SectionOffboarding";
|
||||
|
||||
const SECTION_COMPONENTS: Record<ManagementSectionId, React.ComponentType> = {
|
||||
"data": SectionDataManagement,
|
||||
"offboarding": SectionOffboarding,
|
||||
};
|
||||
|
||||
export default function ManagementSectionPage() {
|
||||
const params = useParams();
|
||||
const section = params.section as string;
|
||||
const SectionContent = SECTION_COMPONENTS[section as ManagementSectionId];
|
||||
if (!SectionContent) notFound();
|
||||
return <SectionContent />;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ManagementSidebar } from "./ManagementSidebar";
|
||||
|
||||
export default function ManagementLayout({ children }: { children: React.ReactNode }) {
|
||||
return <ManagementSidebar>{children}</ManagementSidebar>;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export const MANAGEMENT_NAV_SECTIONS = [
|
||||
{ id: "data", label: "Data Management", description: "Data retention and session timeout configuration" },
|
||||
{ id: "offboarding", label: "Offboarding", description: "Tenant offboarding and data removal" },
|
||||
] as const;
|
||||
|
||||
export type ManagementSectionId = (typeof MANAGEMENT_NAV_SECTIONS)[number]["id"];
|
||||
@@ -0,0 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function ManagementPage() {
|
||||
redirect("/settings/management/data");
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "../../TenantConfigContext";
|
||||
import { TenantNotifications } from "../../TenantNotifications";
|
||||
import DataManagementSection from "../../components/DataManagementSection";
|
||||
|
||||
export function SectionDataManagement() {
|
||||
const {
|
||||
dataRetentionDays, setDataRetentionDays,
|
||||
sessionTimeoutHours, setSessionTimeoutHours,
|
||||
user,
|
||||
handleSaveDataManagement, handleResetDataManagement,
|
||||
savingSection,
|
||||
} = useTenantConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TenantNotifications />
|
||||
<DataManagementSection
|
||||
dataRetentionDays={dataRetentionDays}
|
||||
setDataRetentionDays={setDataRetentionDays}
|
||||
sessionTimeoutHours={sessionTimeoutHours}
|
||||
setSessionTimeoutHours={setSessionTimeoutHours}
|
||||
isGalacticAdmin={user?.isGalacticAdmin}
|
||||
onSave={handleSaveDataManagement}
|
||||
onReset={handleResetDataManagement}
|
||||
saving={savingSection === "dataManagement"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "../../TenantConfigContext";
|
||||
import { TenantNotifications } from "../../TenantNotifications";
|
||||
import OffboardingSection from "../../components/OffboardingSection";
|
||||
|
||||
export function SectionOffboarding() {
|
||||
const {
|
||||
showOffboardDialog, setShowOffboardDialog,
|
||||
offboardConfirmText, setOffboardConfirmText,
|
||||
offboarding, offboardError, setOffboardError,
|
||||
handleOffboard,
|
||||
} = useTenantConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TenantNotifications />
|
||||
<OffboardingSection
|
||||
showOffboardDialog={showOffboardDialog}
|
||||
setShowOffboardDialog={setShowOffboardDialog}
|
||||
offboardConfirmText={offboardConfirmText}
|
||||
setOffboardConfirmText={setOffboardConfirmText}
|
||||
offboarding={offboarding}
|
||||
offboardError={offboardError}
|
||||
setOffboardError={setOffboardError}
|
||||
onOffboard={handleOffboard}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
"use client";
|
||||
|
||||
import { usePageSections } from "../../../hooks/usePageSections";
|
||||
import { PageSectionItem } from "../../../contexts/SidebarContext";
|
||||
import { VALIDATION_NAV_SECTIONS } from "./validationNavSections";
|
||||
import { ShieldCheckIcon, CpuChipIcon } from "../../../lib/sidebarIcons";
|
||||
|
||||
const SECTION_ICONS: Record<string, React.ReactNode> = {
|
||||
"autopilot": <ShieldCheckIcon />,
|
||||
"hardware-whitelist": <CpuChipIcon />,
|
||||
};
|
||||
|
||||
const validationItems: PageSectionItem[] = VALIDATION_NAV_SECTIONS.map((s) => ({
|
||||
id: s.id,
|
||||
label: s.label,
|
||||
icon: SECTION_ICONS[s.id],
|
||||
href: `/settings/validation/${s.id}`,
|
||||
}));
|
||||
|
||||
export function ValidationSidebar({ children }: { children: React.ReactNode }) {
|
||||
usePageSections(validationItems, "Validation", "route");
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="bg-white shadow">
|
||||
<div className="py-6 px-4 sm:px-6 lg:px-8">
|
||||
<h1 className="text-2xl font-normal text-gray-900">Tenant Configuration</h1>
|
||||
<p className="text-sm text-gray-500 mt-1">Autopilot validation and hardware whitelist</p>
|
||||
</div>
|
||||
</header>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
|
||||
{children}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useParams } from "next/navigation";
|
||||
import { notFound } from "next/navigation";
|
||||
import { VALIDATION_NAV_SECTIONS, type ValidationSectionId } from "../validationNavSections";
|
||||
import { SectionAutopilotValidation } from "../sections/SectionAutopilotValidation";
|
||||
import { SectionHardwareWhitelist } from "../sections/SectionHardwareWhitelist";
|
||||
|
||||
const SECTION_COMPONENTS: Record<ValidationSectionId, React.ComponentType> = {
|
||||
"autopilot": SectionAutopilotValidation,
|
||||
"hardware-whitelist": SectionHardwareWhitelist,
|
||||
};
|
||||
|
||||
export default function ValidationSectionPage() {
|
||||
const params = useParams();
|
||||
const section = params.section as string;
|
||||
const SectionContent = SECTION_COMPONENTS[section as ValidationSectionId];
|
||||
if (!SectionContent) notFound();
|
||||
return <SectionContent />;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ValidationSidebar } from "./ValidationSidebar";
|
||||
|
||||
export default function ValidationLayout({ children }: { children: React.ReactNode }) {
|
||||
return <ValidationSidebar>{children}</ValidationSidebar>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function ValidationPage() {
|
||||
redirect("/settings/validation/autopilot");
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "../../TenantConfigContext";
|
||||
import { TenantNotifications } from "../../TenantNotifications";
|
||||
import AutopilotValidationSection from "../../components/AutopilotValidationSection";
|
||||
|
||||
export function SectionAutopilotValidation() {
|
||||
const {
|
||||
validateAutopilotDevice, setValidateAutopilotDevice,
|
||||
validateCorporateIdentifier, setValidateCorporateIdentifier,
|
||||
autopilotConsentInProgress, savingSection,
|
||||
beginDeviceValidationConsentFlow,
|
||||
} = useTenantConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TenantNotifications />
|
||||
<AutopilotValidationSection
|
||||
validateAutopilotDevice={validateAutopilotDevice}
|
||||
setValidateAutopilotDevice={setValidateAutopilotDevice}
|
||||
validateCorporateIdentifier={validateCorporateIdentifier}
|
||||
setValidateCorporateIdentifier={setValidateCorporateIdentifier}
|
||||
autopilotConsentInProgress={autopilotConsentInProgress}
|
||||
saving={savingSection === "autopilotValidation"}
|
||||
onBeginConsent={beginDeviceValidationConsentFlow}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
"use client";
|
||||
|
||||
import { useTenantConfig } from "../../TenantConfigContext";
|
||||
import { TenantNotifications } from "../../TenantNotifications";
|
||||
import HardwareWhitelistSection from "../../components/HardwareWhitelistSection";
|
||||
|
||||
export function SectionHardwareWhitelist() {
|
||||
const {
|
||||
manufacturerWhitelist, setManufacturerWhitelist,
|
||||
modelWhitelist, setModelWhitelist,
|
||||
handleSaveHardwareWhitelist, handleResetHardwareWhitelist,
|
||||
savingSection,
|
||||
} = useTenantConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<TenantNotifications />
|
||||
<HardwareWhitelistSection
|
||||
manufacturerWhitelist={manufacturerWhitelist}
|
||||
setManufacturerWhitelist={setManufacturerWhitelist}
|
||||
modelWhitelist={modelWhitelist}
|
||||
setModelWhitelist={setModelWhitelist}
|
||||
onSave={handleSaveHardwareWhitelist}
|
||||
onReset={handleResetHardwareWhitelist}
|
||||
saving={savingSection === "hardwareWhitelist"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export const VALIDATION_NAV_SECTIONS = [
|
||||
{ id: "autopilot", label: "Autopilot Validation", description: "Autopilot device and corporate identifier validation" },
|
||||
{ id: "hardware-whitelist", label: "Hardware Whitelist", description: "Manufacturer and model whitelist configuration" },
|
||||
] as const;
|
||||
|
||||
export type ValidationSectionId = (typeof VALIDATION_NAV_SECTIONS)[number]["id"];
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user