feat: initial public release (MAESTRO)
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
interface NamespaceEditorProps {
|
||||
value: Record<string, { apiKey: string }>;
|
||||
onChange: (value: Record<string, { apiKey: string }>) => void;
|
||||
/**
|
||||
* Disable the "add namespace" controls (input fields + button). Existing
|
||||
* entries remain editable / removable. Used by the DKS [LEGACY] section
|
||||
* to steer new integrations toward MCP servers.
|
||||
*/
|
||||
addDisabled?: boolean;
|
||||
/** Tooltip shown on the disabled controls. */
|
||||
addDisabledReason?: string;
|
||||
/** Optional href surfaced alongside the tooltip (e.g. MCP help doc). */
|
||||
addDisabledHref?: string;
|
||||
}
|
||||
|
||||
export function NamespaceEditor({
|
||||
value,
|
||||
onChange,
|
||||
addDisabled = false,
|
||||
addDisabledReason,
|
||||
addDisabledHref,
|
||||
}: NamespaceEditorProps) {
|
||||
const [newName, setNewName] = useState('');
|
||||
const [newApiKey, setNewApiKey] = useState('');
|
||||
|
||||
const entries = Object.entries(value);
|
||||
|
||||
const handleAdd = () => {
|
||||
if (addDisabled) return;
|
||||
const name = newName.trim();
|
||||
if (!name || name in value) return;
|
||||
onChange({ ...value, [name]: { apiKey: newApiKey } });
|
||||
setNewName('');
|
||||
setNewApiKey('');
|
||||
};
|
||||
|
||||
const handleRemove = (name: string) => {
|
||||
const { [name]: _, ...rest } = value;
|
||||
onChange(rest);
|
||||
};
|
||||
|
||||
const handleApiKeyChange = (name: string, apiKey: string) => {
|
||||
onChange({ ...value, [name]: { apiKey } });
|
||||
};
|
||||
|
||||
const disabledTitle = addDisabled ? addDisabledReason : undefined;
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{entries.map(([name, { apiKey }]) => (
|
||||
<div key={name} className="flex items-center gap-2">
|
||||
<span className="text-sm text-slate-700 min-w-[140px] truncate" title={name}>{name}</span>
|
||||
<input
|
||||
type="password"
|
||||
value={apiKey}
|
||||
onChange={e => handleApiKeyChange(name, e.target.value)}
|
||||
placeholder="API Key"
|
||||
className="flex-1 px-3 py-1.5 text-sm border border-slate-300 rounded-lg focus:ring-2 focus:ring-accent-ring focus:border-accent outline-none"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleRemove(name)}
|
||||
className="text-slate-400 hover:text-red-500 text-lg leading-none px-1"
|
||||
>×</button>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex gap-1">
|
||||
<input
|
||||
value={newName}
|
||||
onChange={e => setNewName(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleAdd(); } }}
|
||||
placeholder="namespace"
|
||||
disabled={addDisabled}
|
||||
title={disabledTitle}
|
||||
className="w-[140px] px-3 py-1.5 text-sm border border-slate-300 rounded-lg focus:ring-2 focus:ring-accent-ring focus:border-accent outline-none disabled:bg-slate-100 disabled:text-slate-400 disabled:cursor-not-allowed"
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
value={newApiKey}
|
||||
onChange={e => setNewApiKey(e.target.value)}
|
||||
onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); handleAdd(); } }}
|
||||
placeholder="API Key"
|
||||
disabled={addDisabled}
|
||||
title={disabledTitle}
|
||||
className="flex-1 px-3 py-1.5 text-sm border border-slate-300 rounded-lg focus:ring-2 focus:ring-accent-ring focus:border-accent outline-none disabled:bg-slate-100 disabled:text-slate-400 disabled:cursor-not-allowed"
|
||||
/>
|
||||
<button
|
||||
onClick={handleAdd}
|
||||
disabled={addDisabled}
|
||||
title={disabledTitle}
|
||||
className="px-3 py-1.5 text-sm bg-accent text-accent-fg rounded-lg hover:bg-accent-deep disabled:bg-slate-200 disabled:text-slate-400 disabled:cursor-not-allowed disabled:hover:bg-slate-200"
|
||||
aria-label={addDisabled ? '新規追加は無効化されています' : '新規追加'}
|
||||
>+ 追加</button>
|
||||
{addDisabled && addDisabledHref && (
|
||||
<a
|
||||
href={addDisabledHref}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-2 py-1.5 text-xs text-accent underline self-center"
|
||||
title={disabledTitle}
|
||||
>MCP ガイド</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user