Files
ss-tools/frontend/src/components/git/GitManager.svelte

284 lines
11 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- [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 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]
export let dashboardId;
export let dashboardTitle = "";
export let show = false;
// [/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 Проверяет, инициализирован ли репозиторий для данного дашборда.
*/
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 Инициализирует репозиторий для дашборда.
*/
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-репозиторием.
*/
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]
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]
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">
<div class="flex justify-between items-center mb-6 border-b pb-4">
<div>
<h2 class="text-2xl font-bold">Git Management: {dashboardTitle}</h2>
<p class="text-sm text-gray-500">ID: {dashboardId}</p>
</div>
<button on:click={() => show = false} class="text-gray-500 hover:text-gray-700">
<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>
{#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">
<div class="bg-blue-50 border-l-4 border-blue-400 p-4 mb-6">
<p class="text-sm text-blue-700">
This dashboard is not yet linked to a Git repository.
Please configure the repository details below.
</p>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700">Git Server</label>
<select bind:value={selectedConfigId} class="mt-1 block w-full border rounded p-2">
{#each configs as config}
<option value={config.id}>{config.name} ({config.provider})</option>
{/each}
</select>
{#if configs.length === 0}
<p class="text-xs text-red-500 mt-1">No Git servers configured. Go to Settings -> Git to add one.</p>
{/if}
</div>
<div>
<label class="block text-sm font-medium text-gray-700">Remote Repository URL</label>
<input
type="text"
bind:value={remoteUrl}
placeholder="https://github.com/org/repo.git"
class="mt-1 block w-full border rounded p-2"
/>
</div>
<button
on:click={handleInit}
disabled={loading || configs.length === 0}
class="w-full bg-blue-600 text-white py-2 rounded font-medium hover:bg-blue-700 disabled:opacity-50"
>
{loading ? 'Initializing...' : 'Initialize Repository'}
</button>
</div>
</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-2">Branch</h3>
<BranchSelector {dashboardId} bind:currentBranch />
</section>
<section class="space-y-2">
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-2">Actions</h3>
<button
on:click={handleSync}
disabled={loading}
class="w-full flex items-center justify-center px-4 py-2 bg-gray-100 hover:bg-gray-200 rounded text-sm font-medium transition"
>
Sync from Superset
</button>
<button
on:click={() => showCommitModal = true}
disabled={loading}
class="w-full flex items-center justify-center px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded text-sm font-medium transition"
>
Commit Changes
</button>
<div class="grid grid-cols-2 gap-2">
<button
on:click={handlePull}
disabled={loading}
class="flex items-center justify-center px-4 py-2 border hover:bg-gray-50 rounded text-sm font-medium transition"
>
Pull
</button>
<button
on:click={handlePush}
disabled={loading}
class="flex items-center justify-center px-4 py-2 border hover:bg-gray-50 rounded text-sm font-medium transition"
>
Push
</button>
</div>
</section>
<section>
<h3 class="text-sm font-semibold text-gray-400 uppercase tracking-wider mb-2">Deployment</h3>
<button
on:click={() => showDeployModal = true}
disabled={loading}
class="w-full flex items-center justify-center px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded text-sm font-medium transition"
>
Deploy to Environment
</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] -->