diff --git a/plane-src/apps/api/plane/app/urls/codex_agents.py b/plane-src/apps/api/plane/app/urls/codex_agents.py index a50ca66..e6d1edc 100644 --- a/plane-src/apps/api/plane/app/urls/codex_agents.py +++ b/plane-src/apps/api/plane/app/urls/codex_agents.py @@ -7,6 +7,7 @@ from django.urls import path from plane.app.views import ( CodexAgentDetailEndpoint, CodexAgentGrantListEndpoint, + CodexAgentGrantReplaceEndpoint, CodexAgentListEndpoint, CodexAgentRevokeEndpoint, CodexAgentSetupEndpoint, @@ -36,6 +37,11 @@ urlpatterns = [ CodexAgentGrantListEndpoint.as_view(), name="codex-agent-api-agent-grants", ), + path( + "workspaces//codex-agent-api/agents//grants/replace/", + CodexAgentGrantReplaceEndpoint.as_view(), + name="codex-agent-api-agent-grants-replace", + ), path( "workspaces//codex-agent-api/agents//tokens/", CodexAgentTokenListEndpoint.as_view(), diff --git a/plane-src/apps/api/plane/app/views/__init__.py b/plane-src/apps/api/plane/app/views/__init__.py index 781df5f..130a8ed 100644 --- a/plane-src/apps/api/plane/app/views/__init__.py +++ b/plane-src/apps/api/plane/app/views/__init__.py @@ -177,6 +177,7 @@ from .api import ApiTokenEndpoint from .codex_agents import ( CodexAgentDetailEndpoint, CodexAgentGrantListEndpoint, + CodexAgentGrantReplaceEndpoint, CodexAgentListEndpoint, CodexAgentRevokeEndpoint, CodexAgentSetupEndpoint, diff --git a/plane-src/apps/api/plane/app/views/codex_agents.py b/plane-src/apps/api/plane/app/views/codex_agents.py index 8f66603..32be6b3 100644 --- a/plane-src/apps/api/plane/app/views/codex_agents.py +++ b/plane-src/apps/api/plane/app/views/codex_agents.py @@ -139,6 +139,41 @@ def validate_project_in_workspace(workspace, project_id, user): return None +def validate_projects_in_workspace(workspace, project_ids, user): + if not isinstance(project_ids, list) or len(project_ids) == 0: + return Response( + { + "ok": False, + "error": "project_ids_required", + "message": "Select at least one project for Codex Agent grants.", + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + normalized_project_ids = [] + for project_id in project_ids: + project_id = str(project_id or "").strip() + if not project_id or project_id in normalized_project_ids: + continue + + project_error = validate_project_in_workspace(workspace, project_id, user) + if project_error is not None: + return project_error + normalized_project_ids.append(project_id) + + if not normalized_project_ids: + return Response( + { + "ok": False, + "error": "project_ids_required", + "message": "Select at least one project for Codex Agent grants.", + }, + status=status.HTTP_400_BAD_REQUEST, + ) + + return normalized_project_ids + + def gateway_request(method, path, payload=None): config, error_response = require_gateway_config() if error_response is not None: @@ -284,6 +319,34 @@ class CodexAgentGrantListEndpoint(CodexAgentEntitledEndpoint): return gateway_request("POST", f"/api/internal/v1/owners/{owner_path(request.user)}/agents/{agent_path(agent_id)}/grants", payload) +class CodexAgentGrantReplaceEndpoint(CodexAgentEntitledEndpoint): + @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") + def post(self, request, slug, agent_id): + entitlement_error = self.require_entitlement(request, slug) + if entitlement_error is not None: + return entitlement_error + + workspace, workspace_error = require_workspace(slug) + if workspace_error is not None: + return workspace_error + + project_ids_or_error = validate_projects_in_workspace(workspace, request.data.get("project_ids"), request.user) + if isinstance(project_ids_or_error, Response): + return project_ids_or_error + + payload = { + "workspace_slug": slug, + "project_ids": project_ids_or_error, + "scopes": request.data.get("scopes") or [], + "mode": request.data.get("mode") or "voluntary", + } + return gateway_request( + "POST", + f"/api/internal/v1/owners/{owner_path(request.user)}/agents/{agent_path(agent_id)}/grants/replace", + payload, + ) + + class CodexAgentTokenListEndpoint(CodexAgentEntitledEndpoint): @allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], level="WORKSPACE") def get(self, request, slug, agent_id): diff --git a/plane-src/apps/web/app/assets/favicon/apple-touch-icon.png b/plane-src/apps/web/app/assets/favicon/apple-touch-icon.png index a631267..5078c01 100644 Binary files a/plane-src/apps/web/app/assets/favicon/apple-touch-icon.png and b/plane-src/apps/web/app/assets/favicon/apple-touch-icon.png differ diff --git a/plane-src/apps/web/app/assets/favicon/favicon-16x16.png b/plane-src/apps/web/app/assets/favicon/favicon-16x16.png index af59ef0..970564a 100644 Binary files a/plane-src/apps/web/app/assets/favicon/favicon-16x16.png and b/plane-src/apps/web/app/assets/favicon/favicon-16x16.png differ diff --git a/plane-src/apps/web/app/assets/favicon/favicon-32x32.png b/plane-src/apps/web/app/assets/favicon/favicon-32x32.png index 16a1271..b2f01c5 100644 Binary files a/plane-src/apps/web/app/assets/favicon/favicon-32x32.png and b/plane-src/apps/web/app/assets/favicon/favicon-32x32.png differ diff --git a/plane-src/apps/web/app/assets/favicon/favicon.ico b/plane-src/apps/web/app/assets/favicon/favicon.ico index 613b1a3..973f3b2 100644 Binary files a/plane-src/apps/web/app/assets/favicon/favicon.ico and b/plane-src/apps/web/app/assets/favicon/favicon.ico differ diff --git a/plane-src/apps/web/app/assets/icons/icon-180x180.png b/plane-src/apps/web/app/assets/icons/icon-180x180.png index e7142bc..5078c01 100644 Binary files a/plane-src/apps/web/app/assets/icons/icon-180x180.png and b/plane-src/apps/web/app/assets/icons/icon-180x180.png differ diff --git a/plane-src/apps/web/app/assets/icons/icon-512x512.png b/plane-src/apps/web/app/assets/icons/icon-512x512.png index 4c070d0..a6f47b9 100644 Binary files a/plane-src/apps/web/app/assets/icons/icon-512x512.png and b/plane-src/apps/web/app/assets/icons/icon-512x512.png differ diff --git a/plane-src/apps/web/app/layout.tsx b/plane-src/apps/web/app/layout.tsx index 81d1109..7ce90d1 100644 --- a/plane-src/apps/web/app/layout.tsx +++ b/plane-src/apps/web/app/layout.tsx @@ -14,13 +14,6 @@ import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants"; // helpers import { cn } from "@plane/utils"; -// assets -import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url"; -import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url"; -import faviconIco from "@/app/assets/favicon/favicon.ico?url"; -import icon180 from "@/app/assets/icons/icon-180x180.png?url"; -import icon512 from "@/app/assets/icons/icon-512x512.png?url"; - // local import { AppProvider } from "./provider"; @@ -62,10 +55,8 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - - + - {/* Meta info for PWA */} @@ -73,9 +64,9 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - - - + + + diff --git a/plane-src/apps/web/app/root.tsx b/plane-src/apps/web/app/root.tsx index 8bff47e..7e6ac34 100644 --- a/plane-src/apps/web/app/root.tsx +++ b/plane-src/apps/web/app/root.tsx @@ -14,11 +14,6 @@ import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants"; import { cn } from "@plane/utils"; // types // assets -import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url"; -import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url"; -import faviconIco from "@/app/assets/favicon/favicon.ico?url"; -import icon180 from "@/app/assets/icons/icon-180x180.png?url"; -import icon512 from "@/app/assets/icons/icon-512x512.png?url"; import ogImage from "@/app/assets/og-image.png?url"; import globalStyles from "@/styles/globals.css?url"; import type { Route } from "./+types/root"; @@ -95,13 +90,11 @@ const designConfigStyle = { } as CSSProperties; export const links: LinksFunction = () => [ - { rel: "icon", type: "image/png", sizes: "32x32", href: favicon32 }, - { rel: "icon", type: "image/png", sizes: "16x16", href: favicon16 }, - { rel: "shortcut icon", href: faviconIco }, + { rel: "icon", type: "image/svg+xml", sizes: "any", href: "/favicon/icon-adaptive.svg?v=nodedc-adaptive-20260516" }, { rel: "manifest", href: "/site.webmanifest.json" }, - { rel: "apple-touch-icon", href: icon512 }, - { rel: "apple-touch-icon", sizes: "180x180", href: icon180 }, - { rel: "apple-touch-icon", sizes: "512x512", href: icon512 }, + { rel: "apple-touch-icon", href: "/apple-touch-icon.png" }, + { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" }, + { rel: "apple-touch-icon", sizes: "512x512", href: "/icons/icon-512x512.png" }, { rel: "manifest", href: "/manifest.json" }, { rel: "stylesheet", href: globalStyles }, { diff --git a/plane-src/apps/web/core/components/workspace/settings/codex-agent-api-settings.tsx b/plane-src/apps/web/core/components/workspace/settings/codex-agent-api-settings.tsx index d56c463..67cd119 100644 --- a/plane-src/apps/web/core/components/workspace/settings/codex-agent-api-settings.tsx +++ b/plane-src/apps/web/core/components/workspace/settings/codex-agent-api-settings.tsx @@ -6,7 +6,7 @@ import { type ChangeEvent, useMemo, useRef, useState } from "react"; import { observer } from "mobx-react"; -import { Bot, Check, Copy, KeyRound, Route, ShieldCheck } from "lucide-react"; +import { Bot, Check, ChevronDown, Copy, FolderKanban, KeyRound, Route, ShieldCheck } from "lucide-react"; import useSWR from "swr"; import { Button } from "@plane/propel/button"; import { TOAST_TYPE, setToast } from "@plane/propel/toast"; @@ -20,6 +20,7 @@ import { ProjectService } from "@/services/project/project.service"; import { WorkspaceCodexAgentService, type TCodexAgent, + type TCodexAgentGrant, type TCodexAgentSetupPacket, type TCodexAgentToken, } from "@/services/workspace-codex-agent.service"; @@ -59,10 +60,17 @@ type TProps = { type TAgentSetupCard = { agent: TCodexAgent; + grants: TCodexAgentGrant[]; setup?: TCodexAgentSetupPacket; tokens: TCodexAgentToken[]; }; +type TProjectAccessOption = { + id: string; + identifier?: string | null; + name: string; +}; + export const CodexAgentApiSettingsContent = observer(function CodexAgentApiSettingsContent(props: TProps) { const { showHeading = true, workspaceSlug } = props; const createAvatarInputRef = useRef(null); @@ -76,6 +84,9 @@ export const CodexAgentApiSettingsContent = observer(function CodexAgentApiSetti const [revealedTokens, setRevealedTokens] = useState>({}); const [updatingAgentIds, setUpdatingAgentIds] = useState>({}); const [creatingTokenAgentIds, setCreatingTokenAgentIds] = useState>({}); + const [openProjectAccessAgentId, setOpenProjectAccessAgentId] = useState(null); + const [projectGrantDrafts, setProjectGrantDrafts] = useState>({}); + const [savingProjectGrantAgentIds, setSavingProjectGrantAgentIds] = useState>({}); const { currentWorkspace } = useWorkspace(); const { data: nodedcWorkspacePolicy, isLoading } = useSWR( workspaceSlug ? `NODEDC_WORKSPACE_POLICY_${workspaceSlug}` : null, @@ -109,13 +120,15 @@ export const CodexAgentApiSettingsContent = observer(function CodexAgentApiSetti async () => Promise.all( activeAgents.map(async (agent) => { - const [tokensPayload, setupPayload] = await Promise.all([ + const [tokensPayload, setupPayload, grantsPayload] = await Promise.all([ codexAgentService.listTokens(workspaceSlug, agent.id), codexAgentService.getSetup(workspaceSlug, agent.id), + codexAgentService.listGrants(workspaceSlug, agent.id), ]); return { agent, + grants: grantsPayload.grants, setup: setupPayload.setup, tokens: tokensPayload.tokens.filter((token) => token.status === "active"), }; @@ -200,7 +213,7 @@ export const CodexAgentApiSettingsContent = observer(function CodexAgentApiSetti display_name: displayName, avatar_url: newAgentAvatarUrl, }); - await codexAgentService.upsertGrant(workspaceSlug, createResponse.agent.id, { + const grantResponse = await codexAgentService.upsertGrant(workspaceSlug, createResponse.agent.id, { project_id: effectiveSelectedProjectId, scopes: TASK_AUTHOR_SCOPES, mode: "voluntary", @@ -213,7 +226,9 @@ export const CodexAgentApiSettingsContent = observer(function CodexAgentApiSetti [tokenResponse.token_record.id]: tokenResponse.token, })); setCreatedSetupCards((currentCards) => - upsertSetupCardToken(currentCards, createResponse.agent, tokenResponse.token_record, tokenResponse.setup) + upsertSetupCardToken(currentCards, createResponse.agent, tokenResponse.token_record, tokenResponse.setup, [ + grantResponse.grant, + ]) ); await mutateCodexAgents(); await mutateSetupCards(); @@ -243,8 +258,9 @@ export const CodexAgentApiSettingsContent = observer(function CodexAgentApiSetti ...currentTokens, [tokenResponse.token_record.id]: tokenResponse.token, })); + const currentGrants = setupCards.find((card) => card.agent.id === agent.id)?.grants ?? []; setCreatedSetupCards((currentCards) => - upsertSetupCardToken(currentCards, agent, tokenResponse.token_record, tokenResponse.setup) + upsertSetupCardToken(currentCards, agent, tokenResponse.token_record, tokenResponse.setup, currentGrants) ); await mutateSetupCards(); setToast({ @@ -263,6 +279,70 @@ export const CodexAgentApiSettingsContent = observer(function CodexAgentApiSetti } }; + const handleToggleProjectGrant = (agentId: string, currentProjectIds: string[], projectId: string) => { + setProjectGrantDrafts((currentDrafts) => { + const currentDraftProjectIds = currentDrafts[agentId] ?? currentProjectIds; + const nextProjectIds = currentDraftProjectIds.includes(projectId) + ? currentDraftProjectIds.filter((currentProjectId) => currentProjectId !== projectId) + : [...currentDraftProjectIds, projectId]; + + return { + ...currentDrafts, + [agentId]: nextProjectIds, + }; + }); + }; + + const handleSaveProjectAccess = async (agent: TCodexAgent, selectedProjectIds: string[]) => { + const projectIds = [...new Set(selectedProjectIds.filter(Boolean))]; + if (projectIds.length === 0) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Выберите project", + message: "У агента должен быть доступ хотя бы к одному project в workspace.", + }); + return; + } + + setSavingProjectGrantAgentIds((current) => ({ ...current, [agent.id]: true })); + try { + const grantsResponse = await codexAgentService.replaceProjectGrants(workspaceSlug, agent.id, { + project_ids: projectIds, + scopes: TASK_AUTHOR_SCOPES, + mode: "voluntary", + }); + setCreatedSetupCards((currentCards) => + currentCards.map((card) => + card.agent.id === agent.id + ? { + ...card, + grants: mergeAgentGrants(card.grants, grantsResponse.grants, workspaceSlug), + } + : card + ) + ); + setProjectGrantDrafts((currentDrafts) => { + const nextDrafts = { ...currentDrafts }; + delete nextDrafts[agent.id]; + return nextDrafts; + }); + await mutateSetupCards(); + setToast({ + type: TOAST_TYPE.SUCCESS, + title: "Доступы Codex обновлены", + message: "Agent token теперь работает только с выбранными projects.", + }); + } catch (error: any) { + setToast({ + type: TOAST_TYPE.ERROR, + title: "Не удалось обновить доступы", + message: error?.message ?? error?.error ?? "Проверьте project membership и Gateway.", + }); + } finally { + setSavingProjectGrantAgentIds((current) => ({ ...current, [agent.id]: false })); + } + }; + const handleSaveAgentName = async (agent: TCodexAgent) => { const displayName = getAgentDraftName(agentDraftNames, agent).trim(); if (!displayName) return; @@ -431,6 +511,11 @@ export const CodexAgentApiSettingsContent = observer(function CodexAgentApiSetti const isAgentDirty = draftName.trim() !== agent.display_name; const setupCard = setupCards.find((card) => card.agent.id === agent.id); const agentTokens = setupCard?.tokens ?? []; + const agentGrants = setupCard?.grants ?? []; + const currentProjectIds = getGrantedProjectIds(agentGrants, workspaceSlug); + const draftProjectIds = projectGrantDrafts[agent.id] ?? currentProjectIds; + const isProjectAccessOpen = openProjectAccessAgentId === agent.id; + const isSavingProjectAccess = savingProjectGrantAgentIds[agent.id] === true; return (
@@ -513,9 +598,7 @@ export const CodexAgentApiSettingsContent = observer(function CodexAgentApiSetti {areSetupCardsLoading && agentTokens.length === 0 ? ( -
- Загрузка токена... -
+
Загрузка токена...
) : agentTokens.length > 0 ? (
{agentTokens.map((token) => { @@ -550,6 +633,29 @@ export const CodexAgentApiSettingsContent = observer(function CodexAgentApiSetti Токен ещё не выпущен. Нажмите «Новый токен», чтобы получить доступ для локального Codex.
)} + + void handleSaveProjectAccess(agent, draftProjectIds)} + onToggleOpen={() => { + setOpenProjectAccessAgentId(isProjectAccessOpen ? null : agent.id); + setProjectGrantDrafts((currentDrafts) => + currentDrafts[agent.id] + ? currentDrafts + : { + ...currentDrafts, + [agent.id]: currentProjectIds, + } + ); + }} + onToggleProject={(projectId) => + handleToggleProjectGrant(agent.id, currentProjectIds, projectId) + } + />
); }) @@ -600,11 +706,11 @@ function CodexConnectionGuide(props: TCodexConnectionGuideProps) {
1. Найдите config.toml

Windows: откройте файл через проводник или VS Code.

- + C:\Users\имя-пользователя\.codex\config.toml

macOS / Linux:

- ~/.codex/config.toml + ~/.codex/config.toml

Если файла нет — создайте его.

@@ -614,7 +720,7 @@ function CodexConnectionGuide(props: TCodexConnectionGuideProps) { Создайте пользовательскую переменную окружения {CODEX_TOKEN_ENV_VAR}, заменив токен из примера на уникальный токен конкретного агента.

- + {CODEX_TOKEN_ENV_VAR}=ndcag_... @@ -776,6 +882,109 @@ function AgentAvatarButton(props: { ); } +type TAgentProjectAccessPanelProps = { + currentProjectIds: string[]; + draftProjectIds: string[]; + isOpen: boolean; + isSaving: boolean; + onSave: () => void; + onToggleOpen: () => void; + onToggleProject: (projectId: string) => void; + projects: TProjectAccessOption[]; +}; + +function AgentProjectAccessPanel(props: TAgentProjectAccessPanelProps) { + const isDirty = !areProjectSelectionsEqual(props.currentProjectIds, props.draftProjectIds); + const selectedCount = props.draftProjectIds.length; + const summary = + selectedCount === 0 + ? "Нет выбранных projects" + : selectedCount === 1 + ? "1 project выбран" + : `${selectedCount} projects выбрано`; + + return ( +
+
+
+ + + +
+
Доступы к проектам
+
+ Выберите projects, куда этот agent token может читать и писать карточки. +
+
+
+ +
+ + {props.isOpen && ( +
+ {props.projects.length > 0 ? ( +
+ {props.projects.map((project) => { + const isChecked = props.draftProjectIds.includes(project.id); + + return ( + + ); + })} +
+ ) : ( +
В workspace нет доступных projects.
+ )} + +
+
+ Сохранение заменяет grants текущего workspace: снятые галочки сразу отзывают доступ. +
+ +
+
+ )} +
+ ); +} + function mergeSetupCards(persistedCards: TAgentSetupCard[], createdCards: TAgentSetupCard[]): TAgentSetupCard[] { const cardsByAgentId = new Map(); @@ -792,6 +1001,7 @@ function mergeSetupCards(persistedCards: TAgentSetupCard[], createdCards: TAgent cardsByAgentId.set(card.agent.id, { agent: persistedCard.agent, + grants: mergeAgentGrants(persistedCard.grants, card.grants), setup: persistedCard.setup ?? card.setup, tokens: mergeTokens(persistedCard.tokens, card.tokens), }); @@ -813,17 +1023,19 @@ function upsertSetupCardToken( cards: TAgentSetupCard[], agent: TCodexAgent, token: TCodexAgentToken, - setup?: TCodexAgentSetupPacket + setup?: TCodexAgentSetupPacket, + grants: TCodexAgentGrant[] = [] ): TAgentSetupCard[] { const existingCard = cards.find((card) => card.agent.id === agent.id); if (!existingCard) { - return [{ agent, setup, tokens: [token] }, ...cards]; + return [{ agent, grants, setup, tokens: [token] }, ...cards]; } return cards.map((card) => card.agent.id === agent.id ? { agent, + grants: mergeAgentGrants(card.grants, grants), setup: setup ?? card.setup, tokens: mergeTokens([token], card.tokens), } @@ -831,6 +1043,51 @@ function upsertSetupCardToken( ); } +function getGrantedProjectIds(grants: TCodexAgentGrant[], workspaceSlug: string): string[] { + return [ + ...new Set( + grants + .filter((grant) => grant.workspace_slug === workspaceSlug && grant.project_id) + .map((grant) => String(grant.project_id)) + ), + ].sort(); +} + +function mergeAgentGrants( + currentGrants: TCodexAgentGrant[], + nextGrants: TCodexAgentGrant[], + workspaceSlug?: string +): TCodexAgentGrant[] { + const grantsByKey = new Map(); + + for (const grant of currentGrants) { + if (workspaceSlug && grant.workspace_slug === workspaceSlug) continue; + grantsByKey.set(buildGrantKey(grant), grant); + } + + for (const grant of nextGrants) { + grantsByKey.set(buildGrantKey(grant), grant); + } + + return Array.from(grantsByKey.values()); +} + +function buildGrantKey(grant: TCodexAgentGrant): string { + return `${grant.workspace_slug}:${grant.project_id ?? "*"}`; +} + +function areProjectSelectionsEqual(leftProjectIds: string[], rightProjectIds: string[]): boolean { + const leftSet = new Set(leftProjectIds); + const rightSet = new Set(rightProjectIds); + if (leftSet.size !== rightSet.size) return false; + + for (const projectId of leftSet) { + if (!rightSet.has(projectId)) return false; + } + + return true; +} + function readAvatarDataUrl(file: File): Promise { if (!file.type.startsWith("image/")) { return Promise.reject(new Error("Поддерживаются только изображения PNG, JPG, WEBP или GIF.")); diff --git a/plane-src/apps/web/core/services/workspace-codex-agent.service.ts b/plane-src/apps/web/core/services/workspace-codex-agent.service.ts index e9a298d..ac346fb 100644 --- a/plane-src/apps/web/core/services/workspace-codex-agent.service.ts +++ b/plane-src/apps/web/core/services/workspace-codex-agent.service.ts @@ -72,6 +72,11 @@ export type TCodexAgentTokenListResponse = { tokens: TCodexAgentToken[]; }; +export type TCodexAgentGrantListResponse = { + ok: boolean; + grants: TCodexAgentGrant[]; +}; + export type TCodexAgentSetupResponse = { ok: boolean; setup?: TCodexAgentSetupPacket; @@ -129,6 +134,30 @@ export class WorkspaceCodexAgentService extends APIService { }); } + async listGrants(workspaceSlug: string, agentId: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/codex-agent-api/agents/${agentId}/grants/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async replaceProjectGrants( + workspaceSlug: string, + agentId: string, + data: { + mode?: TCodexAgentGrantMode; + project_ids: string[]; + scopes: string[]; + } + ): Promise { + return this.post(`/api/workspaces/${workspaceSlug}/codex-agent-api/agents/${agentId}/grants/replace/`, data) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + async createToken( workspaceSlug: string, agentId: string, diff --git a/plane-src/apps/web/manifest.json b/plane-src/apps/web/manifest.json index d081c52..b6469af 100644 --- a/plane-src/apps/web/manifest.json +++ b/plane-src/apps/web/manifest.json @@ -1,7 +1,7 @@ { - "theme_color": "#3579f6", - "background_color": "#ffffff", - "display": "standalone", + "theme_color": "#eeeff4", + "background_color": "#eeeff4", + "display": "browser", "scope": "/", "start_url": "/", "name": "NODE.DC | Self-hosted task management workspace.", @@ -9,22 +9,17 @@ "description": "NODE.DC streamlines task management, projects, and internal workflows.", "icons": [ { - "src": "/icon-192x192.png", + "src": "/icons/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { - "src": "/icon-256x256.png", - "sizes": "256x256", + "src": "/icons/icon-348x348.png", + "sizes": "348x348", "type": "image/png" }, { - "src": "/icon-384x384.png", - "sizes": "384x384", - "type": "image/png" - }, - { - "src": "/icon-512x512.png", + "src": "/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png" } diff --git a/plane-src/apps/web/public/apple-touch-icon.png b/plane-src/apps/web/public/apple-touch-icon.png new file mode 100644 index 0000000..5078c01 Binary files /dev/null and b/plane-src/apps/web/public/apple-touch-icon.png differ diff --git a/plane-src/apps/web/public/favicon.ico b/plane-src/apps/web/public/favicon.ico new file mode 100644 index 0000000..973f3b2 Binary files /dev/null and b/plane-src/apps/web/public/favicon.ico differ diff --git a/plane-src/apps/web/public/favicon.svg b/plane-src/apps/web/public/favicon.svg new file mode 100644 index 0000000..fee636f --- /dev/null +++ b/plane-src/apps/web/public/favicon.svg @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/plane-src/apps/web/public/favicon/android-chrome-192x192.png b/plane-src/apps/web/public/favicon/android-chrome-192x192.png index 4a005e5..d553a58 100644 Binary files a/plane-src/apps/web/public/favicon/android-chrome-192x192.png and b/plane-src/apps/web/public/favicon/android-chrome-192x192.png differ diff --git a/plane-src/apps/web/public/favicon/android-chrome-512x512.png b/plane-src/apps/web/public/favicon/android-chrome-512x512.png index 27fafe8..a6f47b9 100644 Binary files a/plane-src/apps/web/public/favicon/android-chrome-512x512.png and b/plane-src/apps/web/public/favicon/android-chrome-512x512.png differ diff --git a/plane-src/apps/web/public/favicon/apple-touch-icon.png b/plane-src/apps/web/public/favicon/apple-touch-icon.png new file mode 100644 index 0000000..5078c01 Binary files /dev/null and b/plane-src/apps/web/public/favicon/apple-touch-icon.png differ diff --git a/plane-src/apps/web/public/favicon/favicon.ico b/plane-src/apps/web/public/favicon/favicon.ico new file mode 100644 index 0000000..973f3b2 Binary files /dev/null and b/plane-src/apps/web/public/favicon/favicon.ico differ diff --git a/plane-src/apps/web/public/favicon/icon-adaptive.svg b/plane-src/apps/web/public/favicon/icon-adaptive.svg new file mode 100644 index 0000000..fee636f --- /dev/null +++ b/plane-src/apps/web/public/favicon/icon-adaptive.svg @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/plane-src/apps/web/public/favicon/site.webmanifest b/plane-src/apps/web/public/favicon/site.webmanifest index 1d41057..17ac181 100644 --- a/plane-src/apps/web/public/favicon/site.webmanifest +++ b/plane-src/apps/web/public/favicon/site.webmanifest @@ -1,11 +1,15 @@ { - "name": "", - "short_name": "", + "name": "NODE.DC", + "short_name": "NODE.DC", + "theme_color": "#eeeff4", + "background_color": "#eeeff4", + "display": "browser", + "scope": "/", + "start_url": "/", "icons": [ + { "src": "/favicon/icon-adaptive.svg", "sizes": "any", "type": "image/svg+xml" }, { "src": "/favicon/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, - { "src": "/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } - ], - "theme_color": "#ffffff", - "background_color": "#ffffff", - "display": "standalone" + { "src": "/favicon/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }, + { "src": "/favicon/apple-touch-icon.png", "sizes": "180x180", "type": "image/png" } + ] } diff --git a/plane-src/apps/web/public/icons/icon-192x192.png b/plane-src/apps/web/public/icons/icon-192x192.png index 1654263..d553a58 100644 Binary files a/plane-src/apps/web/public/icons/icon-192x192.png and b/plane-src/apps/web/public/icons/icon-192x192.png differ diff --git a/plane-src/apps/web/public/icons/icon-348x348.png b/plane-src/apps/web/public/icons/icon-348x348.png index c457397..42c8695 100644 Binary files a/plane-src/apps/web/public/icons/icon-348x348.png and b/plane-src/apps/web/public/icons/icon-348x348.png differ diff --git a/plane-src/apps/web/public/icons/icon-512x512.png b/plane-src/apps/web/public/icons/icon-512x512.png index 4c070d0..a6f47b9 100644 Binary files a/plane-src/apps/web/public/icons/icon-512x512.png and b/plane-src/apps/web/public/icons/icon-512x512.png differ diff --git a/plane-src/apps/web/public/manifest.json b/plane-src/apps/web/public/manifest.json index dc76803..00ffc5a 100644 --- a/plane-src/apps/web/public/manifest.json +++ b/plane-src/apps/web/public/manifest.json @@ -19,8 +19,8 @@ "type": "image/png" } ], - "theme_color": "#FFFFFF", - "background_color": "#FFFFFF", + "theme_color": "#eeeff4", + "background_color": "#eeeff4", "start_url": "/", "display": "standalone", "orientation": "portrait" diff --git a/plane-src/apps/web/public/site.webmanifest.json b/plane-src/apps/web/public/site.webmanifest.json index e870fba..543c39e 100644 --- a/plane-src/apps/web/public/site.webmanifest.json +++ b/plane-src/apps/web/public/site.webmanifest.json @@ -2,12 +2,14 @@ "name": "NODE.DC", "short_name": "NODE.DC", "description": "NODE.DC helps you manage work items, projects, and operational workflows.", - "start_url": ".", - "display": "standalone", - "background_color": "#f9fafb", - "theme_color": "#3f76ff", + "start_url": "/", + "display": "browser", + "background_color": "#eeeff4", + "theme_color": "#eeeff4", "icons": [ - { "src": "/plane-logos/plane-mobile-pwa.png", "sizes": "192x192", "type": "image/png" }, - { "src": "/plane-logos/plane-mobile-pwa.png", "sizes": "512x512", "type": "image/png" } + { "src": "/favicon/icon-adaptive.svg", "sizes": "any", "type": "image/svg+xml" }, + { "src": "/icons/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png" }, + { "src": "/favicon/apple-touch-icon.png", "sizes": "180x180", "type": "image/png" } ] } diff --git a/plane-src/apps/web/styles/globals.css b/plane-src/apps/web/styles/globals.css index da8d5ba..3cd9239 100644 --- a/plane-src/apps/web/styles/globals.css +++ b/plane-src/apps/web/styles/globals.css @@ -2234,6 +2234,30 @@ rgba(255, 255, 255, 0.042) !important; } + .nodedc-project-grants-surface { + background: rgba(0, 0, 0, 0.1) !important; + border: 0 !important; + outline: none !important; + box-shadow: none !important; + } + + .nodedc-project-grants-row, + .nodedc-project-grants-row:hover, + .nodedc-project-grants-row:focus, + .nodedc-project-grants-row:focus-visible, + .nodedc-project-grants-row:active { + background: transparent !important; + border: 0 !important; + outline: none !important; + box-shadow: none !important; + } + + .nodedc-project-grants-check { + border: 0 !important; + outline: none !important; + box-shadow: none !important; + } + .nodedc-settings-primary-button { min-height: 2.75rem; border: 0 !important;