| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- import { Select } from "@opencode-ai/ui/select"
- import { showToast } from "@opencode-ai/ui/toast"
- import { Component, For, createMemo, type JSX } from "solid-js"
- import { useGlobalSync } from "@/context/global-sync"
- import { useLanguage } from "@/context/language"
- type PermissionAction = "allow" | "ask" | "deny"
- type PermissionObject = Record<string, PermissionAction>
- type PermissionValue = PermissionAction | PermissionObject | string[] | undefined
- type PermissionMap = Record<string, PermissionValue>
- type PermissionItem = {
- id: string
- title: string
- description: string
- }
- const ACTIONS = [
- { value: "allow", label: "settings.permissions.action.allow" },
- { value: "ask", label: "settings.permissions.action.ask" },
- { value: "deny", label: "settings.permissions.action.deny" },
- ] as const
- const ITEMS = [
- {
- id: "read",
- title: "settings.permissions.tool.read.title",
- description: "settings.permissions.tool.read.description",
- },
- {
- id: "edit",
- title: "settings.permissions.tool.edit.title",
- description: "settings.permissions.tool.edit.description",
- },
- {
- id: "glob",
- title: "settings.permissions.tool.glob.title",
- description: "settings.permissions.tool.glob.description",
- },
- {
- id: "grep",
- title: "settings.permissions.tool.grep.title",
- description: "settings.permissions.tool.grep.description",
- },
- {
- id: "list",
- title: "settings.permissions.tool.list.title",
- description: "settings.permissions.tool.list.description",
- },
- {
- id: "bash",
- title: "settings.permissions.tool.bash.title",
- description: "settings.permissions.tool.bash.description",
- },
- {
- id: "task",
- title: "settings.permissions.tool.task.title",
- description: "settings.permissions.tool.task.description",
- },
- {
- id: "skill",
- title: "settings.permissions.tool.skill.title",
- description: "settings.permissions.tool.skill.description",
- },
- {
- id: "lsp",
- title: "settings.permissions.tool.lsp.title",
- description: "settings.permissions.tool.lsp.description",
- },
- {
- id: "todoread",
- title: "settings.permissions.tool.todoread.title",
- description: "settings.permissions.tool.todoread.description",
- },
- {
- id: "todowrite",
- title: "settings.permissions.tool.todowrite.title",
- description: "settings.permissions.tool.todowrite.description",
- },
- {
- id: "webfetch",
- title: "settings.permissions.tool.webfetch.title",
- description: "settings.permissions.tool.webfetch.description",
- },
- {
- id: "websearch",
- title: "settings.permissions.tool.websearch.title",
- description: "settings.permissions.tool.websearch.description",
- },
- {
- id: "codesearch",
- title: "settings.permissions.tool.codesearch.title",
- description: "settings.permissions.tool.codesearch.description",
- },
- {
- id: "external_directory",
- title: "settings.permissions.tool.external_directory.title",
- description: "settings.permissions.tool.external_directory.description",
- },
- {
- id: "doom_loop",
- title: "settings.permissions.tool.doom_loop.title",
- description: "settings.permissions.tool.doom_loop.description",
- },
- ] as const
- const VALID_ACTIONS = new Set<PermissionAction>(["allow", "ask", "deny"])
- function toMap(value: unknown): PermissionMap {
- if (value && typeof value === "object" && !Array.isArray(value)) return value as PermissionMap
- const action = getAction(value)
- if (action) return { "*": action }
- return {}
- }
- function getAction(value: unknown): PermissionAction | undefined {
- if (typeof value === "string" && VALID_ACTIONS.has(value as PermissionAction)) return value as PermissionAction
- return
- }
- function getRuleDefault(value: unknown): PermissionAction | undefined {
- const action = getAction(value)
- if (action) return action
- if (!value || typeof value !== "object" || Array.isArray(value)) return
- return getAction((value as Record<string, unknown>)["*"])
- }
- export const SettingsPermissions: Component = () => {
- const globalSync = useGlobalSync()
- const language = useLanguage()
- const actions = createMemo(
- (): Array<{ value: PermissionAction; label: string }> =>
- ACTIONS.map((action) => ({
- value: action.value,
- label: language.t(action.label),
- })),
- )
- const permission = createMemo(() => {
- return toMap(globalSync.data.config.permission)
- })
- const actionFor = (id: string): PermissionAction => {
- const value = permission()[id]
- const direct = getRuleDefault(value)
- if (direct) return direct
- const wildcard = getRuleDefault(permission()["*"])
- if (wildcard) return wildcard
- return "allow"
- }
- const setPermission = async (id: string, action: PermissionAction) => {
- const before = globalSync.data.config.permission
- const map = toMap(before)
- const existing = map[id]
- const nextValue =
- existing && typeof existing === "object" && !Array.isArray(existing) ? { ...existing, "*": action } : action
- globalSync.set("config", "permission", { ...map, [id]: nextValue })
- globalSync.updateConfig({ permission: { [id]: nextValue } }).catch((err: unknown) => {
- globalSync.set("config", "permission", before)
- const message = err instanceof Error ? err.message : String(err)
- showToast({ title: language.t("settings.permissions.toast.updateFailed.title"), description: message })
- })
- }
- return (
- <div class="flex flex-col h-full overflow-y-auto no-scrollbar">
- <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
- <div class="flex flex-col gap-1 px-4 py-8 sm:p-8 max-w-[720px]">
- <h2 class="text-16-medium text-text-strong">{language.t("settings.permissions.title")}</h2>
- <p class="text-14-regular text-text-weak">{language.t("settings.permissions.description")}</p>
- </div>
- </div>
- <div class="flex flex-col gap-6 px-4 py-6 sm:p-8 sm:pt-6 max-w-[720px]">
- <div class="flex flex-col gap-2">
- <h3 class="text-14-medium text-text-strong">{language.t("settings.permissions.section.tools")}</h3>
- <div class="border border-border-weak-base rounded-lg overflow-hidden">
- <For each={ITEMS}>
- {(item) => (
- <SettingsRow title={language.t(item.title)} description={language.t(item.description)}>
- <Select
- options={actions()}
- current={actions().find((o) => o.value === actionFor(item.id))}
- value={(o) => o.value}
- label={(o) => o.label}
- onSelect={(option) => option && setPermission(item.id, option.value)}
- variant="secondary"
- size="small"
- triggerVariant="settings"
- />
- </SettingsRow>
- )}
- </For>
- </div>
- </div>
- </div>
- </div>
- )
- }
- interface SettingsRowProps {
- title: string
- description: string
- children: JSX.Element
- }
- const SettingsRow: Component<SettingsRowProps> = (props) => {
- return (
- <div class="flex flex-wrap items-center justify-between gap-4 px-4 py-3 border-b border-border-weak-base last:border-none">
- <div class="flex flex-col gap-0.5 min-w-0">
- <span class="text-14-medium text-text-strong">{props.title}</span>
- <span class="text-12-regular text-text-weak">{props.description}</span>
- </div>
- <div class="flex-shrink-0">{props.children}</div>
- </div>
- )
- }
|