Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/admin-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import Keys from "./pages/Keys";
import NotFound from "./pages/NotFound";
import OrganizationCreate from "./pages/OrganizationCreate";
import OrganizationEdit from "./pages/OrganizationEdit";
import OrganizationFederationSetup from "./pages/OrganizationFederationSetup";
import Organizations from "./pages/Organizations";
import Permissions from "./pages/Permissions";
import Preview from "./pages/Preview";
Expand Down Expand Up @@ -312,6 +313,14 @@ const App = () => {
</DashboardLayout>
}
/>
<Route
path="/organizations/:organizationId/federation/:connectionId"
element={
<DashboardLayout adminSession={adminSession} onLogout={handleLogout}>
<OrganizationFederationSetup />
</DashboardLayout>
}
/>
<Route
path="/roles"
element={
Expand Down
64 changes: 63 additions & 1 deletion packages/admin-ui/src/pages/FederationConnections.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import adminApiService, {
type FederationConnection,
type FederationConnectionRequest,
type FederationPolicyControls,
type Organization,
type SortOrder,
} from "@/services/api";

Expand All @@ -63,6 +64,7 @@ const defaultFederationPolicy: FederationPolicyControls = {

type FormState = {
name: string;
organizationId: string;
issuer: string;
clientId: string;
clientSecret: string;
Expand Down Expand Up @@ -92,6 +94,7 @@ type FormState = {

const emptyForm: FormState = {
name: "",
organizationId: "",
issuer: "",
clientId: "",
clientSecret: "",
Expand All @@ -118,6 +121,7 @@ function formFromConnection(connection: FederationConnection): FormState {
const policy = { ...defaultFederationPolicy, ...(connection.metadata?.darkauth_policy || {}) };
return {
name: connection.name,
organizationId: connection.organizationId || "",
issuer: connection.issuer,
clientId: connection.clientId,
clientSecret: "",
Expand Down Expand Up @@ -177,6 +181,7 @@ function buildPayload(form: FormState, isEdit: boolean): FederationConnectionReq
: undefined;
const payload: FederationConnectionRequest = {
name: form.name.trim(),
organizationId: form.organizationId || undefined,
issuer: form.issuer.trim(),
clientId: form.clientId.trim(),
discoveryUrl: form.discoveryUrl.trim() || undefined,
Expand Down Expand Up @@ -205,6 +210,7 @@ function buildPayload(form: FormState, isEdit: boolean): FederationConnectionReq

export default function FederationConnections() {
const [connections, setConnections] = useState<FederationConnection[]>([]);
const [organizations, setOrganizations] = useState<Organization[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [searchQuery, setSearchQuery] = useState("");
Expand Down Expand Up @@ -250,6 +256,21 @@ export default function FederationConnections() {
loadConnections();
}, [loadConnections]);

useEffect(() => {
let cancelled = false;
adminApiService
.getOrganizationsPaged({ page: 1, limit: 100, sortBy: "name", sortOrder: "asc" })
.then((response) => {
if (!cancelled) setOrganizations(response.organizations);
})
.catch(() => {
if (!cancelled) setOrganizations([]);
});
return () => {
cancelled = true;
};
}, []);

useEffect(() => {
const handle = setTimeout(() => {
setDebouncedSearch(searchQuery);
Expand Down Expand Up @@ -468,6 +489,7 @@ export default function FederationConnections() {
sortOrder={sortOrder}
onToggle={() => toggleSort("issuer")}
/>
<TableHead>Organization</TableHead>
<TableHead>Domains</TableHead>
<TableHead>Status</TableHead>
<SortableTableHead
Expand Down Expand Up @@ -502,6 +524,18 @@ export default function FederationConnections() {
</button>
</TableCell>
<TableCell>{connection.issuer}</TableCell>
<TableCell>
{connection.organizationName ? (
<div>
<div>{connection.organizationName}</div>
{connection.organizationSlug ? (
<code style={{ fontSize: 11 }}>{connection.organizationSlug}</code>
) : null}
</div>
) : (
<Badge variant="outline">Unassigned</Badge>
)}
</TableCell>
<TableCell>
{connection.domains.length > 0 ? connection.domains.join(", ") : "None"}
</TableCell>
Expand Down Expand Up @@ -562,6 +596,30 @@ export default function FederationConnections() {
}
/>
</FormField>
<FormField label={<Label>Organization{editing ? "" : " *"}</Label>}>
<Select
value={form.organizationId}
onValueChange={(value) =>
setForm((current) => ({ ...current, organizationId: value }))
}
>
<SelectTrigger>
<SelectValue placeholder="Select an organization" />
</SelectTrigger>
<SelectContent>
{organizations.map((organization) => (
<SelectItem
key={organization.organizationId}
value={organization.organizationId}
>
{organization.slug
? `${organization.name} (${organization.slug})`
: organization.name}
</SelectItem>
))}
</SelectContent>
</Select>
</FormField>
<FormField label={<Label>Issuer</Label>}>
<div style={{ display: "flex", gap: 8 }}>
<Input
Expand Down Expand Up @@ -857,7 +915,11 @@ export default function FederationConnections() {
<FormActions withMargin>
<Button
disabled={
submitting || !form.name.trim() || !form.issuer.trim() || !form.clientId.trim()
submitting ||
!form.name.trim() ||
!form.issuer.trim() ||
!form.clientId.trim() ||
(!editing && !form.organizationId)
}
onClick={save}
>
Expand Down
50 changes: 50 additions & 0 deletions packages/admin-ui/src/pages/OrganizationEdit.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,53 @@
border: 1px solid hsl(var(--border));
border-radius: var(--radius);
}

.roleCatalog {
display: grid;
gap: 12px;
}

.roleCatalogItem {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 12px;
border: 1px solid hsl(var(--border));
border-radius: 8px;
}

.roleCatalogItem strong {
display: block;
color: hsl(var(--foreground));
}

.enterpriseGrid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}

.enterprisePanel {
display: grid;
align-content: start;
gap: 12px;
padding: 16px;
border: 1px solid hsl(var(--border));
border-radius: 8px;
}

.enterpriseItem {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
min-width: 0;
padding: 10px 0;
border-top: 1px solid hsl(var(--border));
}

.enterpriseItem span {
min-width: 0;
overflow-wrap: anywhere;
}
Loading
Loading