ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: policy создания workspace в Launcher
This commit is contained in:
parent
168010f05e
commit
6b002ec176
|
|
@ -29,6 +29,9 @@ const defaultSettings = {
|
|||
brand: {
|
||||
logoLinkUrl: "/",
|
||||
},
|
||||
taskManager: {
|
||||
workspaceCreationPolicy: "any_authorized_user",
|
||||
},
|
||||
};
|
||||
|
||||
export function createControlPlaneStore({ projectRoot }) {
|
||||
|
|
@ -195,6 +198,10 @@ export function createControlPlaneStore({ projectRoot }) {
|
|||
...(data.settings?.brand ?? {}),
|
||||
...(patch.brand ?? {}),
|
||||
},
|
||||
taskManager: {
|
||||
...(data.settings?.taskManager ?? {}),
|
||||
...(patch.taskManager ?? {}),
|
||||
},
|
||||
});
|
||||
|
||||
data.settings = settings;
|
||||
|
|
@ -203,7 +210,7 @@ export function createControlPlaneStore({ projectRoot }) {
|
|||
objectType: "settings",
|
||||
objectName: "Brand settings",
|
||||
result: "success",
|
||||
details: `Logo link: ${settings.brand.logoLinkUrl}`,
|
||||
details: `Logo link: ${settings.brand.logoLinkUrl}; Tasker workspace policy: ${settings.taskManager.workspaceCreationPolicy}`,
|
||||
});
|
||||
|
||||
await writeData(data);
|
||||
|
|
@ -1051,11 +1058,19 @@ function normalizeData(payload) {
|
|||
function normalizeSettings(payload) {
|
||||
const settings = typeof payload === "object" && payload !== null ? payload : {};
|
||||
const brand = typeof settings.brand === "object" && settings.brand !== null ? settings.brand : {};
|
||||
const taskManager = typeof settings.taskManager === "object" && settings.taskManager !== null ? settings.taskManager : {};
|
||||
|
||||
return {
|
||||
brand: {
|
||||
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 app = getAppsForUser(groups).find((candidate) => candidate.slug === serviceSlug);
|
||||
const allowed = Boolean(app?.hasAccess);
|
||||
const workspacePolicy =
|
||||
serviceSlug === "task-manager" ? resolveTaskManagerWorkspacePolicy(snapshot.data, groups, allowed) : null;
|
||||
|
||||
res.json({
|
||||
ok: true,
|
||||
|
|
@ -381,6 +383,7 @@ app.post("/api/internal/access/check", (req, res) => {
|
|||
serviceSlug,
|
||||
groups,
|
||||
matchedGroups: app?.matchedGroups ?? [],
|
||||
workspacePolicy,
|
||||
user: {
|
||||
id: user.id,
|
||||
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() {
|
||||
const urls = [config.taskLogoutUrl];
|
||||
const launcherData = readLauncherData();
|
||||
|
|
|
|||
|
|
@ -67,8 +67,13 @@ export interface LauncherSettings {
|
|||
brand: {
|
||||
logoLinkUrl: string;
|
||||
};
|
||||
taskManager: {
|
||||
workspaceCreationPolicy: TaskManagerWorkspaceCreationPolicy;
|
||||
};
|
||||
}
|
||||
|
||||
export type TaskManagerWorkspaceCreationPolicy = "any_authorized_user" | "task_admins_only" | "disabled";
|
||||
|
||||
export interface ProfileOption {
|
||||
userId: string;
|
||||
label: string;
|
||||
|
|
@ -94,6 +99,9 @@ export const defaultLauncherSettings: LauncherSettings = {
|
|||
brand: {
|
||||
logoLinkUrl: "/",
|
||||
},
|
||||
taskManager: {
|
||||
workspaceCreationPolicy: "any_authorized_user",
|
||||
},
|
||||
};
|
||||
|
||||
export const initialLauncherData: LauncherData = normalizeLauncherData({
|
||||
|
|
@ -115,15 +123,29 @@ export function normalizeLauncherSettings(settings?: Partial<LauncherSettings> |
|
|||
typeof settings?.brand === "object" && settings.brand !== null
|
||||
? settings.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 workspaceCreationPolicy = isTaskManagerWorkspaceCreationPolicy(taskManager.workspaceCreationPolicy)
|
||||
? taskManager.workspaceCreationPolicy
|
||||
: defaultLauncherSettings.taskManager.workspaceCreationPolicy;
|
||||
|
||||
return {
|
||||
brand: {
|
||||
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 {
|
||||
const payload = data ?? {};
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ import {
|
|||
type LauncherData,
|
||||
type LauncherSettings,
|
||||
type MeResponse,
|
||||
type TaskManagerWorkspaceCreationPolicy,
|
||||
} from "../../shared/api/mockApi";
|
||||
import { uploadStorageFile } from "../../shared/api/storageApi";
|
||||
import { cn } from "../../shared/lib/cn";
|
||||
|
|
@ -885,6 +886,27 @@ const accessAssignmentOptions: Array<NodeDcSelectOption<AccessAssignmentValue>>
|
|||
{ 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 modalActionAccentRgb = [247, 248, 244] as const;
|
||||
|
||||
|
|
@ -2401,13 +2423,19 @@ function MiscSection({
|
|||
onUpdateSettings: (patch: Partial<LauncherSettings>) => void;
|
||||
}) {
|
||||
const [logoLinkUrl, setLogoLinkUrl] = useState(data.settings.brand.logoLinkUrl);
|
||||
const [workspaceCreationPolicy, setWorkspaceCreationPolicy] = useState(
|
||||
data.settings.taskManager.workspaceCreationPolicy
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
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 hasChanges = normalizedLogoLinkUrl !== data.settings.brand.logoLinkUrl;
|
||||
const hasChanges =
|
||||
normalizedLogoLinkUrl !== data.settings.brand.logoLinkUrl ||
|
||||
workspaceCreationPolicy !== data.settings.taskManager.workspaceCreationPolicy;
|
||||
|
||||
return (
|
||||
<GlassSurface className="table-shell admin-settings-panel">
|
||||
|
|
@ -2423,7 +2451,12 @@ function MiscSection({
|
|||
type="button"
|
||||
icon={<Save size={16} />}
|
||||
disabled={!hasChanges}
|
||||
onClick={() => onUpdateSettings({ brand: { logoLinkUrl: normalizedLogoLinkUrl } })}
|
||||
onClick={() =>
|
||||
onUpdateSettings({
|
||||
brand: { logoLinkUrl: normalizedLogoLinkUrl },
|
||||
taskManager: { workspaceCreationPolicy },
|
||||
})
|
||||
}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
|
|
@ -2440,6 +2473,18 @@ function MiscSection({
|
|||
/>
|
||||
<small>Куда ведёт клик по логотипу NODE.DC. Можно указать относительный путь или полный URL.</small>
|
||||
</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>
|
||||
</GlassSurface>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue