UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: единая шапка и OIDC вход launcher
This commit is contained in:
+33
-8
@@ -24,6 +24,7 @@ import {
|
||||
updateAdminInvite,
|
||||
updateAdminMembership,
|
||||
updateAdminService,
|
||||
updateAdminSettings,
|
||||
updateAdminUserProfile,
|
||||
type ControlPlaneMutationResult,
|
||||
} from "../shared/api/adminApi";
|
||||
@@ -31,8 +32,10 @@ import {
|
||||
buildLauncherServices,
|
||||
buildMe,
|
||||
initialLauncherData,
|
||||
normalizeLauncherData,
|
||||
profileOptions,
|
||||
type LauncherData,
|
||||
type LauncherSettings,
|
||||
} from "../shared/api/mockApi";
|
||||
import {
|
||||
fetchAuthSession,
|
||||
@@ -55,6 +58,8 @@ import { ServiceRail } from "../widgets/service-rail/ServiceRail";
|
||||
import { ServiceStage } from "../widgets/service-stage/ServiceStage";
|
||||
import { TopBar } from "../widgets/top-bar/TopBar";
|
||||
|
||||
let lastAuthRedirect: { url: string; startedAt: number } | null = null;
|
||||
|
||||
export function LauncherApp() {
|
||||
const [data, setData] = useState<LauncherData>(() => syncLauncherServiceLinks(initialLauncherData));
|
||||
const [activeProfileId, setActiveProfileId] = useState(profileOptions[0].userId);
|
||||
@@ -188,7 +193,7 @@ export function LauncherApp() {
|
||||
useEffect(() => {
|
||||
if (!authSession || authSession.authenticated) return;
|
||||
|
||||
window.location.replace(buildLoginRedirectUrl(authSession.loginUrl));
|
||||
redirectToLogin(authSession.loginUrl);
|
||||
}, [authSession]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -198,7 +203,7 @@ export function LauncherApp() {
|
||||
if (isRedirecting) return;
|
||||
|
||||
isRedirecting = true;
|
||||
window.location.replace(buildLoginRedirectUrl("/auth/login?prompt=login"));
|
||||
redirectToLogin("/auth/login?prompt=login");
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -213,7 +218,7 @@ export function LauncherApp() {
|
||||
if (!isMounted) return;
|
||||
|
||||
if (!session.authenticated) {
|
||||
window.location.replace(buildLoginRedirectUrl(session.loginUrl));
|
||||
redirectToLogin(session.loginUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -221,7 +226,7 @@ export function LauncherApp() {
|
||||
})
|
||||
.catch(() => {
|
||||
if (isMounted) {
|
||||
window.location.replace(buildLoginRedirectUrl("/auth/login"));
|
||||
redirectToLogin("/auth/login");
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -418,6 +423,10 @@ export function LauncherApp() {
|
||||
applyControlPlaneMutation(retryAdminSync(syncId));
|
||||
}
|
||||
|
||||
function handleUpdateSettings(patch: Partial<LauncherSettings>) {
|
||||
applyControlPlaneMutation(updateAdminSettings(patch));
|
||||
}
|
||||
|
||||
function handleUpdateService(serviceId: string, patch: Partial<Service>) {
|
||||
applyControlPlaneMutation(updateAdminService(serviceId, patch));
|
||||
}
|
||||
@@ -542,6 +551,7 @@ export function LauncherApp() {
|
||||
onOpenShowcase={() => setAdminOpen(false)}
|
||||
onOpenProfileSettings={() => setProfileSettingsOpen(true)}
|
||||
onLogout={handleLogout}
|
||||
brandLinkUrl={data.settings.brand.logoLinkUrl}
|
||||
/>
|
||||
|
||||
<main className="launcher-main">
|
||||
@@ -578,6 +588,7 @@ export function LauncherApp() {
|
||||
onReorderServices={handleReorderServices}
|
||||
onCreateService={handleCreateService}
|
||||
onDeleteService={handleDeleteService}
|
||||
onUpdateSettings={handleUpdateSettings}
|
||||
/>
|
||||
) : null}
|
||||
{profileSettingsOpen && activeProfileUser ? (
|
||||
@@ -594,10 +605,12 @@ export function LauncherApp() {
|
||||
);
|
||||
}
|
||||
|
||||
function syncLauncherServiceLinks(data: LauncherData): LauncherData {
|
||||
function syncLauncherServiceLinks(data: Partial<LauncherData>): LauncherData {
|
||||
const normalizedData = normalizeLauncherData(data);
|
||||
|
||||
return {
|
||||
...data,
|
||||
services: data.services.map(syncServiceLaunchLink),
|
||||
...normalizedData,
|
||||
services: normalizedData.services.map(syncServiceLaunchLink),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -697,7 +710,7 @@ function AuthStateScreen({
|
||||
<p style={{ margin: 0, color: "var(--text-secondary)", lineHeight: 1.5 }}>{description}</p>
|
||||
{error ? <p style={{ margin: 0, color: "var(--warning)", lineHeight: 1.45 }}>{error}</p> : null}
|
||||
{loginUrl ? (
|
||||
<button className="button button--primary" type="button" onClick={() => window.location.assign(loginUrl)}>
|
||||
<button className="button button--primary" type="button" onClick={() => redirectToLogin(loginUrl)}>
|
||||
Войти
|
||||
</button>
|
||||
) : null}
|
||||
@@ -720,3 +733,15 @@ function buildLoginRedirectUrl(loginUrl?: string) {
|
||||
|
||||
return url.origin === window.location.origin ? `${url.pathname}${url.search}${url.hash}` : url.toString();
|
||||
}
|
||||
|
||||
function redirectToLogin(loginUrl?: string) {
|
||||
const redirectUrl = buildLoginRedirectUrl(loginUrl);
|
||||
const now = Date.now();
|
||||
|
||||
if (lastAuthRedirect && now - lastAuthRedirect.startedAt < 1500) {
|
||||
return;
|
||||
}
|
||||
|
||||
lastAuthRedirect = { url: redirectUrl, startedAt: now };
|
||||
window.location.replace(redirectUrl);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user