UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: единая шапка и OIDC вход launcher

This commit is contained in:
DCCONSTRUCTIONS
2026-05-05 15:00:23 +03:00
parent 09400f7db8
commit fd921cc400
10 changed files with 365 additions and 55 deletions
@@ -32,6 +32,7 @@ import {
Save,
SearchCheck,
ShieldCheck,
SlidersHorizontal,
Trash2,
UsersRound,
Video,
@@ -59,6 +60,7 @@ import {
getUser,
type AccessMatrixCell,
type LauncherData,
type LauncherSettings,
type MeResponse,
} from "../../shared/api/mockApi";
import { uploadStorageFile } from "../../shared/api/storageApi";
@@ -78,6 +80,7 @@ type AdminSection =
| "invites"
| "sync"
| "audit"
| "misc"
| "company";
type AccessAssignmentRole = Exclude<ServiceAppRole, "owner">;
@@ -109,6 +112,7 @@ const rootSections: Array<{ id: AdminSection; label: string; icon: React.ReactNo
{ id: "invites", label: "Инвайты", icon: <MailPlus size={16} /> },
{ id: "sync", label: "Синхронизация", icon: <RefreshCw size={16} /> },
{ id: "audit", label: "Аудит", icon: <ClipboardList size={16} /> },
{ id: "misc", label: "Разное", icon: <SlidersHorizontal size={16} /> },
];
const clientSections: Array<{ id: AdminSection; label: string; icon: React.ReactNode }> = [
@@ -146,6 +150,7 @@ export function AdminOverlay({
onReorderServices,
onCreateService,
onDeleteService,
onUpdateSettings,
}: {
data: LauncherData;
me: MeResponse;
@@ -171,6 +176,7 @@ export function AdminOverlay({
onReorderServices: (orderedServiceIds: string[]) => void;
onCreateService: () => void;
onDeleteService: (serviceId: string) => void;
onUpdateSettings: (patch: Partial<LauncherSettings>) => void;
}) {
const isRoot = me.launcherRole === "root_admin";
const sections = isRoot ? rootSections : clientSections;
@@ -335,6 +341,7 @@ export function AdminOverlay({
) : null}
{activeSection === "sync" ? <SyncSection data={data} clientId={scopedClientId} isRoot={isRoot} onRetrySync={onRetrySync} /> : null}
{activeSection === "audit" && isRoot ? <AuditSection data={data} /> : null}
{activeSection === "misc" && isRoot ? <MiscSection data={data} onUpdateSettings={onUpdateSettings} /> : null}
{activeSection === "company" ? <CompanySection data={data} clientId={scopedClientId} /> : null}
</div>
</section>
@@ -2327,6 +2334,58 @@ function AuditSection({ data }: { data: LauncherData }) {
);
}
function MiscSection({
data,
onUpdateSettings,
}: {
data: LauncherData;
onUpdateSettings: (patch: Partial<LauncherSettings>) => void;
}) {
const [logoLinkUrl, setLogoLinkUrl] = useState(data.settings.brand.logoLinkUrl);
useEffect(() => {
setLogoLinkUrl(data.settings.brand.logoLinkUrl);
}, [data.settings.brand.logoLinkUrl]);
const normalizedLogoLinkUrl = logoLinkUrl.trim() || "/";
const hasChanges = normalizedLogoLinkUrl !== data.settings.brand.logoLinkUrl;
return (
<GlassSurface className="table-shell admin-settings-panel">
<div className="table-toolbar">
<div>
<h3>Разное</h3>
<p className="admin-helper-note">
Общие настройки платформы, которые должны применяться в лаунчере, Task Manager и системных auth-экранах.
</p>
</div>
<Button
variant="accent"
type="button"
icon={<Save size={16} />}
disabled={!hasChanges}
onClick={() => onUpdateSettings({ brand: { logoLinkUrl: normalizedLogoLinkUrl } })}
>
Сохранить
</Button>
</div>
<div className="admin-settings-grid">
<label className="admin-settings-field">
<span>Ссылка логотипа</span>
<input
className="admin-table-input admin-settings-field__input"
value={logoLinkUrl}
placeholder="/"
onChange={(event) => setLogoLinkUrl(event.target.value)}
/>
<small>Куда ведёт клик по логотипу NODE.DC. Можно указать относительный путь или полный URL.</small>
</label>
</div>
</GlassSurface>
);
}
function CompanySection({ data, clientId }: { data: LauncherData; clientId: string }) {
const client = getClient(data, clientId);
@@ -2386,6 +2445,7 @@ function sectionTitle(section: AdminSection): string {
invites: "Инвайты",
sync: "Синхронизация",
audit: "Аудит",
misc: "Разное",
company: "Профиль компании",
};
return labels[section];
+3 -1
View File
@@ -17,6 +17,7 @@ export function TopBar({
onOpenShowcase,
onOpenProfileSettings,
onLogout,
brandLinkUrl = "/",
}: {
me: MeResponse;
clients: Client[];
@@ -30,6 +31,7 @@ export function TopBar({
onOpenShowcase: () => void;
onOpenProfileSettings: () => void;
onLogout?: () => void;
brandLinkUrl?: string;
}) {
const availableClientIds = new Set(me.memberships.map((membership) => membership.clientId));
const availableClients = clients.filter((client) => availableClientIds.has(client.id));
@@ -51,7 +53,7 @@ export function TopBar({
<div className="nodedc-expanded-toolbar">
<div className="nodedc-expanded-toolbar-top">
<div className="nodedc-expanded-toolbar-left">
<a href="/" aria-label="NODE.DC">
<a href={brandLinkUrl} className="nodedc-expanded-brand-link" aria-label="NODE.DC">
<img src="/nodedc-logo.svg" alt="NODE DC" className="nodedc-expanded-brand-logo" />
</a>
</div>