mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-20 02:17:05 +00:00
feat(auth): add opt-in shared context groups across accounts
This commit is contained in:
1 parent
1dae52014b
commit
4fed74c64b
21 files changed
+787
-25
No files matched your search
@@ -87,6 +87,24 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
||||
return <span className="text-muted-foreground">{date.toLocaleDateString()}</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'context',
|
||||
header: 'Context',
|
||||
size: 170,
|
||||
cell: ({ row }) => {
|
||||
if (row.original.type === 'cliproxy') {
|
||||
return <span className="text-muted-foreground/50">-</span>;
|
||||
}
|
||||
|
||||
const mode = row.original.context_mode || 'isolated';
|
||||
if (mode === 'shared') {
|
||||
const group = row.original.context_group || 'default';
|
||||
return <span className="text-muted-foreground">shared ({group})</span>;
|
||||
}
|
||||
|
||||
return <span className="text-muted-foreground">isolated</span>;
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
header: 'Actions',
|
||||
@@ -154,6 +172,7 @@ export function AccountsTable({ data, defaultAccount }: AccountsTableProps) {
|
||||
type: 'w-[100px]',
|
||||
created: 'w-[150px]',
|
||||
last_used: 'w-[150px]',
|
||||
context: 'w-[170px]',
|
||||
actions: 'w-[180px]',
|
||||
}[header.id] || 'w-auto';
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Copy, Check, Terminal } from 'lucide-react';
|
||||
|
||||
interface CreateAuthProfileDialogProps {
|
||||
@@ -23,14 +24,32 @@ interface CreateAuthProfileDialogProps {
|
||||
|
||||
export function CreateAuthProfileDialog({ open, onClose }: CreateAuthProfileDialogProps) {
|
||||
const [profileName, setProfileName] = useState('');
|
||||
const [shareContext, setShareContext] = useState(false);
|
||||
const [contextGroup, setContextGroup] = useState('');
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
// Validate profile name: alphanumeric, dash, underscore only
|
||||
const isValidName = /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(profileName);
|
||||
const command = profileName ? `ccs auth create ${profileName}` : 'ccs auth create <name>';
|
||||
const normalizedGroup = contextGroup.trim().toLowerCase();
|
||||
const isValidContextGroup =
|
||||
normalizedGroup.length === 0 || /^[a-zA-Z][a-zA-Z0-9_-]*$/.test(normalizedGroup);
|
||||
|
||||
const command =
|
||||
profileName && isValidName
|
||||
? [
|
||||
`ccs auth create ${profileName}`,
|
||||
shareContext
|
||||
? normalizedGroup.length > 0
|
||||
? `--context-group ${normalizedGroup}`
|
||||
: '--share-context'
|
||||
: '',
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' ')
|
||||
: 'ccs auth create <name>';
|
||||
|
||||
const handleCopy = async () => {
|
||||
if (!isValidName) return;
|
||||
if (!isValidName || (shareContext && !isValidContextGroup)) return;
|
||||
await navigator.clipboard.writeText(command);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
@@ -38,6 +57,8 @@ export function CreateAuthProfileDialog({ open, onClose }: CreateAuthProfileDial
|
||||
|
||||
const handleClose = () => {
|
||||
setProfileName('');
|
||||
setShareContext(false);
|
||||
setContextGroup('');
|
||||
setCopied(false);
|
||||
onClose();
|
||||
};
|
||||
@@ -70,6 +91,41 @@ export function CreateAuthProfileDialog({ open, onClose }: CreateAuthProfileDial
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="share-context"
|
||||
checked={shareContext}
|
||||
onCheckedChange={(checked) => setShareContext(checked === true)}
|
||||
/>
|
||||
<Label htmlFor="share-context" className="cursor-pointer">
|
||||
Share project context with other accounts
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
{shareContext && (
|
||||
<div className="space-y-2 pl-6">
|
||||
<Label htmlFor="context-group">Context Group (optional)</Label>
|
||||
<Input
|
||||
id="context-group"
|
||||
value={contextGroup}
|
||||
onChange={(e) => setContextGroup(e.target.value)}
|
||||
placeholder="default, sprint-a, client-x"
|
||||
autoComplete="off"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Leave empty to use the default shared group.
|
||||
</p>
|
||||
{contextGroup.trim().length > 0 && !isValidContextGroup && (
|
||||
<p className="text-xs text-destructive">
|
||||
Group must start with a letter and use only letters, numbers, dashes, or
|
||||
underscores.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Command</Label>
|
||||
<div className="flex items-center gap-2 p-3 bg-muted rounded-md font-mono text-sm">
|
||||
@@ -80,7 +136,7 @@ export function CreateAuthProfileDialog({ open, onClose }: CreateAuthProfileDial
|
||||
size="sm"
|
||||
className="shrink-0 h-8 px-2"
|
||||
onClick={handleCopy}
|
||||
disabled={!isValidName}
|
||||
disabled={!isValidName || (shareContext && !isValidContextGroup)}
|
||||
>
|
||||
{copied ? (
|
||||
<Check className="w-4 h-4 text-green-500" />
|
||||
@@ -103,7 +159,10 @@ export function CreateAuthProfileDialog({ open, onClose }: CreateAuthProfileDial
|
||||
<Button variant="ghost" onClick={handleClose}>
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={handleCopy} disabled={!isValidName}>
|
||||
<Button
|
||||
onClick={handleCopy}
|
||||
disabled={!isValidName || (shareContext && !isValidContextGroup)}
|
||||
>
|
||||
{copied ? (
|
||||
<>
|
||||
<Check className="w-4 h-4 mr-2" />
|
||||
|
||||
@@ -445,6 +445,8 @@ export interface Account {
|
||||
type?: string;
|
||||
created: string;
|
||||
last_used?: string | null;
|
||||
context_mode?: 'isolated' | 'shared';
|
||||
context_group?: string;
|
||||
}
|
||||
|
||||
// Unified config types
|
||||
|
||||
Reference in new issue
Block a user