feat: strengthen module contracts and shared WebUI runtime
This commit is contained in:
@@ -20,10 +20,26 @@ import DismissibleAlert from "./DismissibleAlert";
|
||||
import FormField from "./FormField";
|
||||
import LoadingFrame from "./LoadingFrame";
|
||||
import PasswordField from "./PasswordField";
|
||||
import {
|
||||
ReferenceMultiSelect,
|
||||
combineReferenceOptionProviders,
|
||||
customReferenceOption,
|
||||
staticReferenceOptionProvider,
|
||||
type ReferenceOption
|
||||
} from "./ReferenceSelect";
|
||||
import { platformModuleReferenceProvider } from "../platform/referenceProviders";
|
||||
import StatusBadge from "./StatusBadge";
|
||||
import TableActionGroup from "./table/TableActionGroup";
|
||||
import ToggleSwitch from "./ToggleSwitch";
|
||||
import { useUnsavedDraftGuard } from "./UnsavedChangesGuard";
|
||||
import {
|
||||
usePlatformModules,
|
||||
usePlatformUiCapabilities
|
||||
} from "../platform/ModuleContext";
|
||||
import { usePlatformLanguage } from "../i18n/LanguageContext";
|
||||
import type {
|
||||
CredentialReferenceSelectorsUiCapability
|
||||
} from "./ReferenceSelect";
|
||||
|
||||
export type CredentialEnvelopeTargetOption = {
|
||||
id: string;
|
||||
@@ -31,11 +47,19 @@ export type CredentialEnvelopeTargetOption = {
|
||||
secondary?: string | null;
|
||||
};
|
||||
|
||||
export type CredentialEnvelopeServerOption = {
|
||||
id: string;
|
||||
label: string;
|
||||
secondary?: string | null;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type CredentialEnvelopeManagerProps = {
|
||||
settings: ApiSettings;
|
||||
scopeType: Extract<MailProfileScope, "system" | "tenant" | "user" | "group">;
|
||||
scopeId?: string | null;
|
||||
targetOptions?: CredentialEnvelopeTargetOption[];
|
||||
serverOptions?: CredentialEnvelopeServerOption[];
|
||||
targetLabel?: string;
|
||||
title?: string;
|
||||
canWrite: boolean;
|
||||
@@ -76,10 +100,13 @@ export default function CredentialEnvelopeManager({
|
||||
scopeType,
|
||||
scopeId,
|
||||
targetOptions = [],
|
||||
serverOptions = [],
|
||||
targetLabel = "Target",
|
||||
title = "Credential envelopes",
|
||||
canWrite
|
||||
}: CredentialEnvelopeManagerProps) {
|
||||
const modules = usePlatformModules();
|
||||
const { translateText } = usePlatformLanguage();
|
||||
const [selectedTargetId, setSelectedTargetId] = useState(scopeId ?? targetOptions[0]?.id ?? "");
|
||||
const [credentials, setCredentials] = useState<CredentialEnvelopeSummary[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -95,6 +122,56 @@ export default function CredentialEnvelopeManager({
|
||||
: selectedTargetId || null;
|
||||
const scopeReady = scopeType === "system" || scopeType === "tenant" || Boolean(activeScopeId);
|
||||
const draftDirty = Boolean(editing) && credentialDraftKey(draft) !== savedDraftKey;
|
||||
const referenceCapabilities =
|
||||
usePlatformUiCapabilities<CredentialReferenceSelectorsUiCapability>(
|
||||
"core.credentialReferenceSelectors"
|
||||
);
|
||||
const moduleReferenceOptions = useMemo<ReferenceOption[]>(
|
||||
() => modules.map((module) => ({
|
||||
value: module.id,
|
||||
label: translateText(module.label),
|
||||
description: `${module.id} · version ${module.version}`,
|
||||
kind: "module",
|
||||
sourceModule: "core",
|
||||
provenance: { version: module.version }
|
||||
})),
|
||||
[modules, translateText]
|
||||
);
|
||||
const serverReferenceOptions = useMemo<ReferenceOption[]>(
|
||||
() => serverOptions.map((server) => ({
|
||||
value: server.id,
|
||||
label: server.label,
|
||||
description: server.secondary ?? server.id,
|
||||
kind: "server",
|
||||
disabled: server.disabled,
|
||||
availability: server.disabled ? "inactive" : "available",
|
||||
provenance: { announcedByConsumer: true }
|
||||
})),
|
||||
[serverOptions]
|
||||
);
|
||||
const moduleReferenceProvider = useMemo(
|
||||
() => platformModuleReferenceProvider(settings, moduleReferenceOptions),
|
||||
[moduleReferenceOptions, settings]
|
||||
);
|
||||
const serverReferenceProvider = useMemo(
|
||||
() =>
|
||||
combineReferenceOptionProviders([
|
||||
staticReferenceOptionProvider(serverReferenceOptions),
|
||||
...referenceCapabilities.map((capability) =>
|
||||
capability.serverProvider(settings, {
|
||||
scopeType,
|
||||
scopeId: activeScopeId
|
||||
})
|
||||
)
|
||||
]),
|
||||
[
|
||||
activeScopeId,
|
||||
referenceCapabilities,
|
||||
scopeType,
|
||||
serverReferenceOptions,
|
||||
settings
|
||||
]
|
||||
);
|
||||
|
||||
useUnsavedDraftGuard({
|
||||
dirty: draftDirty,
|
||||
@@ -436,11 +513,26 @@ export default function CredentialEnvelopeManager({
|
||||
<p>Empty module or server lists mean every module or server allowed by scope.</p>
|
||||
</header>
|
||||
<div className="form-grid two">
|
||||
<FormField label="Modules" help="Comma-separated module IDs, for example mail, files, calendar, addresses.">
|
||||
<input value={draft.allowedModules} disabled={saving} onChange={(event) => setDraft({ ...draft, allowedModules: event.target.value })} />
|
||||
<FormField label="Modules" help="Choose installed modules that may use this credential. Empty means every module allowed by scope.">
|
||||
<ReferenceMultiSelect
|
||||
values={splitValues(draft.allowedModules)}
|
||||
onChange={(values) => setDraft({ ...draft, allowedModules: values.join(", ") })}
|
||||
provider={moduleReferenceProvider}
|
||||
aria-label="Credential modules"
|
||||
placeholder="Add a module"
|
||||
disabled={saving}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Servers" help="Optional references such as mail:server-id, files:connection-id, calendar:source-id, or addresses:source-id.">
|
||||
<input value={draft.allowedServerRefs} disabled={saving} onChange={(event) => setDraft({ ...draft, allowedServerRefs: event.target.value })} />
|
||||
<FormField label="Servers" help="Choose announced servers or enter an explicit module-qualified reference. Empty means every permitted server.">
|
||||
<ReferenceMultiSelect
|
||||
values={splitValues(draft.allowedServerRefs)}
|
||||
onChange={(values) => setDraft({ ...draft, allowedServerRefs: values.join(", ") })}
|
||||
provider={serverReferenceProvider}
|
||||
createCustomOption={(value) => customReferenceOption(value, "server")}
|
||||
aria-label="Credential servers"
|
||||
placeholder="Add a server reference"
|
||||
disabled={saving}
|
||||
/>
|
||||
</FormField>
|
||||
<ToggleSwitch checked={draft.inheritToLowerScopes} disabled={saving} onChange={(inheritToLowerScopes) => setDraft({ ...draft, inheritToLowerScopes })} label="Visible to lower scopes" />
|
||||
<ToggleSwitch checked={draft.isActive} disabled={saving} onChange={(isActive) => setDraft({ ...draft, isActive })} label="Active" />
|
||||
|
||||
Reference in New Issue
Block a user