303 lines
11 KiB
Svelte
303 lines
11 KiB
Svelte
<!-- [DEF:GitManager:Component] -->
|
||
<!--
|
||
@SEMANTICS: git, manager, dashboard, version_control, initialization
|
||
@PURPOSE: Центральный компонент для управления Git-операциями конкретного дашборда.
|
||
@LAYER: Component
|
||
@RELATION: USES -> BranchSelector
|
||
@RELATION: USES -> CommitModal
|
||
@RELATION: USES -> CommitHistory
|
||
@RELATION: USES -> DeploymentModal
|
||
@RELATION: USES -> ConflictResolver
|
||
@RELATION: CALLS -> gitService
|
||
-->
|
||
|
||
<script>
|
||
// [SECTION: IMPORTS]
|
||
import { onMount } from 'svelte';
|
||
import { gitService } from '../../services/gitService';
|
||
import { addToast as toast } from '../../lib/toasts.js';
|
||
import { t } from '../../lib/i18n';
|
||
import { Button, Card, PageHeader, Select, Input } from '../../lib/ui';
|
||
import BranchSelector from './BranchSelector.svelte';
|
||
import CommitModal from './CommitModal.svelte';
|
||
import CommitHistory from './CommitHistory.svelte';
|
||
import DeploymentModal from './DeploymentModal.svelte';
|
||
import ConflictResolver from './ConflictResolver.svelte';
|
||
// [/SECTION]
|
||
|
||
// [SECTION: PROPS]
|
||
let {
|
||
dashboardId,
|
||
dashboardTitle = "",
|
||
show = false,
|
||
} = $props();
|
||
|
||
// [/SECTION]
|
||
|
||
// [SECTION: STATE]
|
||
let currentBranch = 'main';
|
||
let showCommitModal = false;
|
||
let showDeployModal = false;
|
||
let showHistory = true;
|
||
let showConflicts = false;
|
||
let conflicts = [];
|
||
let loading = false;
|
||
let initialized = false;
|
||
let checkingStatus = true;
|
||
|
||
// Initialization form state
|
||
let configs = [];
|
||
let selectedConfigId = "";
|
||
let remoteUrl = "";
|
||
// [/SECTION]
|
||
|
||
// [DEF:checkStatus:Function]
|
||
/**
|
||
* @purpose Проверяет, инициализирован ли репозиторий для данного дашборда.
|
||
* @pre Component is mounted and has dashboardId.
|
||
* @post initialized state is set; configs loaded if not initialized.
|
||
*/
|
||
async function checkStatus() {
|
||
checkingStatus = true;
|
||
try {
|
||
// If we can get branches, it means repo exists
|
||
await gitService.getBranches(dashboardId);
|
||
initialized = true;
|
||
} catch (e) {
|
||
initialized = false;
|
||
// Load configs if not initialized
|
||
configs = await gitService.getConfigs();
|
||
if (configs.length > 0) selectedConfigId = configs[0].id;
|
||
} finally {
|
||
checkingStatus = false;
|
||
}
|
||
}
|
||
// [/DEF:checkStatus:Function]
|
||
|
||
// [DEF:handleInit:Function]
|
||
/**
|
||
* @purpose Инициализирует репозиторий для дашборда.
|
||
* @pre selectedConfigId and remoteUrl are provided.
|
||
* @post Repository is created on backend; initialized set to true.
|
||
*/
|
||
async function handleInit() {
|
||
if (!selectedConfigId || !remoteUrl) {
|
||
toast('Please select a Git server and provide remote URL', 'error');
|
||
return;
|
||
}
|
||
loading = true;
|
||
try {
|
||
await gitService.initRepository(dashboardId, selectedConfigId, remoteUrl);
|
||
toast('Repository initialized successfully', 'success');
|
||
initialized = true;
|
||
} catch (e) {
|
||
toast(e.message, 'error');
|
||
} finally {
|
||
loading = false;
|
||
}
|
||
}
|
||
// [/DEF:handleInit:Function]
|
||
|
||
// [DEF:handleSync:Function]
|
||
/**
|
||
* @purpose Синхронизирует состояние Superset с локальным Git-репозиторием.
|
||
* @pre Repository is initialized.
|
||
* @post Dashboard YAMLs are exported to Git and staged.
|
||
*/
|
||
async function handleSync() {
|
||
loading = true;
|
||
try {
|
||
// Try to get selected environment from localStorage (set by EnvSelector)
|
||
const sourceEnvId = localStorage.getItem('selected_env_id');
|
||
await gitService.sync(dashboardId, sourceEnvId);
|
||
toast('Dashboard state synced to Git', 'success');
|
||
} catch (e) {
|
||
toast(e.message, 'error');
|
||
} finally {
|
||
loading = false;
|
||
}
|
||
}
|
||
// [/DEF:handleSync:Function]
|
||
|
||
// [DEF:handlePush:Function]
|
||
/**
|
||
* @purpose Pushes local commits to the remote repository.
|
||
* @pre Repository is initialized and has commits.
|
||
* @post Changes are pushed to origin.
|
||
*/
|
||
async function handlePush() {
|
||
loading = true;
|
||
try {
|
||
await gitService.push(dashboardId);
|
||
toast('Changes pushed to remote', 'success');
|
||
} catch (e) {
|
||
toast(e.message, 'error');
|
||
} finally {
|
||
loading = false;
|
||
}
|
||
}
|
||
// [/DEF:handlePush:Function]
|
||
|
||
// [DEF:handlePull:Function]
|
||
/**
|
||
* @purpose Pulls changes from the remote repository.
|
||
* @pre Repository is initialized.
|
||
* @post Local branch is updated with remote changes.
|
||
*/
|
||
async function handlePull() {
|
||
loading = true;
|
||
try {
|
||
await gitService.pull(dashboardId);
|
||
toast('Changes pulled from remote', 'success');
|
||
} catch (e) {
|
||
toast(e.message, 'error');
|
||
} finally {
|
||
loading = false;
|
||
}
|
||
}
|
||
// [/DEF:handlePull:Function]
|
||
|
||
onMount(checkStatus);
|
||
</script>
|
||
|
||
<!-- [SECTION: TEMPLATE] -->
|
||
{#if show}
|
||
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||
<div class="bg-white p-6 rounded-lg shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-y-auto">
|
||
<PageHeader title="{$t.git.management}: {dashboardTitle}">
|
||
<div slot="subtitle" class="text-sm text-gray-500">ID: {dashboardId}</div>
|
||
<div slot="actions">
|
||
<button on:click={() => show = false} class="text-gray-400 hover:text-gray-600 transition-colors">
|
||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</PageHeader>
|
||
|
||
{#if checkingStatus}
|
||
<div class="flex justify-center py-12">
|
||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||
</div>
|
||
{:else if !initialized}
|
||
<div class="max-w-md mx-auto py-8">
|
||
<Card>
|
||
<p class="text-sm text-gray-600 mb-6">
|
||
{$t.git.not_linked}
|
||
</p>
|
||
|
||
<div class="space-y-6">
|
||
<Select
|
||
label={$t.git.server}
|
||
bind:value={selectedConfigId}
|
||
options={configs.map(c => ({ value: c.id, label: `${c.name} (${c.provider})` }))}
|
||
/>
|
||
{#if configs.length === 0}
|
||
<p class="text-xs text-red-500 -mt-4">No Git servers configured. Go to Settings -> Git to add one.</p>
|
||
{/if}
|
||
|
||
<Input
|
||
label={$t.git.remote_url}
|
||
bind:value={remoteUrl}
|
||
placeholder="https://github.com/org/repo.git"
|
||
/>
|
||
|
||
<Button
|
||
on:click={handleInit}
|
||
disabled={loading || configs.length === 0}
|
||
isLoading={loading}
|
||
class="w-full"
|
||
>
|
||
{$t.git.init_repo}
|
||
</Button>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
{:else}
|
||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||
<!-- Left Column: Controls -->
|
||
<div class="md:col-span-1 space-y-6">
|
||
<section>
|
||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">{$t.git.branch}</h3>
|
||
<BranchSelector {dashboardId} bind:currentBranch />
|
||
</section>
|
||
|
||
<section class="space-y-3">
|
||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">{$t.git.actions}</h3>
|
||
<Button
|
||
variant="secondary"
|
||
on:click={handleSync}
|
||
disabled={loading}
|
||
class="w-full"
|
||
>
|
||
{$t.git.sync}
|
||
</Button>
|
||
<Button
|
||
on:click={() => showCommitModal = true}
|
||
disabled={loading}
|
||
class="w-full"
|
||
>
|
||
{$t.git.commit}
|
||
</Button>
|
||
<div class="grid grid-cols-2 gap-3">
|
||
<Button
|
||
variant="ghost"
|
||
on:click={handlePull}
|
||
disabled={loading}
|
||
class="border border-gray-200"
|
||
>
|
||
{$t.git.pull}
|
||
</Button>
|
||
<Button
|
||
variant="ghost"
|
||
on:click={handlePush}
|
||
disabled={loading}
|
||
class="border border-gray-200"
|
||
>
|
||
{$t.git.push}
|
||
</Button>
|
||
</div>
|
||
</section>
|
||
|
||
<section>
|
||
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-3">{$t.git.deployment}</h3>
|
||
<Button
|
||
variant="primary"
|
||
on:click={() => showDeployModal = true}
|
||
disabled={loading}
|
||
class="w-full bg-green-600 hover:bg-green-700 focus-visible:ring-green-500"
|
||
>
|
||
{$t.git.deploy}
|
||
</Button>
|
||
</section>
|
||
</div>
|
||
|
||
<!-- Right Column: History -->
|
||
<div class="md:col-span-2 border-l pl-6">
|
||
<CommitHistory {dashboardId} />
|
||
</div>
|
||
</div>
|
||
{/if}
|
||
</div>
|
||
</div>
|
||
{/if}
|
||
|
||
<CommitModal
|
||
{dashboardId}
|
||
bind:show={showCommitModal}
|
||
on:commit={() => { /* Refresh history */ }}
|
||
/>
|
||
|
||
<DeploymentModal
|
||
{dashboardId}
|
||
bind:show={showDeployModal}
|
||
/>
|
||
|
||
<ConflictResolver
|
||
{conflicts}
|
||
bind:show={showConflicts}
|
||
on:resolve={() => { /* Handle resolution */ }}
|
||
/>
|
||
<!-- [/SECTION] -->
|
||
|
||
<!-- [/DEF:GitManager:Component] --> |