+ git config

This commit is contained in:
2026-02-25 20:27:29 +03:00
parent f9ac282596
commit 999c0c54df
10 changed files with 9262 additions and 1185 deletions

View File

@@ -23,6 +23,8 @@
import { openDrawerForTask } from "$lib/stores/taskDrawer.js";
import { api } from "$lib/api.js";
import { debounce } from "$lib/utils/debounce.js";
import { addToast } from "$lib/toasts.js";
import { gitService } from "../../services/gitService.js";
import MappingTable from "$components/MappingTable.svelte";
// State
@@ -65,6 +67,8 @@
// Validation state
let validatingIds = new Set();
let gitBusyIds = new Set();
let cachedGitConfigs = [];
// Environment options - will be loaded from API
let environments = [];
@@ -142,7 +146,12 @@
id: d.id,
title: d.title,
slug: d.slug,
gitStatus: d.git_status?.sync_status?.toLowerCase() || null,
git: {
status: d.git_status?.sync_status?.toLowerCase() || "no_repo",
branch: d.git_status?.branch || null,
hasRepo: d.git_status?.has_repo === true,
hasChangesForCommit: d.git_status?.has_changes_for_commit === true,
},
lastTask: d.last_task
? {
status: d.last_task.status?.toLowerCase() || null,
@@ -510,6 +519,147 @@
}
}
function isGitBusy(dashboardId) {
return gitBusyIds.has(dashboardId);
}
function setGitBusy(dashboardId, busy) {
if (busy) {
gitBusyIds.add(dashboardId);
} else {
gitBusyIds.delete(dashboardId);
}
gitBusyIds = new Set(gitBusyIds);
}
async function ensureGitConfigs() {
if (cachedGitConfigs.length > 0) return cachedGitConfigs;
cachedGitConfigs = await gitService.getConfigs();
return cachedGitConfigs;
}
function updateDashboardGitState(dashboardId, nextGit) {
dashboards = dashboards.map((dashboard) =>
dashboard.id === dashboardId
? { ...dashboard, git: { ...dashboard.git, ...nextGit } }
: dashboard,
);
}
async function refreshDashboardGitState(dashboardId) {
try {
const status = await gitService.getStatus(dashboardId);
updateDashboardGitState(dashboardId, {
status: status?.is_dirty ? "diff" : "ok",
branch: status?.current_branch || null,
hasRepo: true,
hasChangesForCommit: Boolean(status?.is_dirty),
});
} catch (_err) {
updateDashboardGitState(dashboardId, {
status: "no_repo",
branch: null,
hasRepo: false,
hasChangesForCommit: false,
});
}
}
async function handleGitInit(dashboard) {
setGitBusy(dashboard.id, true);
try {
const configs = await ensureGitConfigs();
if (!configs.length) {
addToast($t.git?.no_servers_configured || "No Git config found", "error");
return;
}
const config = configs[0];
const defaultRemote = config?.default_repository
? `${String(config.url || "").replace(/\/$/, "")}/${config.default_repository}.git`
: "";
const remoteUrl = prompt($t.git?.remote_url || "Remote URL", defaultRemote);
if (!remoteUrl) return;
await gitService.initRepository(dashboard.id, config.id, remoteUrl.trim());
addToast($t.git?.init_success || "Repository initialized", "success");
await refreshDashboardGitState(dashboard.id);
} catch (err) {
addToast(err?.message || "Git init failed", "error");
} finally {
setGitBusy(dashboard.id, false);
}
}
async function handleGitSync(dashboard) {
setGitBusy(dashboard.id, true);
try {
await gitService.sync(dashboard.id, selectedEnv || null);
addToast($t.git?.sync_success || "Synced", "success");
await refreshDashboardGitState(dashboard.id);
} catch (err) {
addToast(err?.message || "Git sync failed", "error");
} finally {
setGitBusy(dashboard.id, false);
}
}
async function handleGitCommit(dashboard) {
if (!dashboard.git?.hasRepo) {
addToast($t.git?.not_linked || "Repository not linked", "error");
return;
}
if (!dashboard.git?.hasChangesForCommit) {
addToast($t.git?.nothing_to_commit || "No changes to commit", "error");
return;
}
const message = prompt(
$t.git?.commit_message || "Commit message",
`Update dashboard ${dashboard.title}`,
);
if (!message?.trim()) return;
setGitBusy(dashboard.id, true);
try {
await gitService.commit(dashboard.id, message.trim());
addToast($t.git?.commit_success || "Committed", "success");
await refreshDashboardGitState(dashboard.id);
} catch (err) {
addToast(err?.message || "Git commit failed", "error");
} finally {
setGitBusy(dashboard.id, false);
}
}
async function handleGitPull(dashboard) {
if (!dashboard.git?.hasRepo) return;
setGitBusy(dashboard.id, true);
try {
await gitService.pull(dashboard.id);
addToast($t.git?.pull_success || "Pulled", "success");
await refreshDashboardGitState(dashboard.id);
} catch (err) {
addToast(err?.message || "Git pull failed", "error");
} finally {
setGitBusy(dashboard.id, false);
}
}
async function handleGitPush(dashboard) {
if (!dashboard.git?.hasRepo) return;
setGitBusy(dashboard.id, true);
try {
await gitService.push(dashboard.id);
addToast($t.git?.push_success || "Pushed", "success");
await refreshDashboardGitState(dashboard.id);
} catch (err) {
addToast(err?.message || "Git push failed", "error");
} finally {
setGitBusy(dashboard.id, false);
}
}
// Get task status icon
function getTaskStatusIcon(status) {
if (!status) return "";
@@ -736,19 +886,24 @@
<!-- Git Status -->
<div class="col-span-2">
{#if dashboard.gitStatus}
<div class="flex flex-col gap-1">
<span
class="status-badge {getStatusBadgeClass(dashboard.gitStatus)}"
class="status-badge {dashboard.git?.hasRepo ? 'bg-blue-100 text-blue-800' : 'bg-gray-100 text-gray-700'}"
>
{#if dashboard.gitStatus.toLowerCase() === "ok"}
{$t.dashboard?.status_synced }
{:else if dashboard.gitStatus.toLowerCase() === "diff"}
! {$t.dashboard?.status_diff }
{/if}
{dashboard.git?.hasRepo
? ($t.dashboard?.status_repo || "Repo")
: ($t.dashboard?.status_no_repo || "No Repo")}
</span>
{:else}
<span class="text-gray-400">-</span>
{/if}
{#if dashboard.git?.hasRepo}
<span
class="status-badge {dashboard.git?.hasChangesForCommit ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800'}"
>
{dashboard.git?.hasChangesForCommit
? ($t.dashboard?.status_changes || "Diff")
: ($t.dashboard?.status_no_changes || "Synced")}
</span>
{/if}
</div>
</div>
<!-- Last Task -->
@@ -785,6 +940,73 @@
<!-- Actions -->
<div class="col-span-3 flex items-center">
<div class="flex items-center gap-1">
{#if !dashboard.git?.hasRepo}
<button
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
on:click={() => handleGitInit(dashboard)}
disabled={isGitBusy(dashboard.id)}
title={$t.git?.init_repo || "Init Git repository"}
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<path d="M12 5v14M5 12h14" />
</svg>
</button>
{:else}
<button
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
on:click={() => handleGitSync(dashboard)}
disabled={isGitBusy(dashboard.id)}
title={$t.git?.sync || "Sync from Superset"}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 12a9 9 0 1 1-3-6.7" />
<polyline points="21 3 21 9 15 9" />
</svg>
</button>
<button
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
on:click={() => handleGitCommit(dashboard)}
disabled={isGitBusy(dashboard.id) || !dashboard.git?.hasChangesForCommit}
title={$t.git?.commit || "Commit"}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 3v12" />
<path d="M7 10l5 5 5-5" />
<path d="M5 21h14" />
</svg>
</button>
<button
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
on:click={() => handleGitPull(dashboard)}
disabled={isGitBusy(dashboard.id)}
title={$t.git?.pull || "Pull"}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 3v12" />
<path d="M17 10l-5 5-5-5" />
<path d="M5 21h14" />
</svg>
</button>
<button
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
on:click={() => handleGitPush(dashboard)}
disabled={isGitBusy(dashboard.id)}
title={$t.git?.push || "Push"}
>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 21V9" />
<path d="M7 14l5-5 5 5" />
<path d="M5 3h14" />
</svg>
</button>
{/if}
<button
class="p-2 rounded border border-gray-200 hover:bg-gray-50 hover:border-gray-300 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
on:click={() => handleAction(dashboard, "migrate")}