Compare commits
2 Commits
v0.1.86-ma
...
v0.1.88
| Author | SHA1 | Date | |
|---|---|---|---|
| c853f2676d | |||
| 090aad6bc6 |
@@ -35,7 +35,7 @@ pub struct Project {
|
|||||||
pub bedrock_config: Option<BedrockConfig>,
|
pub bedrock_config: Option<BedrockConfig>,
|
||||||
pub allow_docker_access: bool,
|
pub allow_docker_access: bool,
|
||||||
pub ssh_key_path: Option<String>,
|
pub ssh_key_path: Option<String>,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing, default)]
|
||||||
pub git_token: Option<String>,
|
pub git_token: Option<String>,
|
||||||
pub git_user_name: Option<String>,
|
pub git_user_name: Option<String>,
|
||||||
pub git_user_email: Option<String>,
|
pub git_user_email: Option<String>,
|
||||||
@@ -100,14 +100,14 @@ impl Default for BedrockAuthMethod {
|
|||||||
pub struct BedrockConfig {
|
pub struct BedrockConfig {
|
||||||
pub auth_method: BedrockAuthMethod,
|
pub auth_method: BedrockAuthMethod,
|
||||||
pub aws_region: String,
|
pub aws_region: String,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing, default)]
|
||||||
pub aws_access_key_id: Option<String>,
|
pub aws_access_key_id: Option<String>,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing, default)]
|
||||||
pub aws_secret_access_key: Option<String>,
|
pub aws_secret_access_key: Option<String>,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing, default)]
|
||||||
pub aws_session_token: Option<String>,
|
pub aws_session_token: Option<String>,
|
||||||
pub aws_profile: Option<String>,
|
pub aws_profile: Option<String>,
|
||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing, default)]
|
||||||
pub aws_bearer_token: Option<String>,
|
pub aws_bearer_token: Option<String>,
|
||||||
pub model_id: Option<String>,
|
pub model_id: Option<String>,
|
||||||
pub disable_prompt_caching: bool,
|
pub disable_prompt_caching: bool,
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ export default function ProjectCard({ project }: Props) {
|
|||||||
const [progressMsg, setProgressMsg] = useState<string | null>(null);
|
const [progressMsg, setProgressMsg] = useState<string | null>(null);
|
||||||
const [activeOperation, setActiveOperation] = useState<"starting" | "stopping" | "resetting" | null>(null);
|
const [activeOperation, setActiveOperation] = useState<"starting" | "stopping" | "resetting" | null>(null);
|
||||||
const [operationCompleted, setOperationCompleted] = useState(false);
|
const [operationCompleted, setOperationCompleted] = useState(false);
|
||||||
|
const [showRemoveConfirm, setShowRemoveConfirm] = useState(false);
|
||||||
|
const [isEditingName, setIsEditingName] = useState(false);
|
||||||
|
const [editName, setEditName] = useState(project.name);
|
||||||
const isSelected = selectedProjectId === project.id;
|
const isSelected = selectedProjectId === project.id;
|
||||||
const isStopped = project.status === "stopped" || project.status === "error";
|
const isStopped = project.status === "stopped" || project.status === "error";
|
||||||
|
|
||||||
@@ -54,6 +57,7 @@ export default function ProjectCard({ project }: Props) {
|
|||||||
|
|
||||||
// Sync local state when project prop changes (e.g., after save or external update)
|
// Sync local state when project prop changes (e.g., after save or external update)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setEditName(project.name);
|
||||||
setPaths(project.paths ?? []);
|
setPaths(project.paths ?? []);
|
||||||
setSshKeyPath(project.ssh_key_path ?? "");
|
setSshKeyPath(project.ssh_key_path ?? "");
|
||||||
setGitName(project.git_user_name ?? "");
|
setGitName(project.git_user_name ?? "");
|
||||||
@@ -309,7 +313,41 @@ export default function ProjectCard({ project }: Props) {
|
|||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className={`w-2 h-2 rounded-full flex-shrink-0 ${statusColor}`} />
|
<span className={`w-2 h-2 rounded-full flex-shrink-0 ${statusColor}`} />
|
||||||
<span className="text-sm font-medium truncate flex-1">{project.name}</span>
|
{isEditingName ? (
|
||||||
|
<input
|
||||||
|
autoFocus
|
||||||
|
value={editName}
|
||||||
|
onChange={(e) => setEditName(e.target.value)}
|
||||||
|
onBlur={async () => {
|
||||||
|
setIsEditingName(false);
|
||||||
|
const trimmed = editName.trim();
|
||||||
|
if (trimmed && trimmed !== project.name) {
|
||||||
|
try {
|
||||||
|
await update({ ...project, name: trimmed });
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to rename project:", err);
|
||||||
|
setEditName(project.name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setEditName(project.name);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") (e.target as HTMLInputElement).blur();
|
||||||
|
if (e.key === "Escape") { setEditName(project.name); setIsEditingName(false); }
|
||||||
|
}}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
className="text-sm font-medium flex-1 min-w-0 px-1 py-0 bg-[var(--bg-primary)] border border-[var(--accent)] rounded text-[var(--text-primary)] focus:outline-none"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
className="text-sm font-medium truncate flex-1 cursor-text"
|
||||||
|
title="Double-click to rename"
|
||||||
|
onDoubleClick={(e) => { e.stopPropagation(); setIsEditingName(true); }}
|
||||||
|
>
|
||||||
|
{project.name}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-0.5 ml-4 space-y-0.5">
|
<div className="mt-0.5 ml-4 space-y-0.5">
|
||||||
{project.paths.map((pp, i) => (
|
{project.paths.map((pp, i) => (
|
||||||
@@ -385,16 +423,34 @@ export default function ProjectCard({ project }: Props) {
|
|||||||
disabled={false}
|
disabled={false}
|
||||||
label={showConfig ? "Hide" : "Config"}
|
label={showConfig ? "Hide" : "Config"}
|
||||||
/>
|
/>
|
||||||
<ActionButton
|
{showRemoveConfirm ? (
|
||||||
onClick={async () => {
|
<span className="inline-flex items-center gap-1 text-xs">
|
||||||
if (confirm(`Remove project "${project.name}"?`)) {
|
<span className="text-[var(--text-secondary)]">Remove?</span>
|
||||||
|
<button
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setShowRemoveConfirm(false);
|
||||||
await remove(project.id);
|
await remove(project.id);
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
|
className="px-1.5 py-0.5 rounded text-white bg-[var(--error)] hover:opacity-80 transition-colors"
|
||||||
|
>
|
||||||
|
Yes
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={(e) => { e.stopPropagation(); setShowRemoveConfirm(false); }}
|
||||||
|
className="px-1.5 py-0.5 rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-primary)] transition-colors"
|
||||||
|
>
|
||||||
|
No
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<ActionButton
|
||||||
|
onClick={() => setShowRemoveConfirm(true)}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
label="Remove"
|
label="Remove"
|
||||||
danger
|
danger
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Config panel */}
|
{/* Config panel */}
|
||||||
|
|||||||
Reference in New Issue
Block a user