Fix git/storage workflows: repos-only page, default dev branch, robust pull/push, and storage path resolution
This commit is contained in:
@@ -21,7 +21,13 @@
|
||||
// [/SECTION]
|
||||
|
||||
// [SECTION: PROPS]
|
||||
let { dashboards = [], selectedIds = [], statusMode = "dashboard" } = $props();
|
||||
let {
|
||||
dashboards = [],
|
||||
selectedIds = [],
|
||||
statusMode = "dashboard",
|
||||
envId = null,
|
||||
repositoriesOnly = false,
|
||||
} = $props();
|
||||
|
||||
// [/SECTION]
|
||||
|
||||
@@ -44,9 +50,12 @@
|
||||
|
||||
// [SECTION: DERIVED]
|
||||
let filteredDashboards = $derived(
|
||||
dashboards.filter((d) =>
|
||||
d.title.toLowerCase().includes(filterText.toLowerCase()),
|
||||
),
|
||||
dashboards.filter((d) => {
|
||||
const matchesTitle = d.title.toLowerCase().includes(filterText.toLowerCase());
|
||||
if (!matchesTitle) return false;
|
||||
if (!repositoriesOnly || statusMode !== "repository") return true;
|
||||
return getRepositoryStatusToken(d.id) !== "no_repo";
|
||||
}),
|
||||
);
|
||||
|
||||
let sortedDashboards = $derived(
|
||||
@@ -318,7 +327,7 @@
|
||||
Array.from({ length: Math.min(concurrency, selectedDashboardIds.length) }, () => worker()),
|
||||
);
|
||||
invalidateRepositoryStatuses(selectedDashboardIds);
|
||||
const actionLabel = $t.git?.[`bulk_action_${actionToken}`];
|
||||
const actionLabel = $t.git?.[`bulk_action_${actionToken}`] || actionToken;
|
||||
addToast(
|
||||
$t.git?.bulk_result
|
||||
.replace("{action}", actionLabel)
|
||||
@@ -334,7 +343,7 @@
|
||||
|
||||
// [DEF:handleBulkSync:Function]
|
||||
async function handleBulkSync(): Promise<void> {
|
||||
await runBulkGitAction("sync", (dashboardId) => gitService.sync(dashboardId));
|
||||
await runBulkGitAction("sync", (dashboardId) => gitService.sync(dashboardId, null, envId));
|
||||
}
|
||||
// [/DEF:handleBulkSync:Function]
|
||||
|
||||
@@ -343,23 +352,37 @@
|
||||
const message = prompt($t.git?.commit_message);
|
||||
if (!message?.trim()) return;
|
||||
await runBulkGitAction("commit", (dashboardId) =>
|
||||
gitService.commit(dashboardId, message.trim(), []),
|
||||
gitService.commit(dashboardId, message.trim(), [], envId),
|
||||
);
|
||||
}
|
||||
// [/DEF:handleBulkCommit:Function]
|
||||
|
||||
// [DEF:handleBulkPull:Function]
|
||||
async function handleBulkPull(): Promise<void> {
|
||||
await runBulkGitAction("pull", (dashboardId) => gitService.pull(dashboardId));
|
||||
await runBulkGitAction("pull", (dashboardId) => gitService.pull(dashboardId, envId));
|
||||
}
|
||||
// [/DEF:handleBulkPull:Function]
|
||||
|
||||
// [DEF:handleBulkPush:Function]
|
||||
async function handleBulkPush(): Promise<void> {
|
||||
await runBulkGitAction("push", (dashboardId) => gitService.push(dashboardId));
|
||||
await runBulkGitAction("push", (dashboardId) => gitService.push(dashboardId, envId));
|
||||
}
|
||||
// [/DEF:handleBulkPush:Function]
|
||||
|
||||
// [DEF:handleBulkDelete:Function]
|
||||
// @PURPOSE: Removes selected repositories from storage and binding table.
|
||||
async function handleBulkDelete(): Promise<void> {
|
||||
if (!confirm($t.git?.confirm_delete_repo || "Удалить выбранные репозитории?")) return;
|
||||
const idsToDelete = [...selectedIds];
|
||||
await runBulkGitAction("delete", (dashboardId) =>
|
||||
gitService.deleteRepository(dashboardId, envId),
|
||||
);
|
||||
dashboards = dashboards.filter((dashboard) => !idsToDelete.includes(dashboard.id));
|
||||
selectedIds = [];
|
||||
dispatch("selectionChanged", []);
|
||||
}
|
||||
// [/DEF:handleBulkDelete:Function]
|
||||
|
||||
// [DEF:handleManageSelected:Function]
|
||||
// @PURPOSE: Opens Git manager for exactly one selected dashboard.
|
||||
async function handleManageSelected(): Promise<void> {
|
||||
@@ -372,13 +395,59 @@
|
||||
const selectedDashboard = dashboards.find(
|
||||
(dashboard) => dashboard.id === selectedDashboardId,
|
||||
);
|
||||
|
||||
gitDashboardId = String(selectedDashboard?.slug || selectedDashboardId);
|
||||
gitDashboardTitle = selectedDashboard?.title || "";
|
||||
showGitManager = true;
|
||||
openGitManagerForDashboard(selectedDashboard || null);
|
||||
}
|
||||
// [/DEF:handleManageSelected:Function]
|
||||
|
||||
// [DEF:resolveDashboardRef:Function]
|
||||
// @PURPOSE: Resolves dashboard slug from payload fields.
|
||||
// @PRE: Dashboard metadata is provided.
|
||||
// @POST: Returns slug string or null if unavailable.
|
||||
function resolveDashboardRef(dashboard: DashboardMetadata): string | null {
|
||||
const directSlug = String(
|
||||
dashboard.slug ||
|
||||
dashboard.dashboard_slug ||
|
||||
dashboard.url_slug ||
|
||||
"",
|
||||
).trim();
|
||||
if (directSlug) return directSlug;
|
||||
|
||||
const dashboardUrl = String(dashboard.url || "").trim();
|
||||
if (!dashboardUrl) return null;
|
||||
const slugMatch = dashboardUrl.match(/\/dashboard\/([^/?#]+)/i);
|
||||
if (!slugMatch?.[1]) return null;
|
||||
return decodeURIComponent(slugMatch[1]);
|
||||
}
|
||||
// [/DEF:resolveDashboardRef:Function]
|
||||
|
||||
// [DEF:openGitManagerForDashboard:Function]
|
||||
// @PURPOSE: Opens Git manager for provided dashboard metadata.
|
||||
function openGitManagerForDashboard(dashboard: DashboardMetadata | null): void {
|
||||
if (!dashboard) return;
|
||||
const dashboardRef = resolveDashboardRef(dashboard);
|
||||
if (!dashboardRef) {
|
||||
addToast($t.git?.select_dashboard_with_slug || "Dashboard slug is required to open GitManager", "error");
|
||||
return;
|
||||
}
|
||||
gitDashboardId = dashboardRef;
|
||||
gitDashboardTitle = dashboard.title || "";
|
||||
showGitManager = true;
|
||||
}
|
||||
// [/DEF:openGitManagerForDashboard:Function]
|
||||
|
||||
// [DEF:handleInitializeRepositories:Function]
|
||||
// @PURPOSE: Opens Git manager from bulk actions to initialize selected repository.
|
||||
async function handleInitializeRepositories(): Promise<void> {
|
||||
if (selectedIds.length !== 1) {
|
||||
addToast($t.git?.select_single_for_manage, "warning");
|
||||
return;
|
||||
}
|
||||
const selectedDashboardId = selectedIds[0];
|
||||
const selectedDashboard = dashboards.find((dashboard) => dashboard.id === selectedDashboardId) || null;
|
||||
openGitManagerForDashboard(selectedDashboard);
|
||||
}
|
||||
// [/DEF:handleInitializeRepositories:Function]
|
||||
|
||||
// [DEF:getSortStatusValue:Function]
|
||||
/**
|
||||
* @purpose Returns sort value for status column based on mode.
|
||||
@@ -450,27 +519,45 @@
|
||||
|
||||
{#if selectedIds.length > 0}
|
||||
<div class="mb-4 flex flex-wrap items-center gap-2 rounded-lg border border-blue-100 bg-blue-50/60 px-3 py-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onclick={handleManageSelected}
|
||||
disabled={bulkActionRunning || selectedIds.length !== 1}
|
||||
class="border-blue-200 bg-white text-blue-700 hover:bg-blue-50 disabled:opacity-40"
|
||||
>
|
||||
{$t.git?.manage_selected}
|
||||
</Button>
|
||||
{#if !repositoriesOnly}
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onclick={handleManageSelected}
|
||||
disabled={bulkActionRunning || selectedIds.length !== 1}
|
||||
class="border-blue-200 bg-white text-blue-700 hover:bg-blue-50 disabled:opacity-40"
|
||||
>
|
||||
{$t.git?.manage_selected}
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
onclick={handleInitializeRepositories}
|
||||
disabled={bulkActionRunning || selectedIds.length !== 1}
|
||||
class="border-slate-200 bg-white text-slate-700 hover:bg-slate-50 disabled:opacity-40"
|
||||
>
|
||||
{$t.git?.init_repo || "Инициализировать Git-репозиторий"}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button size="sm" variant="secondary" onclick={handleBulkSync} disabled={bulkActionRunning} class="border-blue-200 bg-white text-blue-700 hover:bg-blue-50">
|
||||
{$t.git?.bulk_sync}
|
||||
</Button>
|
||||
<Button size="sm" variant="secondary" onclick={handleBulkCommit} disabled={bulkActionRunning} class="border-amber-200 bg-white text-amber-700 hover:bg-amber-50">
|
||||
{$t.git?.bulk_commit}
|
||||
</Button>
|
||||
{#if !repositoriesOnly}
|
||||
<Button size="sm" variant="secondary" onclick={handleBulkCommit} disabled={bulkActionRunning} class="border-amber-200 bg-white text-amber-700 hover:bg-amber-50">
|
||||
{$t.git?.bulk_commit}
|
||||
</Button>
|
||||
{/if}
|
||||
<Button size="sm" variant="secondary" onclick={handleBulkPull} disabled={bulkActionRunning} class="border-cyan-200 bg-white text-cyan-700 hover:bg-cyan-50">
|
||||
{$t.git?.bulk_pull}
|
||||
</Button>
|
||||
<Button size="sm" variant="secondary" onclick={handleBulkPush} disabled={bulkActionRunning} class="border-indigo-200 bg-white text-indigo-700 hover:bg-indigo-50">
|
||||
{$t.git?.bulk_push}
|
||||
</Button>
|
||||
{#if repositoriesOnly}
|
||||
<Button size="sm" variant="secondary" onclick={handleBulkDelete} disabled={bulkActionRunning} class="border-rose-200 bg-white text-rose-700 hover:bg-rose-50">
|
||||
{$t.git?.delete_repo || "Удалить репозиторий"}
|
||||
</Button>
|
||||
{/if}
|
||||
<span class="ml-1 text-xs font-medium text-slate-600">
|
||||
{$t.git?.selected_count.replace(
|
||||
"{count}",
|
||||
@@ -545,10 +632,15 @@
|
||||
class="h-4 w-4 text-blue-600 border-gray-300 rounded focus:ring-blue-500"
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900"
|
||||
>{dashboard.title}</td
|
||||
>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
|
||||
<button
|
||||
type="button"
|
||||
class="text-left text-blue-700 hover:text-blue-900 hover:underline"
|
||||
onclick={() => openGitManagerForDashboard(dashboard)}
|
||||
>
|
||||
{dashboard.title}
|
||||
</button>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"
|
||||
>{new Date(dashboard.last_modified).toLocaleDateString()}</td
|
||||
>
|
||||
@@ -603,6 +695,7 @@
|
||||
{#if showGitManager && gitDashboardId}
|
||||
<GitManager
|
||||
dashboardId={gitDashboardId}
|
||||
envId={envId}
|
||||
dashboardTitle={gitDashboardTitle}
|
||||
bind:show={showGitManager}
|
||||
/>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
dashboardId,
|
||||
envId = null,
|
||||
dashboardTitle = '',
|
||||
show = false,
|
||||
show = $bindable(false),
|
||||
} = $props();
|
||||
// [/SECTION]
|
||||
|
||||
@@ -59,6 +59,8 @@
|
||||
let workspaceLoading = $state(false);
|
||||
let isPulling = $state(false);
|
||||
let isPushing = $state(false);
|
||||
let autoPushAfterCommit = $state(true);
|
||||
let repositoryProvider = $state('');
|
||||
// [/SECTION]
|
||||
|
||||
const hasWorkspaceChanges = $derived.by(() => {
|
||||
@@ -277,7 +279,12 @@
|
||||
committing = true;
|
||||
try {
|
||||
await gitService.commit(dashboardId, commitMessage, [], envId);
|
||||
toast($t.git?.commit_success || 'Коммит успешно создан', 'success');
|
||||
if (autoPushAfterCommit) {
|
||||
await gitService.push(dashboardId, envId);
|
||||
toast($t.git?.commit_and_push_success || 'Коммит создан и отправлен в remote', 'success');
|
||||
} else {
|
||||
toast($t.git?.commit_success || 'Коммит успешно создан', 'success');
|
||||
}
|
||||
commitMessage = '';
|
||||
await loadWorkspace();
|
||||
} catch (e) {
|
||||
@@ -420,6 +427,20 @@
|
||||
}
|
||||
// [/DEF:resolveDefaultConfig:Function]
|
||||
|
||||
// [DEF:resolvePushProviderLabel:Function]
|
||||
/**
|
||||
* @purpose Resolve lower-case provider label for auto-push checkbox.
|
||||
* @post Returns provider label, fallback "git".
|
||||
*/
|
||||
function resolvePushProviderLabel() {
|
||||
const selectedConfig = getSelectedConfig() || resolveDefaultConfig(configs);
|
||||
const provider = String(selectedConfig?.provider || repositoryProvider || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
return provider || 'git';
|
||||
}
|
||||
// [/DEF:resolvePushProviderLabel:Function]
|
||||
|
||||
// [DEF:buildSuggestedRepoName:Function]
|
||||
/**
|
||||
* @purpose Build deterministic repository name from dashboard title/id.
|
||||
@@ -490,6 +511,8 @@
|
||||
await gitService.initRepository(dashboardId, selectedConfigId, remoteUrl, envId);
|
||||
toast($t.git?.init_success || 'Репозиторий инициализирован', 'success');
|
||||
initialized = true;
|
||||
const selectedConfig = getSelectedConfig();
|
||||
repositoryProvider = selectedConfig?.provider || repositoryProvider;
|
||||
await loadWorkspace();
|
||||
} catch (e) {
|
||||
toast(e.message, 'error');
|
||||
@@ -522,7 +545,25 @@
|
||||
// [/DEF:handleBackdropClick:Function]
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
configs = await gitService.getConfigs();
|
||||
const defaultConfig = resolveDefaultConfig(configs);
|
||||
if (defaultConfig?.id) selectedConfigId = defaultConfig.id;
|
||||
} catch (_e) {
|
||||
configs = [];
|
||||
}
|
||||
|
||||
await Promise.all([checkStatus(), loadCurrentEnvironmentStage()]);
|
||||
|
||||
if (initialized) {
|
||||
try {
|
||||
const binding = await gitService.getRepositoryBinding(dashboardId, envId);
|
||||
repositoryProvider = binding?.provider || '';
|
||||
if (binding?.config_id) selectedConfigId = String(binding.config_id);
|
||||
} catch (_e) {
|
||||
repositoryProvider = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -676,6 +717,10 @@
|
||||
>
|
||||
Зафиксировать (Commit)
|
||||
</Button>
|
||||
<label class="flex items-center gap-2 text-xs text-slate-600">
|
||||
<input type="checkbox" bind:checked={autoPushAfterCommit} />
|
||||
{$t.git?.auto_push_after_commit || 'Сделать push после commit в'} {resolvePushProviderLabel()}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-[420px] flex-col overflow-hidden rounded-lg border border-slate-200 bg-slate-50">
|
||||
|
||||
Reference in New Issue
Block a user