ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: policy создания workspace в Launcher
This commit is contained in:
parent
168010f05e
commit
6b002ec176
|
|
@ -29,6 +29,9 @@ const defaultSettings = {
|
||||||
brand: {
|
brand: {
|
||||||
logoLinkUrl: "/",
|
logoLinkUrl: "/",
|
||||||
},
|
},
|
||||||
|
taskManager: {
|
||||||
|
workspaceCreationPolicy: "any_authorized_user",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createControlPlaneStore({ projectRoot }) {
|
export function createControlPlaneStore({ projectRoot }) {
|
||||||
|
|
@ -195,6 +198,10 @@ export function createControlPlaneStore({ projectRoot }) {
|
||||||
...(data.settings?.brand ?? {}),
|
...(data.settings?.brand ?? {}),
|
||||||
...(patch.brand ?? {}),
|
...(patch.brand ?? {}),
|
||||||
},
|
},
|
||||||
|
taskManager: {
|
||||||
|
...(data.settings?.taskManager ?? {}),
|
||||||
|
...(patch.taskManager ?? {}),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
data.settings = settings;
|
data.settings = settings;
|
||||||
|
|
@ -203,7 +210,7 @@ export function createControlPlaneStore({ projectRoot }) {
|
||||||
objectType: "settings",
|
objectType: "settings",
|
||||||
objectName: "Brand settings",
|
objectName: "Brand settings",
|
||||||
result: "success",
|
result: "success",
|
||||||
details: `Logo link: ${settings.brand.logoLinkUrl}`,
|
details: `Logo link: ${settings.brand.logoLinkUrl}; Tasker workspace policy: ${settings.taskManager.workspaceCreationPolicy}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await writeData(data);
|
await writeData(data);
|
||||||
|
|
@ -1051,11 +1058,19 @@ function normalizeData(payload) {
|
||||||
function normalizeSettings(payload) {
|
function normalizeSettings(payload) {
|
||||||
const settings = typeof payload === "object" && payload !== null ? payload : {};
|
const settings = typeof payload === "object" && payload !== null ? payload : {};
|
||||||
const brand = typeof settings.brand === "object" && settings.brand !== null ? settings.brand : {};
|
const brand = typeof settings.brand === "object" && settings.brand !== null ? settings.brand : {};
|
||||||
|
const taskManager = typeof settings.taskManager === "object" && settings.taskManager !== null ? settings.taskManager : {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
brand: {
|
brand: {
|
||||||
logoLinkUrl: optionalString(brand.logoLinkUrl, defaultSettings.brand.logoLinkUrl),
|
logoLinkUrl: optionalString(brand.logoLinkUrl, defaultSettings.brand.logoLinkUrl),
|
||||||
},
|
},
|
||||||
|
taskManager: {
|
||||||
|
workspaceCreationPolicy: pickEnum(
|
||||||
|
taskManager.workspaceCreationPolicy,
|
||||||
|
new Set(["any_authorized_user", "task_admins_only", "disabled"]),
|
||||||
|
defaultSettings.taskManager.workspaceCreationPolicy
|
||||||
|
),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -373,6 +373,8 @@ app.post("/api/internal/access/check", (req, res) => {
|
||||||
const groups = resolveRequiredGroups(snapshot.data, user);
|
const groups = resolveRequiredGroups(snapshot.data, user);
|
||||||
const app = getAppsForUser(groups).find((candidate) => candidate.slug === serviceSlug);
|
const app = getAppsForUser(groups).find((candidate) => candidate.slug === serviceSlug);
|
||||||
const allowed = Boolean(app?.hasAccess);
|
const allowed = Boolean(app?.hasAccess);
|
||||||
|
const workspacePolicy =
|
||||||
|
serviceSlug === "task-manager" ? resolveTaskManagerWorkspacePolicy(snapshot.data, groups, allowed) : null;
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|
@ -381,6 +383,7 @@ app.post("/api/internal/access/check", (req, res) => {
|
||||||
serviceSlug,
|
serviceSlug,
|
||||||
groups,
|
groups,
|
||||||
matchedGroups: app?.matchedGroups ?? [],
|
matchedGroups: app?.matchedGroups ?? [],
|
||||||
|
workspacePolicy,
|
||||||
user: {
|
user: {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
|
|
@ -1234,6 +1237,43 @@ function pruneExpiredServiceHandoffs() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveTaskManagerWorkspacePolicy(data, groups, hasTaskManagerAccess) {
|
||||||
|
const mode = data.settings?.taskManager?.workspaceCreationPolicy ?? "any_authorized_user";
|
||||||
|
const groupSet = new Set(groups);
|
||||||
|
const isSuperAdmin = groupSet.has("nodedc:superadmin");
|
||||||
|
const isTaskManagerAdmin = groupSet.has("nodedc:taskmanager:admin");
|
||||||
|
|
||||||
|
if (!hasTaskManagerAccess) {
|
||||||
|
return {
|
||||||
|
mode,
|
||||||
|
canCreateWorkspace: false,
|
||||||
|
reason: "Нет доступа к Operational Core.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === "disabled") {
|
||||||
|
return {
|
||||||
|
mode,
|
||||||
|
canCreateWorkspace: false,
|
||||||
|
reason: "Создание рабочих пространств отключено на уровне платформы.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === "task_admins_only" && !isSuperAdmin && !isTaskManagerAdmin) {
|
||||||
|
return {
|
||||||
|
mode,
|
||||||
|
canCreateWorkspace: false,
|
||||||
|
reason: "Создание рабочих пространств доступно только администраторам Operational Core.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
mode,
|
||||||
|
canCreateWorkspace: true,
|
||||||
|
reason: "Создание рабочих пространств разрешено платформенной policy.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function getFrontchannelLogoutUrls() {
|
function getFrontchannelLogoutUrls() {
|
||||||
const urls = [config.taskLogoutUrl];
|
const urls = [config.taskLogoutUrl];
|
||||||
const launcherData = readLauncherData();
|
const launcherData = readLauncherData();
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,13 @@ export interface LauncherSettings {
|
||||||
brand: {
|
brand: {
|
||||||
logoLinkUrl: string;
|
logoLinkUrl: string;
|
||||||
};
|
};
|
||||||
|
taskManager: {
|
||||||
|
workspaceCreationPolicy: TaskManagerWorkspaceCreationPolicy;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TaskManagerWorkspaceCreationPolicy = "any_authorized_user" | "task_admins_only" | "disabled";
|
||||||
|
|
||||||
export interface ProfileOption {
|
export interface ProfileOption {
|
||||||
userId: string;
|
userId: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
|
@ -94,6 +99,9 @@ export const defaultLauncherSettings: LauncherSettings = {
|
||||||
brand: {
|
brand: {
|
||||||
logoLinkUrl: "/",
|
logoLinkUrl: "/",
|
||||||
},
|
},
|
||||||
|
taskManager: {
|
||||||
|
workspaceCreationPolicy: "any_authorized_user",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialLauncherData: LauncherData = normalizeLauncherData({
|
export const initialLauncherData: LauncherData = normalizeLauncherData({
|
||||||
|
|
@ -115,15 +123,29 @@ export function normalizeLauncherSettings(settings?: Partial<LauncherSettings> |
|
||||||
typeof settings?.brand === "object" && settings.brand !== null
|
typeof settings?.brand === "object" && settings.brand !== null
|
||||||
? settings.brand
|
? settings.brand
|
||||||
: ({} as Partial<LauncherSettings["brand"]>);
|
: ({} as Partial<LauncherSettings["brand"]>);
|
||||||
|
const taskManager =
|
||||||
|
typeof settings?.taskManager === "object" && settings.taskManager !== null
|
||||||
|
? settings.taskManager
|
||||||
|
: ({} as Partial<LauncherSettings["taskManager"]>);
|
||||||
const logoLinkUrl = typeof brand.logoLinkUrl === "string" && brand.logoLinkUrl.trim() ? brand.logoLinkUrl.trim() : "/";
|
const logoLinkUrl = typeof brand.logoLinkUrl === "string" && brand.logoLinkUrl.trim() ? brand.logoLinkUrl.trim() : "/";
|
||||||
|
const workspaceCreationPolicy = isTaskManagerWorkspaceCreationPolicy(taskManager.workspaceCreationPolicy)
|
||||||
|
? taskManager.workspaceCreationPolicy
|
||||||
|
: defaultLauncherSettings.taskManager.workspaceCreationPolicy;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
brand: {
|
brand: {
|
||||||
logoLinkUrl,
|
logoLinkUrl,
|
||||||
},
|
},
|
||||||
|
taskManager: {
|
||||||
|
workspaceCreationPolicy,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTaskManagerWorkspaceCreationPolicy(value: unknown): value is TaskManagerWorkspaceCreationPolicy {
|
||||||
|
return value === "any_authorized_user" || value === "task_admins_only" || value === "disabled";
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizeLauncherData(data: Partial<LauncherData> | null | undefined): LauncherData {
|
export function normalizeLauncherData(data: Partial<LauncherData> | null | undefined): LauncherData {
|
||||||
const payload = data ?? {};
|
const payload = data ?? {};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ import {
|
||||||
type LauncherData,
|
type LauncherData,
|
||||||
type LauncherSettings,
|
type LauncherSettings,
|
||||||
type MeResponse,
|
type MeResponse,
|
||||||
|
type TaskManagerWorkspaceCreationPolicy,
|
||||||
} from "../../shared/api/mockApi";
|
} from "../../shared/api/mockApi";
|
||||||
import { uploadStorageFile } from "../../shared/api/storageApi";
|
import { uploadStorageFile } from "../../shared/api/storageApi";
|
||||||
import { cn } from "../../shared/lib/cn";
|
import { cn } from "../../shared/lib/cn";
|
||||||
|
|
@ -885,6 +886,27 @@ const accessAssignmentOptions: Array<NodeDcSelectOption<AccessAssignmentValue>>
|
||||||
{ value: "deny", label: "Deny", description: "Исключение", tone: "red" },
|
{ value: "deny", label: "Deny", description: "Исключение", tone: "red" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const taskManagerWorkspacePolicyOptions: Array<NodeDcSelectOption<TaskManagerWorkspaceCreationPolicy>> = [
|
||||||
|
{
|
||||||
|
value: "any_authorized_user",
|
||||||
|
label: "Все с доступом",
|
||||||
|
description: "Пользователь с доступом к Operational Core может создать собственный workspace.",
|
||||||
|
tone: "green",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "task_admins_only",
|
||||||
|
label: "Только админы",
|
||||||
|
description: "Workspace создают только суперпользователь и админы Operational Core.",
|
||||||
|
tone: "yellow",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "disabled",
|
||||||
|
label: "Отключено",
|
||||||
|
description: "Создание workspace закрыто для всех через платформенную policy.",
|
||||||
|
tone: "red",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const mediaAccept = "image/*,video/*,.gif,.webm,.mov,.mp4,.m4v,.avi,.mkv";
|
const mediaAccept = "image/*,video/*,.gif,.webm,.mov,.mp4,.m4v,.avi,.mkv";
|
||||||
const modalActionAccentRgb = [247, 248, 244] as const;
|
const modalActionAccentRgb = [247, 248, 244] as const;
|
||||||
|
|
||||||
|
|
@ -2401,13 +2423,19 @@ function MiscSection({
|
||||||
onUpdateSettings: (patch: Partial<LauncherSettings>) => void;
|
onUpdateSettings: (patch: Partial<LauncherSettings>) => void;
|
||||||
}) {
|
}) {
|
||||||
const [logoLinkUrl, setLogoLinkUrl] = useState(data.settings.brand.logoLinkUrl);
|
const [logoLinkUrl, setLogoLinkUrl] = useState(data.settings.brand.logoLinkUrl);
|
||||||
|
const [workspaceCreationPolicy, setWorkspaceCreationPolicy] = useState(
|
||||||
|
data.settings.taskManager.workspaceCreationPolicy
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLogoLinkUrl(data.settings.brand.logoLinkUrl);
|
setLogoLinkUrl(data.settings.brand.logoLinkUrl);
|
||||||
}, [data.settings.brand.logoLinkUrl]);
|
setWorkspaceCreationPolicy(data.settings.taskManager.workspaceCreationPolicy);
|
||||||
|
}, [data.settings.brand.logoLinkUrl, data.settings.taskManager.workspaceCreationPolicy]);
|
||||||
|
|
||||||
const normalizedLogoLinkUrl = logoLinkUrl.trim() || "/";
|
const normalizedLogoLinkUrl = logoLinkUrl.trim() || "/";
|
||||||
const hasChanges = normalizedLogoLinkUrl !== data.settings.brand.logoLinkUrl;
|
const hasChanges =
|
||||||
|
normalizedLogoLinkUrl !== data.settings.brand.logoLinkUrl ||
|
||||||
|
workspaceCreationPolicy !== data.settings.taskManager.workspaceCreationPolicy;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GlassSurface className="table-shell admin-settings-panel">
|
<GlassSurface className="table-shell admin-settings-panel">
|
||||||
|
|
@ -2423,7 +2451,12 @@ function MiscSection({
|
||||||
type="button"
|
type="button"
|
||||||
icon={<Save size={16} />}
|
icon={<Save size={16} />}
|
||||||
disabled={!hasChanges}
|
disabled={!hasChanges}
|
||||||
onClick={() => onUpdateSettings({ brand: { logoLinkUrl: normalizedLogoLinkUrl } })}
|
onClick={() =>
|
||||||
|
onUpdateSettings({
|
||||||
|
brand: { logoLinkUrl: normalizedLogoLinkUrl },
|
||||||
|
taskManager: { workspaceCreationPolicy },
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Сохранить
|
Сохранить
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -2440,6 +2473,18 @@ function MiscSection({
|
||||||
/>
|
/>
|
||||||
<small>Куда ведёт клик по логотипу NODE.DC. Можно указать относительный путь или полный URL.</small>
|
<small>Куда ведёт клик по логотипу NODE.DC. Можно указать относительный путь или полный URL.</small>
|
||||||
</label>
|
</label>
|
||||||
|
<label className="admin-settings-field">
|
||||||
|
<span>Operational Core: создание workspace</span>
|
||||||
|
<NodeDcSelect
|
||||||
|
className="admin-modal-select-wrap"
|
||||||
|
triggerClassName="admin-modal-select-trigger"
|
||||||
|
value={workspaceCreationPolicy}
|
||||||
|
options={taskManagerWorkspacePolicyOptions}
|
||||||
|
label="Политика создания workspace в Operational Core"
|
||||||
|
onChange={(value) => setWorkspaceCreationPolicy(value)}
|
||||||
|
/>
|
||||||
|
<small>Платформенная policy для новых пользователей без назначенных рабочих пространств в Task Manager.</small>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</GlassSurface>
|
</GlassSurface>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue