Fix git/storage workflows: repos-only page, default dev branch, robust pull/push, and storage path resolution
This commit is contained in:
@@ -49,7 +49,24 @@
|
||||
if (!envId) return;
|
||||
fetchingDashboards = true;
|
||||
try {
|
||||
dashboards = await api.requestApi(`/environments/${envId}/dashboards`);
|
||||
const pageSize = 100;
|
||||
let page = 1;
|
||||
let aggregatedDashboards: DashboardMetadata[] = [];
|
||||
let totalPages = 1;
|
||||
|
||||
while (page <= totalPages) {
|
||||
const response = await api.requestApi(
|
||||
`/dashboards?env_id=${encodeURIComponent(envId)}&page=${page}&page_size=${pageSize}`,
|
||||
);
|
||||
const pageDashboards = Array.isArray(response?.dashboards)
|
||||
? response.dashboards
|
||||
: [];
|
||||
aggregatedDashboards = aggregatedDashboards.concat(pageDashboards);
|
||||
totalPages = Number(response?.total_pages || 1);
|
||||
page += 1;
|
||||
}
|
||||
|
||||
dashboards = aggregatedDashboards;
|
||||
} catch (e) {
|
||||
toast(e.message, 'error');
|
||||
dashboards = [];
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import RepositoryDashboardGrid from '../../../components/RepositoryDashboardGrid.svelte';
|
||||
import { addToast as toast } from '$lib/toasts.js';
|
||||
import { api } from '$lib/api.js';
|
||||
import { gitService } from '../../../services/gitService.js';
|
||||
import type { DashboardMetadata } from '$lib/types/dashboard';
|
||||
import { t } from '$lib/i18n';
|
||||
import { Button, Card, PageHeader, Select } from '$lib/ui';
|
||||
@@ -65,7 +66,24 @@
|
||||
if (!envId) return;
|
||||
fetchingDashboards = true;
|
||||
try {
|
||||
dashboards = await api.requestApi(`/environments/${envId}/dashboards`);
|
||||
const pageSize = 100;
|
||||
let page = 1;
|
||||
let aggregatedDashboards: DashboardMetadata[] = [];
|
||||
let totalPages = 1;
|
||||
|
||||
while (page <= totalPages) {
|
||||
const response = await api.requestApi(
|
||||
`/dashboards?env_id=${encodeURIComponent(envId)}&page=${page}&page_size=${pageSize}`,
|
||||
);
|
||||
const pageDashboards = Array.isArray(response?.dashboards)
|
||||
? response.dashboards
|
||||
: [];
|
||||
aggregatedDashboards = aggregatedDashboards.concat(pageDashboards);
|
||||
totalPages = Number(response?.total_pages || 1);
|
||||
page += 1;
|
||||
}
|
||||
|
||||
dashboards = await filterDashboardsWithRepositories(aggregatedDashboards);
|
||||
} catch (e) {
|
||||
toast(e.message, 'error');
|
||||
dashboards = [];
|
||||
@@ -75,6 +93,46 @@
|
||||
}
|
||||
// [/DEF:fetchDashboards:Function]
|
||||
|
||||
// [DEF:filterDashboardsWithRepositories:Function]
|
||||
/**
|
||||
* @PURPOSE: Keep only dashboards that already have initialized Git repositories.
|
||||
* @PRE: dashboards list is loaded for selected environment.
|
||||
* @POST: Returns dashboards with status != NO_REPO.
|
||||
*/
|
||||
async function filterDashboardsWithRepositories(
|
||||
allDashboards: DashboardMetadata[],
|
||||
): Promise<DashboardMetadata[]> {
|
||||
if (allDashboards.length === 0) return [];
|
||||
|
||||
const chunkSize = 50;
|
||||
const repositoryDashboardIds = new Set<number>();
|
||||
const allIds = allDashboards.map((dashboard) => dashboard.id);
|
||||
|
||||
for (let offset = 0; offset < allIds.length; offset += chunkSize) {
|
||||
const idsChunk = allIds.slice(offset, offset + chunkSize);
|
||||
try {
|
||||
const batchResponse = await gitService.getStatusesBatch(idsChunk);
|
||||
const statuses = batchResponse?.statuses || {};
|
||||
idsChunk.forEach((dashboardId) => {
|
||||
const status = statuses[dashboardId] || statuses[String(dashboardId)];
|
||||
const syncStatus = String(status?.sync_status || status?.sync_state || "").toUpperCase();
|
||||
if (syncStatus && syncStatus !== "NO_REPO") {
|
||||
repositoryDashboardIds.add(dashboardId);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`[StorageReposPage][Coherence:Failed] Failed to resolve repository statuses chunk: ${error?.message || error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return allDashboards.filter((dashboard) =>
|
||||
repositoryDashboardIds.has(dashboard.id),
|
||||
);
|
||||
}
|
||||
// [/DEF:filterDashboardsWithRepositories:Function]
|
||||
|
||||
onMount(fetchEnvironments);
|
||||
|
||||
$: environments = $environmentContextStore?.environments || [];
|
||||
@@ -107,13 +165,13 @@
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
</div>
|
||||
{:else}
|
||||
<Card title={$t.git?.select_dashboard }>
|
||||
<Card title={$t.nav?.repositories || "Репозитории"}>
|
||||
{#if fetchingDashboards}
|
||||
<p class="text-gray-500">{$t.common?.loading }</p>
|
||||
{:else if dashboards.length > 0}
|
||||
<RepositoryDashboardGrid {dashboards} statusMode="repository" />
|
||||
<RepositoryDashboardGrid {dashboards} statusMode="repository" envId={selectedEnvId || null} repositoriesOnly={true} />
|
||||
{:else}
|
||||
<p class="text-gray-500 italic">{$t.dashboard?.no_dashboards }</p>
|
||||
<p class="text-gray-500 italic">{$t.git?.no_repositories_selected || "Репозитории не найдены"}</p>
|
||||
{/if}
|
||||
</Card>
|
||||
{/if}
|
||||
|
||||
@@ -32,12 +32,35 @@
|
||||
let isLoading = false;
|
||||
let currentPath = '';
|
||||
let uploadCategory = 'backups';
|
||||
let uploadSubpath = '';
|
||||
|
||||
// [DEF:resolveStorageQueryFromPath:Function]
|
||||
/**
|
||||
* @purpose Splits UI path into storage API category and category-local subpath.
|
||||
* @pre uiPath may be empty or start with backups/repositorys.
|
||||
* @post Returns {category, subpath} compatible with /api/storage/files.
|
||||
*/
|
||||
function resolveStorageQueryFromPath(uiPath: string): { category?: string; subpath?: string } {
|
||||
const segments = String(uiPath || '').split('/').filter(Boolean);
|
||||
if (segments.length === 0) return {};
|
||||
const topLevel = segments[0];
|
||||
if (topLevel !== 'backups' && topLevel !== 'repositorys') {
|
||||
return {};
|
||||
}
|
||||
const subpath = segments.slice(1).join('/');
|
||||
return {
|
||||
category: topLevel,
|
||||
subpath: subpath || undefined,
|
||||
};
|
||||
}
|
||||
// [/DEF:resolveStorageQueryFromPath:Function]
|
||||
|
||||
async function loadFiles() {
|
||||
console.log('[STORAGE-PAGE][LOAD_START] path=%s', currentPath);
|
||||
isLoading = true;
|
||||
try {
|
||||
files = await listFiles(undefined, currentPath);
|
||||
const query = resolveStorageQueryFromPath(currentPath);
|
||||
files = await listFiles(query.category, query.subpath);
|
||||
console.log('[STORAGE-PAGE][LOAD_OK] count=%d', files.length);
|
||||
} catch (error) {
|
||||
console.log('[STORAGE-PAGE][LOAD_ERR] error=%s', error.message);
|
||||
@@ -108,8 +131,11 @@
|
||||
* @post uploadCategory is either backups or repositorys.
|
||||
*/
|
||||
function updateUploadCategory() {
|
||||
const [topLevel] = currentPath.split('/').filter(Boolean);
|
||||
const [topLevel, ...rest] = currentPath.split('/').filter(Boolean);
|
||||
uploadCategory = topLevel === 'repositorys' ? 'repositorys' : 'backups';
|
||||
uploadSubpath = topLevel === 'repositorys' || topLevel === 'backups'
|
||||
? rest.join('/')
|
||||
: '';
|
||||
}
|
||||
// [/DEF:updateUploadCategory:Function]
|
||||
|
||||
@@ -180,7 +206,7 @@
|
||||
<div class="lg:col-span-1">
|
||||
<FileUpload
|
||||
category={uploadCategory}
|
||||
path={currentPath}
|
||||
path={uploadSubpath}
|
||||
on:uploaded={loadFiles}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user