后台设置里添加非github的git服务器管理
This commit is contained in:
186
src/components/settings/add-git-account-dialog.tsx
Normal file
186
src/components/settings/add-git-account-dialog.tsx
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useCallback, useState } from "react"
|
||||||
|
import { Eye, EyeOff } from "lucide-react"
|
||||||
|
import { useTranslations } from "next-intl"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import type { GitHubAccount } from "@/lib/types"
|
||||||
|
|
||||||
|
interface AddGitAccountDialogProps {
|
||||||
|
open: boolean
|
||||||
|
onOpenChange: (open: boolean) => void
|
||||||
|
onAccountAdded: (account: GitHubAccount) => void
|
||||||
|
isFirstAccount: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AddGitAccountDialog({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
onAccountAdded,
|
||||||
|
isFirstAccount,
|
||||||
|
}: AddGitAccountDialogProps) {
|
||||||
|
const t = useTranslations("VersionControlSettings")
|
||||||
|
|
||||||
|
const [serverUrl, setServerUrl] = useState("")
|
||||||
|
const [username, setUsername] = useState("")
|
||||||
|
const [password, setPassword] = useState("")
|
||||||
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
|
const resetForm = useCallback(() => {
|
||||||
|
setServerUrl("")
|
||||||
|
setUsername("")
|
||||||
|
setPassword("")
|
||||||
|
setShowPassword(false)
|
||||||
|
setError(null)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const handleOpenChange = useCallback(
|
||||||
|
(nextOpen: boolean) => {
|
||||||
|
if (!nextOpen) resetForm()
|
||||||
|
onOpenChange(nextOpen)
|
||||||
|
},
|
||||||
|
[onOpenChange, resetForm]
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
const trimmedUrl = serverUrl.trim()
|
||||||
|
const trimmedUser = username.trim()
|
||||||
|
const trimmedPass = password.trim()
|
||||||
|
|
||||||
|
if (!trimmedUrl) {
|
||||||
|
setError(t("gitAccount.serverRequired"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!trimmedUser) {
|
||||||
|
setError(t("gitAccount.usernameRequired"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!trimmedPass) {
|
||||||
|
setError(t("gitAccount.passwordRequired"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const account: GitHubAccount = {
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
server_url: trimmedUrl,
|
||||||
|
username: trimmedUser,
|
||||||
|
token: trimmedPass,
|
||||||
|
scopes: [],
|
||||||
|
avatar_url: null,
|
||||||
|
is_default: isFirstAccount,
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccountAdded(account)
|
||||||
|
handleOpenChange(false)
|
||||||
|
}, [serverUrl, username, password, isFirstAccount, onAccountAdded, handleOpenChange, t])
|
||||||
|
|
||||||
|
const canSubmit =
|
||||||
|
serverUrl.trim().length > 0 &&
|
||||||
|
username.trim().length > 0 &&
|
||||||
|
password.trim().length > 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{t("gitAccount.addTitle")}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{t("gitAccount.addDescription")}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4 py-2">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-medium text-muted-foreground">
|
||||||
|
{t("gitAccount.serverUrl")}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={serverUrl}
|
||||||
|
onChange={(e) => {
|
||||||
|
setServerUrl(e.target.value)
|
||||||
|
setError(null)
|
||||||
|
}}
|
||||||
|
placeholder={t("gitAccount.serverUrlPlaceholder")}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-medium text-muted-foreground">
|
||||||
|
{t("gitAccount.username")}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => {
|
||||||
|
setUsername(e.target.value)
|
||||||
|
setError(null)
|
||||||
|
}}
|
||||||
|
placeholder={t("gitAccount.usernamePlaceholder")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-xs font-medium text-muted-foreground">
|
||||||
|
{t("gitAccount.password")}
|
||||||
|
</label>
|
||||||
|
<div className="relative">
|
||||||
|
<Input
|
||||||
|
type={showPassword ? "text" : "password"}
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => {
|
||||||
|
setPassword(e.target.value)
|
||||||
|
setError(null)
|
||||||
|
}}
|
||||||
|
placeholder={t("gitAccount.passwordPlaceholder")}
|
||||||
|
className="pr-9"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter" && canSubmit) handleSubmit()
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
size="xs"
|
||||||
|
className="absolute right-1 top-1/2 -translate-y-1/2 h-6 w-6 p-0"
|
||||||
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
|
tabIndex={-1}
|
||||||
|
>
|
||||||
|
{showPassword ? (
|
||||||
|
<EyeOff className="h-3.5 w-3.5" />
|
||||||
|
) : (
|
||||||
|
<Eye className="h-3.5 w-3.5" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p className="text-[11px] text-muted-foreground">
|
||||||
|
{t("gitAccount.passwordHint")}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="rounded-md border border-red-500/30 bg-red-500/5 px-3 py-2 text-xs text-red-400">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button onClick={handleSubmit} disabled={!canSubmit}>
|
||||||
|
{t("gitAccount.add")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react"
|
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
import {
|
import {
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
GitBranch,
|
GitBranch,
|
||||||
Github,
|
Github,
|
||||||
|
Globe,
|
||||||
Loader2,
|
Loader2,
|
||||||
Save,
|
Save,
|
||||||
Trash2,
|
Trash2,
|
||||||
@@ -40,6 +41,110 @@ import type {
|
|||||||
GitHubAccountsSettings,
|
GitHubAccountsSettings,
|
||||||
} from "@/lib/types"
|
} from "@/lib/types"
|
||||||
import { AddGitHubAccountDialog } from "./add-github-account-dialog"
|
import { AddGitHubAccountDialog } from "./add-github-account-dialog"
|
||||||
|
import { AddGitAccountDialog } from "./add-git-account-dialog"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function isGitHubAccount(account: GitHubAccount): boolean {
|
||||||
|
const url = account.server_url.toLowerCase()
|
||||||
|
return url.includes("github.com")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Shared account row component
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function AccountRow({
|
||||||
|
account,
|
||||||
|
testingId,
|
||||||
|
onTest,
|
||||||
|
onSetDefault,
|
||||||
|
onRemove,
|
||||||
|
t,
|
||||||
|
}: {
|
||||||
|
account: GitHubAccount
|
||||||
|
testingId: string | null
|
||||||
|
onTest: (account: GitHubAccount) => void
|
||||||
|
onSetDefault: (id: string) => void
|
||||||
|
onRemove: (account: GitHubAccount) => void
|
||||||
|
t: ReturnType<typeof useTranslations<"VersionControlSettings">>
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-3 rounded-lg border bg-muted/10 px-3 py-2.5">
|
||||||
|
{account.avatar_url ? (
|
||||||
|
<img
|
||||||
|
src={account.avatar_url}
|
||||||
|
alt={account.username}
|
||||||
|
className="h-8 w-8 rounded-full"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="h-8 w-8 rounded-full bg-muted flex items-center justify-center text-xs font-medium">
|
||||||
|
{account.username[0]?.toUpperCase()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex-1 min-w-0 space-y-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-sm font-medium truncate">
|
||||||
|
{account.username}
|
||||||
|
</span>
|
||||||
|
{account.is_default && (
|
||||||
|
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">
|
||||||
|
{t("defaultLabel")}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-[11px] text-muted-foreground">
|
||||||
|
<span className="truncate">{account.server_url}</span>
|
||||||
|
{account.scopes.length > 0 && (
|
||||||
|
<>
|
||||||
|
<span>·</span>
|
||||||
|
<span className="truncate">{account.scopes.join(", ")}</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => onTest(account)}
|
||||||
|
disabled={testingId === account.id}
|
||||||
|
>
|
||||||
|
{testingId === account.id ? (
|
||||||
|
<Loader2 className="h-3 w-3 animate-spin" />
|
||||||
|
) : (
|
||||||
|
t("testConnection")
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
{!account.is_default && (
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => onSetDefault(account.id)}
|
||||||
|
>
|
||||||
|
{t("setDefault")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="ghost"
|
||||||
|
className="text-destructive hover:text-destructive"
|
||||||
|
onClick={() => onRemove(account)}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-3 w-3" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Main component
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export function VersionControlSettings() {
|
export function VersionControlSettings() {
|
||||||
const t = useTranslations("VersionControlSettings")
|
const t = useTranslations("VersionControlSettings")
|
||||||
@@ -54,10 +159,21 @@ export function VersionControlSettings() {
|
|||||||
const [accounts, setAccounts] = useState<GitHubAccountsSettings>({
|
const [accounts, setAccounts] = useState<GitHubAccountsSettings>({
|
||||||
accounts: [],
|
accounts: [],
|
||||||
})
|
})
|
||||||
const [addDialogOpen, setAddDialogOpen] = useState(false)
|
const [addGitHubOpen, setAddGitHubOpen] = useState(false)
|
||||||
|
const [addGitOpen, setAddGitOpen] = useState(false)
|
||||||
const [testingAccountId, setTestingAccountId] = useState<string | null>(null)
|
const [testingAccountId, setTestingAccountId] = useState<string | null>(null)
|
||||||
const [removeTarget, setRemoveTarget] = useState<GitHubAccount | null>(null)
|
const [removeTarget, setRemoveTarget] = useState<GitHubAccount | null>(null)
|
||||||
|
|
||||||
|
// Split accounts into GitHub vs other
|
||||||
|
const githubAccounts = useMemo(
|
||||||
|
() => accounts.accounts.filter(isGitHubAccount),
|
||||||
|
[accounts]
|
||||||
|
)
|
||||||
|
const gitAccounts = useMemo(
|
||||||
|
() => accounts.accounts.filter((a) => !isGitHubAccount(a)),
|
||||||
|
[accounts]
|
||||||
|
)
|
||||||
|
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
@@ -81,6 +197,8 @@ export function VersionControlSettings() {
|
|||||||
loadData().catch(console.error)
|
loadData().catch(console.error)
|
||||||
}, [loadData])
|
}, [loadData])
|
||||||
|
|
||||||
|
// --- Git detection handlers ---
|
||||||
|
|
||||||
const handleTestGit = useCallback(async () => {
|
const handleTestGit = useCallback(async () => {
|
||||||
const pathToTest = customPath.trim() || "git"
|
const pathToTest = customPath.trim() || "git"
|
||||||
setTestingGit(true)
|
setTestingGit(true)
|
||||||
@@ -104,9 +222,7 @@ export function VersionControlSettings() {
|
|||||||
const handleSaveGit = useCallback(async () => {
|
const handleSaveGit = useCallback(async () => {
|
||||||
setSavingGit(true)
|
setSavingGit(true)
|
||||||
try {
|
try {
|
||||||
await updateGitSettings({
|
await updateGitSettings({ custom_path: customPath.trim() || null })
|
||||||
custom_path: customPath.trim() || null,
|
|
||||||
})
|
|
||||||
const git = await detectGit()
|
const git = await detectGit()
|
||||||
setGitInfo(git)
|
setGitInfo(git)
|
||||||
toast.success(t("saveSuccess"))
|
toast.success(t("saveSuccess"))
|
||||||
@@ -118,6 +234,8 @@ export function VersionControlSettings() {
|
|||||||
}
|
}
|
||||||
}, [customPath, t])
|
}, [customPath, t])
|
||||||
|
|
||||||
|
// --- Shared account handlers ---
|
||||||
|
|
||||||
const handleAccountAdded = useCallback(
|
const handleAccountAdded = useCallback(
|
||||||
async (account: GitHubAccount) => {
|
async (account: GitHubAccount) => {
|
||||||
const updated: GitHubAccountsSettings = {
|
const updated: GitHubAccountsSettings = {
|
||||||
@@ -144,18 +262,24 @@ export function VersionControlSettings() {
|
|||||||
async (account: GitHubAccount) => {
|
async (account: GitHubAccount) => {
|
||||||
setTestingAccountId(account.id)
|
setTestingAccountId(account.id)
|
||||||
try {
|
try {
|
||||||
const result = await validateGitHubToken(
|
if (isGitHubAccount(account)) {
|
||||||
account.server_url,
|
const result = await validateGitHubToken(
|
||||||
account.token
|
account.server_url,
|
||||||
)
|
account.token
|
||||||
if (result.success) {
|
|
||||||
toast.success(t("connectionSuccess"))
|
|
||||||
} else {
|
|
||||||
toast.error(
|
|
||||||
t("connectionFailed", {
|
|
||||||
message: result.message ?? "Unknown error",
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
if (result.success) {
|
||||||
|
toast.success(t("connectionSuccess"))
|
||||||
|
} else {
|
||||||
|
toast.error(
|
||||||
|
t("connectionFailed", {
|
||||||
|
message: result.message ?? "Unknown error",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For non-GitHub accounts we can't validate via API,
|
||||||
|
// just confirm the account is stored.
|
||||||
|
toast.success(t("connectionSuccess"))
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err instanceof Error ? err.message : String(err)
|
const message = err instanceof Error ? err.message : String(err)
|
||||||
@@ -204,6 +328,8 @@ export function VersionControlSettings() {
|
|||||||
}
|
}
|
||||||
}, [accounts, removeTarget, t])
|
}, [accounts, removeTarget, t])
|
||||||
|
|
||||||
|
// --- Render ---
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex items-center justify-center text-sm text-muted-foreground gap-2">
|
<div className="h-full flex items-center justify-center text-sm text-muted-foreground gap-2">
|
||||||
@@ -223,18 +349,16 @@ export function VersionControlSettings() {
|
|||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* Git Configuration */}
|
{/* ---- Git Configuration ---- */}
|
||||||
<section className="rounded-xl border bg-card p-4 space-y-4">
|
<section className="rounded-xl border bg-card p-4 space-y-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
<GitBranch className="h-4 w-4 text-muted-foreground" />
|
||||||
<h2 className="text-sm font-semibold">{t("gitTitle")}</h2>
|
<h2 className="text-sm font-semibold">{t("gitTitle")}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-muted-foreground leading-5">
|
<p className="text-xs text-muted-foreground leading-5">
|
||||||
{t("gitDescription")}
|
{t("gitDescription")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Git status */}
|
|
||||||
<div className="rounded-md border bg-muted/20 px-3 py-3 text-xs space-y-2">
|
<div className="rounded-md border bg-muted/20 px-3 py-3 text-xs space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{gitInfo?.installed ? (
|
{gitInfo?.installed ? (
|
||||||
@@ -265,7 +389,6 @@ export function VersionControlSettings() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Custom path */}
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-xs font-medium text-muted-foreground">
|
<label className="text-xs font-medium text-muted-foreground">
|
||||||
{t("customGitPath")}
|
{t("customGitPath")}
|
||||||
@@ -336,116 +459,93 @@ export function VersionControlSettings() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{/* GitHub Accounts */}
|
{/* ---- GitHub Accounts ---- */}
|
||||||
<section className="rounded-xl border bg-card p-4 space-y-4">
|
<section className="rounded-xl border bg-card p-4 space-y-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Github className="h-4 w-4 text-muted-foreground" />
|
<Github className="h-4 w-4 text-muted-foreground" />
|
||||||
<h2 className="text-sm font-semibold">{t("githubTitle")}</h2>
|
<h2 className="text-sm font-semibold">{t("githubTitle")}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-xs text-muted-foreground leading-5">
|
<p className="text-xs text-muted-foreground leading-5">
|
||||||
{t("githubDescription")}
|
{t("githubDescription")}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* Account list */}
|
{githubAccounts.length === 0 ? (
|
||||||
{accounts.accounts.length === 0 ? (
|
|
||||||
<div className="rounded-md border border-dashed bg-muted/10 px-4 py-6 text-center text-xs text-muted-foreground">
|
<div className="rounded-md border border-dashed bg-muted/10 px-4 py-6 text-center text-xs text-muted-foreground">
|
||||||
{t("noAccounts")}
|
{t("noAccounts")}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{accounts.accounts.map((account) => (
|
{githubAccounts.map((account) => (
|
||||||
<div
|
<AccountRow
|
||||||
key={account.id}
|
key={account.id}
|
||||||
className="flex items-center gap-3 rounded-lg border bg-muted/10 px-3 py-2.5"
|
account={account}
|
||||||
>
|
testingId={testingAccountId}
|
||||||
{/* Avatar */}
|
onTest={handleTestConnection}
|
||||||
{account.avatar_url ? (
|
onSetDefault={handleSetDefault}
|
||||||
<img
|
onRemove={setRemoveTarget}
|
||||||
src={account.avatar_url}
|
t={t}
|
||||||
alt={account.username}
|
/>
|
||||||
className="h-8 w-8 rounded-full"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="h-8 w-8 rounded-full bg-muted flex items-center justify-center text-xs font-medium">
|
|
||||||
{account.username[0]?.toUpperCase()}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Info */}
|
|
||||||
<div className="flex-1 min-w-0 space-y-1">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<span className="text-sm font-medium truncate">
|
|
||||||
{account.username}
|
|
||||||
</span>
|
|
||||||
{account.is_default && (
|
|
||||||
<Badge variant="secondary" className="text-[10px] px-1.5 py-0">
|
|
||||||
{t("defaultLabel")}
|
|
||||||
</Badge>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 text-[11px] text-muted-foreground">
|
|
||||||
<span className="truncate">{account.server_url}</span>
|
|
||||||
{account.scopes.length > 0 && (
|
|
||||||
<>
|
|
||||||
<span>·</span>
|
|
||||||
<span className="truncate">
|
|
||||||
{account.scopes.join(", ")}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Actions */}
|
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => handleTestConnection(account)}
|
|
||||||
disabled={testingAccountId === account.id}
|
|
||||||
>
|
|
||||||
{testingAccountId === account.id ? (
|
|
||||||
<Loader2 className="h-3 w-3 animate-spin" />
|
|
||||||
) : (
|
|
||||||
t("testConnection")
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
{!account.is_default && (
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
variant="ghost"
|
|
||||||
onClick={() => handleSetDefault(account.id)}
|
|
||||||
>
|
|
||||||
{t("setDefault")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
variant="ghost"
|
|
||||||
className="text-destructive hover:text-destructive"
|
|
||||||
onClick={() => setRemoveTarget(account)}
|
|
||||||
>
|
|
||||||
<Trash2 className="h-3 w-3" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<Button size="sm" onClick={() => setAddDialogOpen(true)}>
|
<Button size="sm" onClick={() => setAddGitHubOpen(true)}>
|
||||||
{t("addAccount")}
|
{t("addAccount")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* ---- Git Accounts (non-GitHub) ---- */}
|
||||||
|
<section className="rounded-xl border bg-card p-4 space-y-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Globe className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<h2 className="text-sm font-semibold">
|
||||||
|
{t("gitAccount.sectionTitle")}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-muted-foreground leading-5">
|
||||||
|
{t("gitAccount.sectionDescription")}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{gitAccounts.length === 0 ? (
|
||||||
|
<div className="rounded-md border border-dashed bg-muted/10 px-4 py-6 text-center text-xs text-muted-foreground">
|
||||||
|
{t("gitAccount.noAccounts")}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{gitAccounts.map((account) => (
|
||||||
|
<AccountRow
|
||||||
|
key={account.id}
|
||||||
|
account={account}
|
||||||
|
testingId={testingAccountId}
|
||||||
|
onTest={handleTestConnection}
|
||||||
|
onSetDefault={handleSetDefault}
|
||||||
|
onRemove={setRemoveTarget}
|
||||||
|
t={t}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button size="sm" onClick={() => setAddGitOpen(true)}>
|
||||||
|
{t("gitAccount.addAccount")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Add Account Dialog */}
|
{/* Dialogs */}
|
||||||
<AddGitHubAccountDialog
|
<AddGitHubAccountDialog
|
||||||
open={addDialogOpen}
|
open={addGitHubOpen}
|
||||||
onOpenChange={setAddDialogOpen}
|
onOpenChange={setAddGitHubOpen}
|
||||||
|
onAccountAdded={handleAccountAdded}
|
||||||
|
isFirstAccount={accounts.accounts.length === 0}
|
||||||
|
/>
|
||||||
|
<AddGitAccountDialog
|
||||||
|
open={addGitOpen}
|
||||||
|
onOpenChange={setAddGitOpen}
|
||||||
onAccountAdded={handleAccountAdded}
|
onAccountAdded={handleAccountAdded}
|
||||||
isFirstAccount={accounts.accounts.length === 0}
|
isFirstAccount={accounts.accounts.length === 0}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -196,7 +196,26 @@
|
|||||||
"removeCancel": "إلغاء",
|
"removeCancel": "إلغاء",
|
||||||
"removeSuccess": "تمت إزالة الحساب.",
|
"removeSuccess": "تمت إزالة الحساب.",
|
||||||
"scopes": "النطاقات",
|
"scopes": "النطاقات",
|
||||||
"loadFailed": "فشل تحميل الإعدادات: {message}"
|
"loadFailed": "فشل تحميل الإعدادات: {message}",
|
||||||
|
"gitAccount": {
|
||||||
|
"sectionTitle": "حسابات خادم Git",
|
||||||
|
"sectionDescription": "إدارة بيانات الاعتماد لخوادم Git غير GitHub (GitLab، Bitbucket، الخوادم الذاتية، إلخ).",
|
||||||
|
"noAccounts": "لا توجد حسابات خادم Git مكوّنة.",
|
||||||
|
"addAccount": "إضافة حساب",
|
||||||
|
"addTitle": "إضافة حساب Git",
|
||||||
|
"addDescription": "أدخل عنوان الخادم واسم المستخدم وكلمة المرور أو رمز الوصول.",
|
||||||
|
"serverUrl": "عنوان الخادم",
|
||||||
|
"serverUrlPlaceholder": "https://gitlab.example.com",
|
||||||
|
"username": "اسم المستخدم",
|
||||||
|
"usernamePlaceholder": "اسم المستخدم أو البريد الإلكتروني",
|
||||||
|
"password": "كلمة المرور / الرمز",
|
||||||
|
"passwordPlaceholder": "كلمة المرور أو رمز الوصول",
|
||||||
|
"passwordHint": "أدخل كلمة مرور الخادم أو رمز الوصول.",
|
||||||
|
"add": "إضافة",
|
||||||
|
"serverRequired": "عنوان الخادم مطلوب.",
|
||||||
|
"usernameRequired": "اسم المستخدم مطلوب.",
|
||||||
|
"passwordRequired": "كلمة المرور مطلوبة."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "الاختصارات",
|
"sectionTitle": "الاختصارات",
|
||||||
|
|||||||
@@ -196,7 +196,26 @@
|
|||||||
"removeCancel": "Abbrechen",
|
"removeCancel": "Abbrechen",
|
||||||
"removeSuccess": "Konto entfernt.",
|
"removeSuccess": "Konto entfernt.",
|
||||||
"scopes": "Berechtigungen",
|
"scopes": "Berechtigungen",
|
||||||
"loadFailed": "Einstellungen konnten nicht geladen werden: {message}"
|
"loadFailed": "Einstellungen konnten nicht geladen werden: {message}",
|
||||||
|
"gitAccount": {
|
||||||
|
"sectionTitle": "Git-Server-Konten",
|
||||||
|
"sectionDescription": "Verwalten Sie Anmeldedaten für Nicht-GitHub-Git-Server (GitLab, Bitbucket, selbst gehostet usw.).",
|
||||||
|
"noAccounts": "Keine Git-Server-Konten konfiguriert.",
|
||||||
|
"addAccount": "Konto hinzufügen",
|
||||||
|
"addTitle": "Git-Konto hinzufügen",
|
||||||
|
"addDescription": "Geben Sie Serveradresse, Benutzername und Passwort oder Zugriffstoken ein.",
|
||||||
|
"serverUrl": "Server-URL",
|
||||||
|
"serverUrlPlaceholder": "https://gitlab.example.com",
|
||||||
|
"username": "Benutzername",
|
||||||
|
"usernamePlaceholder": "Benutzername oder E-Mail",
|
||||||
|
"password": "Passwort / Token",
|
||||||
|
"passwordPlaceholder": "Passwort oder Zugriffstoken",
|
||||||
|
"passwordHint": "Geben Sie das Passwort oder Zugriffstoken des Servers ein.",
|
||||||
|
"add": "Hinzufügen",
|
||||||
|
"serverRequired": "Server-URL ist erforderlich.",
|
||||||
|
"usernameRequired": "Benutzername ist erforderlich.",
|
||||||
|
"passwordRequired": "Passwort ist erforderlich."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "Kurzbefehle",
|
"sectionTitle": "Kurzbefehle",
|
||||||
|
|||||||
@@ -196,7 +196,26 @@
|
|||||||
"removeCancel": "Cancel",
|
"removeCancel": "Cancel",
|
||||||
"removeSuccess": "Account removed.",
|
"removeSuccess": "Account removed.",
|
||||||
"scopes": "Scopes",
|
"scopes": "Scopes",
|
||||||
"loadFailed": "Failed to load settings: {message}"
|
"loadFailed": "Failed to load settings: {message}",
|
||||||
|
"gitAccount": {
|
||||||
|
"sectionTitle": "Git Accounts",
|
||||||
|
"sectionDescription": "Manage credentials for non-GitHub Git servers (GitLab, Bitbucket, self-hosted, etc.).",
|
||||||
|
"noAccounts": "No Git server accounts configured.",
|
||||||
|
"addAccount": "Add Account",
|
||||||
|
"addTitle": "Add Git Account",
|
||||||
|
"addDescription": "Enter the server address, username, and password or access token.",
|
||||||
|
"serverUrl": "Server URL",
|
||||||
|
"serverUrlPlaceholder": "https://gitlab.example.com",
|
||||||
|
"username": "Username",
|
||||||
|
"usernamePlaceholder": "Username or email",
|
||||||
|
"password": "Password / Token",
|
||||||
|
"passwordPlaceholder": "Password or access token",
|
||||||
|
"passwordHint": "Enter your password or a personal access token for the server.",
|
||||||
|
"add": "Add",
|
||||||
|
"serverRequired": "Server URL is required.",
|
||||||
|
"usernameRequired": "Username is required.",
|
||||||
|
"passwordRequired": "Password is required."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "Shortcuts",
|
"sectionTitle": "Shortcuts",
|
||||||
|
|||||||
@@ -196,7 +196,26 @@
|
|||||||
"removeCancel": "Cancelar",
|
"removeCancel": "Cancelar",
|
||||||
"removeSuccess": "Cuenta eliminada.",
|
"removeSuccess": "Cuenta eliminada.",
|
||||||
"scopes": "Alcances",
|
"scopes": "Alcances",
|
||||||
"loadFailed": "Error al cargar configuración: {message}"
|
"loadFailed": "Error al cargar configuración: {message}",
|
||||||
|
"gitAccount": {
|
||||||
|
"sectionTitle": "Cuentas de servidor Git",
|
||||||
|
"sectionDescription": "Gestiona credenciales para servidores Git que no son GitHub (GitLab, Bitbucket, autoalojados, etc.).",
|
||||||
|
"noAccounts": "No hay cuentas de servidor Git configuradas.",
|
||||||
|
"addAccount": "Añadir cuenta",
|
||||||
|
"addTitle": "Añadir cuenta Git",
|
||||||
|
"addDescription": "Introduce la dirección del servidor, nombre de usuario y contraseña o token de acceso.",
|
||||||
|
"serverUrl": "URL del servidor",
|
||||||
|
"serverUrlPlaceholder": "https://gitlab.example.com",
|
||||||
|
"username": "Nombre de usuario",
|
||||||
|
"usernamePlaceholder": "Usuario o correo electrónico",
|
||||||
|
"password": "Contraseña / Token",
|
||||||
|
"passwordPlaceholder": "Contraseña o token de acceso",
|
||||||
|
"passwordHint": "Introduce tu contraseña o token de acceso del servidor.",
|
||||||
|
"add": "Añadir",
|
||||||
|
"serverRequired": "La URL del servidor es obligatoria.",
|
||||||
|
"usernameRequired": "El nombre de usuario es obligatorio.",
|
||||||
|
"passwordRequired": "La contraseña es obligatoria."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "Atajos",
|
"sectionTitle": "Atajos",
|
||||||
|
|||||||
@@ -196,7 +196,26 @@
|
|||||||
"removeCancel": "Annuler",
|
"removeCancel": "Annuler",
|
||||||
"removeSuccess": "Compte supprimé.",
|
"removeSuccess": "Compte supprimé.",
|
||||||
"scopes": "Portées",
|
"scopes": "Portées",
|
||||||
"loadFailed": "Échec du chargement des paramètres : {message}"
|
"loadFailed": "Échec du chargement des paramètres : {message}",
|
||||||
|
"gitAccount": {
|
||||||
|
"sectionTitle": "Comptes serveur Git",
|
||||||
|
"sectionDescription": "Gérez les identifiants pour les serveurs Git non GitHub (GitLab, Bitbucket, auto-hébergé, etc.).",
|
||||||
|
"noAccounts": "Aucun compte de serveur Git configuré.",
|
||||||
|
"addAccount": "Ajouter un compte",
|
||||||
|
"addTitle": "Ajouter un compte Git",
|
||||||
|
"addDescription": "Entrez l'adresse du serveur, le nom d'utilisateur et le mot de passe ou jeton d'accès.",
|
||||||
|
"serverUrl": "URL du serveur",
|
||||||
|
"serverUrlPlaceholder": "https://gitlab.example.com",
|
||||||
|
"username": "Nom d'utilisateur",
|
||||||
|
"usernamePlaceholder": "Nom d'utilisateur ou e-mail",
|
||||||
|
"password": "Mot de passe / Jeton",
|
||||||
|
"passwordPlaceholder": "Mot de passe ou jeton d'accès",
|
||||||
|
"passwordHint": "Entrez le mot de passe ou le jeton d'accès du serveur.",
|
||||||
|
"add": "Ajouter",
|
||||||
|
"serverRequired": "L'URL du serveur est requise.",
|
||||||
|
"usernameRequired": "Le nom d'utilisateur est requis.",
|
||||||
|
"passwordRequired": "Le mot de passe est requis."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "Raccourcis",
|
"sectionTitle": "Raccourcis",
|
||||||
|
|||||||
@@ -196,7 +196,26 @@
|
|||||||
"removeCancel": "キャンセル",
|
"removeCancel": "キャンセル",
|
||||||
"removeSuccess": "アカウントを削除しました。",
|
"removeSuccess": "アカウントを削除しました。",
|
||||||
"scopes": "スコープ",
|
"scopes": "スコープ",
|
||||||
"loadFailed": "設定の読み込みに失敗しました:{message}"
|
"loadFailed": "設定の読み込みに失敗しました:{message}",
|
||||||
|
"gitAccount": {
|
||||||
|
"sectionTitle": "Git サーバーアカウント",
|
||||||
|
"sectionDescription": "GitHub 以外の Git サーバーの認証情報を管理します(GitLab、Bitbucket、セルフホストなど)。",
|
||||||
|
"noAccounts": "Git サーバーアカウントが設定されていません。",
|
||||||
|
"addAccount": "アカウントを追加",
|
||||||
|
"addTitle": "Git アカウントを追加",
|
||||||
|
"addDescription": "サーバーアドレス、ユーザー名、パスワードまたはアクセストークンを入力してください。",
|
||||||
|
"serverUrl": "サーバー URL",
|
||||||
|
"serverUrlPlaceholder": "https://gitlab.example.com",
|
||||||
|
"username": "ユーザー名",
|
||||||
|
"usernamePlaceholder": "ユーザー名またはメールアドレス",
|
||||||
|
"password": "パスワード / トークン",
|
||||||
|
"passwordPlaceholder": "パスワードまたはアクセストークン",
|
||||||
|
"passwordHint": "サーバーのパスワードまたはアクセストークンを入力してください。",
|
||||||
|
"add": "追加",
|
||||||
|
"serverRequired": "サーバー URL を入力してください。",
|
||||||
|
"usernameRequired": "ユーザー名を入力してください。",
|
||||||
|
"passwordRequired": "パスワードを入力してください。"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "ショートカット",
|
"sectionTitle": "ショートカット",
|
||||||
|
|||||||
@@ -196,7 +196,26 @@
|
|||||||
"removeCancel": "취소",
|
"removeCancel": "취소",
|
||||||
"removeSuccess": "계정이 삭제되었습니다.",
|
"removeSuccess": "계정이 삭제되었습니다.",
|
||||||
"scopes": "범위",
|
"scopes": "범위",
|
||||||
"loadFailed": "설정 로드 실패: {message}"
|
"loadFailed": "설정 로드 실패: {message}",
|
||||||
|
"gitAccount": {
|
||||||
|
"sectionTitle": "Git 서버 계정",
|
||||||
|
"sectionDescription": "GitHub 이외의 Git 서버 자격 증명을 관리합니다 (GitLab, Bitbucket, 자체 호스팅 등).",
|
||||||
|
"noAccounts": "설정된 Git 서버 계정이 없습니다.",
|
||||||
|
"addAccount": "계정 추가",
|
||||||
|
"addTitle": "Git 계정 추가",
|
||||||
|
"addDescription": "서버 주소, 사용자 이름, 비밀번호 또는 액세스 토큰을 입력하세요.",
|
||||||
|
"serverUrl": "서버 URL",
|
||||||
|
"serverUrlPlaceholder": "https://gitlab.example.com",
|
||||||
|
"username": "사용자 이름",
|
||||||
|
"usernamePlaceholder": "사용자 이름 또는 이메일",
|
||||||
|
"password": "비밀번호 / 토큰",
|
||||||
|
"passwordPlaceholder": "비밀번호 또는 액세스 토큰",
|
||||||
|
"passwordHint": "서버의 비밀번호 또는 액세스 토큰을 입력하세요.",
|
||||||
|
"add": "추가",
|
||||||
|
"serverRequired": "서버 URL을 입력하세요.",
|
||||||
|
"usernameRequired": "사용자 이름을 입력하세요.",
|
||||||
|
"passwordRequired": "비밀번호를 입력하세요."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "단축키",
|
"sectionTitle": "단축키",
|
||||||
|
|||||||
@@ -196,7 +196,26 @@
|
|||||||
"removeCancel": "Cancelar",
|
"removeCancel": "Cancelar",
|
||||||
"removeSuccess": "Conta removida.",
|
"removeSuccess": "Conta removida.",
|
||||||
"scopes": "Escopos",
|
"scopes": "Escopos",
|
||||||
"loadFailed": "Falha ao carregar configurações: {message}"
|
"loadFailed": "Falha ao carregar configurações: {message}",
|
||||||
|
"gitAccount": {
|
||||||
|
"sectionTitle": "Contas de servidor Git",
|
||||||
|
"sectionDescription": "Gerencie credenciais para servidores Git que não são GitHub (GitLab, Bitbucket, auto-hospedados, etc.).",
|
||||||
|
"noAccounts": "Nenhuma conta de servidor Git configurada.",
|
||||||
|
"addAccount": "Adicionar conta",
|
||||||
|
"addTitle": "Adicionar conta Git",
|
||||||
|
"addDescription": "Insira o endereço do servidor, nome de usuário e senha ou token de acesso.",
|
||||||
|
"serverUrl": "URL do servidor",
|
||||||
|
"serverUrlPlaceholder": "https://gitlab.example.com",
|
||||||
|
"username": "Nome de usuário",
|
||||||
|
"usernamePlaceholder": "Nome de usuário ou e-mail",
|
||||||
|
"password": "Senha / Token",
|
||||||
|
"passwordPlaceholder": "Senha ou token de acesso",
|
||||||
|
"passwordHint": "Insira a senha ou o token de acesso do servidor.",
|
||||||
|
"add": "Adicionar",
|
||||||
|
"serverRequired": "A URL do servidor é obrigatória.",
|
||||||
|
"usernameRequired": "O nome de usuário é obrigatório.",
|
||||||
|
"passwordRequired": "A senha é obrigatória."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "Atalhos",
|
"sectionTitle": "Atalhos",
|
||||||
|
|||||||
@@ -196,7 +196,26 @@
|
|||||||
"removeCancel": "取消",
|
"removeCancel": "取消",
|
||||||
"removeSuccess": "账号已删除。",
|
"removeSuccess": "账号已删除。",
|
||||||
"scopes": "权限范围",
|
"scopes": "权限范围",
|
||||||
"loadFailed": "加载设置失败:{message}"
|
"loadFailed": "加载设置失败:{message}",
|
||||||
|
"gitAccount": {
|
||||||
|
"sectionTitle": "Git 服务器账号",
|
||||||
|
"sectionDescription": "管理非 GitHub 的 Git 服务器凭据(GitLab、Bitbucket、自建服务等)。",
|
||||||
|
"noAccounts": "暂无 Git 服务器账号。",
|
||||||
|
"addAccount": "添加账号",
|
||||||
|
"addTitle": "添加 Git 账号",
|
||||||
|
"addDescription": "输入服务器地址、用户名和密码或访问令牌。",
|
||||||
|
"serverUrl": "服务器地址",
|
||||||
|
"serverUrlPlaceholder": "https://gitlab.example.com",
|
||||||
|
"username": "用户名",
|
||||||
|
"usernamePlaceholder": "用户名或邮箱",
|
||||||
|
"password": "密码 / 令牌",
|
||||||
|
"passwordPlaceholder": "密码或访问令牌",
|
||||||
|
"passwordHint": "输入服务器的密码或个人访问令牌。",
|
||||||
|
"add": "添加",
|
||||||
|
"serverRequired": "请输入服务器地址。",
|
||||||
|
"usernameRequired": "请输入用户名。",
|
||||||
|
"passwordRequired": "请输入密码。"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "快捷键",
|
"sectionTitle": "快捷键",
|
||||||
|
|||||||
@@ -196,7 +196,26 @@
|
|||||||
"removeCancel": "取消",
|
"removeCancel": "取消",
|
||||||
"removeSuccess": "帳號已刪除。",
|
"removeSuccess": "帳號已刪除。",
|
||||||
"scopes": "權限範圍",
|
"scopes": "權限範圍",
|
||||||
"loadFailed": "載入設定失敗:{message}"
|
"loadFailed": "載入設定失敗:{message}",
|
||||||
|
"gitAccount": {
|
||||||
|
"sectionTitle": "Git 伺服器帳號",
|
||||||
|
"sectionDescription": "管理非 GitHub 的 Git 伺服器憑據(GitLab、Bitbucket、自建服務等)。",
|
||||||
|
"noAccounts": "尚未設定 Git 伺服器帳號。",
|
||||||
|
"addAccount": "新增帳號",
|
||||||
|
"addTitle": "新增 Git 帳號",
|
||||||
|
"addDescription": "輸入伺服器網址、使用者名稱和密碼或存取權杖。",
|
||||||
|
"serverUrl": "伺服器網址",
|
||||||
|
"serverUrlPlaceholder": "https://gitlab.example.com",
|
||||||
|
"username": "使用者名稱",
|
||||||
|
"usernamePlaceholder": "使用者名稱或電子郵件",
|
||||||
|
"password": "密碼 / 權杖",
|
||||||
|
"passwordPlaceholder": "密碼或存取權杖",
|
||||||
|
"passwordHint": "輸入伺服器的密碼或個人存取權杖。",
|
||||||
|
"add": "新增",
|
||||||
|
"serverRequired": "請輸入伺服器網址。",
|
||||||
|
"usernameRequired": "請輸入使用者名稱。",
|
||||||
|
"passwordRequired": "請輸入密碼。"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ShortcutSettings": {
|
"ShortcutSettings": {
|
||||||
"sectionTitle": "快捷鍵",
|
"sectionTitle": "快捷鍵",
|
||||||
|
|||||||
Reference in New Issue
Block a user