css refactor

This commit is contained in:
2026-02-19 18:24:36 +03:00
parent 0064b30b0f
commit 59c6c08bda
45 changed files with 1798 additions and 1857 deletions

View File

@@ -19,8 +19,11 @@
// [/SECTION]
// [SECTION: PROPS]
export let dashboardId;
export let currentBranch = 'main';
let {
dashboardId,
currentBranch = 'main',
} = $props();
// [/SECTION]
// [SECTION: STATE]

View File

@@ -16,7 +16,10 @@
// [/SECTION]
// [SECTION: PROPS]
export let dashboardId;
let {
dashboardId,
} = $props();
// [/SECTION]
// [SECTION: STATE]

View File

@@ -12,22 +12,22 @@
<script>
// [SECTION: IMPORTS]
import { createEventDispatcher, onMount } from 'svelte';
import { gitService } from '../../services/gitService';
import { addToast as toast } from '../../lib/toasts.js';
import { api } from '../../lib/api';
import { createEventDispatcher, onMount } from "svelte";
import { gitService } from "../../services/gitService";
import { addToast as toast } from "../../lib/toasts.js";
import { api } from "../../lib/api";
// [/SECTION]
// [SECTION: PROPS]
export let dashboardId;
export let show = false;
let { dashboardId, show = false } = $props();
// [/SECTION]
// [SECTION: STATE]
let message = '';
let message = "";
let committing = false;
let status = null;
let diff = '';
let diff = "";
let loading = false;
let generatingMessage = false;
// [/SECTION]
@@ -41,14 +41,18 @@
async function handleGenerateMessage() {
generatingMessage = true;
try {
console.log(`[CommitModal][Action] Generating commit message for dashboard ${dashboardId}`);
console.log(
`[CommitModal][Action] Generating commit message for dashboard ${dashboardId}`,
);
// postApi returns the JSON data directly or throws an error
const data = await api.postApi(`/git/repositories/${dashboardId}/generate-message`);
const data = await api.postApi(
`/git/repositories/${dashboardId}/generate-message`,
);
message = data.message;
toast('Commit message generated', 'success');
toast("Commit message generated", "success");
} catch (e) {
console.error(`[CommitModal][Coherence:Failed] ${e.message}`);
toast(e.message || 'Failed to generate message', 'error');
toast(e.message || "Failed to generate message", "error");
} finally {
generatingMessage = false;
}
@@ -64,20 +68,32 @@
if (!dashboardId || !show) return;
loading = true;
try {
console.log(`[CommitModal][Action] Loading status and diff for ${dashboardId}`);
console.log(
`[CommitModal][Action] Loading status and diff for ${dashboardId}`,
);
status = await gitService.getStatus(dashboardId);
// Fetch both unstaged and staged diffs to show complete picture
const unstagedDiff = await gitService.getDiff(dashboardId, null, false);
const stagedDiff = await gitService.getDiff(dashboardId, null, true);
const unstagedDiff = await gitService.getDiff(
dashboardId,
null,
false,
);
const stagedDiff = await gitService.getDiff(
dashboardId,
null,
true,
);
diff = "";
if (stagedDiff) diff += "--- STAGED CHANGES ---\n" + stagedDiff + "\n\n";
if (unstagedDiff) diff += "--- UNSTAGED CHANGES ---\n" + unstagedDiff;
if (stagedDiff)
diff += "--- STAGED CHANGES ---\n" + stagedDiff + "\n\n";
if (unstagedDiff)
diff += "--- UNSTAGED CHANGES ---\n" + unstagedDiff;
if (!diff) diff = "";
} catch (e) {
console.error(`[CommitModal][Coherence:Failed] ${e.message}`);
toast('Failed to load changes', 'error');
toast("Failed to load changes", "error");
} finally {
loading = false;
}
@@ -92,39 +108,50 @@
*/
async function handleCommit() {
if (!message) return;
console.log(`[CommitModal][Action] Committing changes for dashboard ${dashboardId}`);
console.log(
`[CommitModal][Action] Committing changes for dashboard ${dashboardId}`,
);
committing = true;
try {
await gitService.commit(dashboardId, message, []);
toast('Changes committed successfully', 'success');
dispatch('commit');
toast("Changes committed successfully", "success");
dispatch("commit");
show = false;
message = '';
message = "";
console.log(`[CommitModal][Coherence:OK] Committed`);
} catch (e) {
console.error(`[CommitModal][Coherence:Failed] ${e.message}`);
toast(e.message, 'error');
toast(e.message, "error");
} finally {
committing = false;
}
}
// [/DEF:handleCommit:Function]
$: if (show) loadStatus();
$effect(() => {
if (show) loadStatus();
});
</script>
<!-- [SECTION: TEMPLATE] -->
{#if show}
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] flex flex-col">
<div
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
>
<div
class="bg-white p-6 rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] flex flex-col"
>
<h2 class="text-xl font-bold mb-4">Commit Changes</h2>
<div class="flex flex-col md:flex-row gap-4 flex-1 overflow-hidden">
<!-- Left: Message and Files -->
<div class="w-full md:w-1/3 flex flex-col">
<div class="mb-4">
<div class="flex justify-between items-center mb-1">
<label class="block text-sm font-medium text-gray-700">Commit Message</label>
<label
class="block text-sm font-medium text-gray-700"
>Commit Message</label
>
<button
on:click={handleGenerateMessage}
disabled={generatingMessage || loading}
@@ -146,21 +173,37 @@
{#if status}
<div class="flex-1 overflow-y-auto">
<h3 class="text-sm font-bold text-gray-500 uppercase mb-2">Changed Files</h3>
<h3
class="text-sm font-bold text-gray-500 uppercase mb-2"
>
Changed Files
</h3>
<ul class="text-xs space-y-1">
{#each status.staged_files as file}
<li class="text-green-600 flex items-center font-semibold" title="Staged">
<span class="mr-2">S</span> {file}
<li
class="text-green-600 flex items-center font-semibold"
title="Staged"
>
<span class="mr-2">S</span>
{file}
</li>
{/each}
{#each status.modified_files as file}
<li class="text-yellow-600 flex items-center" title="Modified (Unstaged)">
<span class="mr-2">M</span> {file}
<li
class="text-yellow-600 flex items-center"
title="Modified (Unstaged)"
>
<span class="mr-2">M</span>
{file}
</li>
{/each}
{#each status.untracked_files as file}
<li class="text-blue-600 flex items-center" title="Untracked">
<span class="mr-2">?</span> {file}
<li
class="text-blue-600 flex items-center"
title="Untracked"
>
<span class="mr-2">?</span>
{file}
</li>
{/each}
</ul>
@@ -169,33 +212,52 @@
</div>
<!-- Right: Diff Viewer -->
<div class="w-full md:w-2/3 flex flex-col overflow-hidden border rounded bg-gray-50">
<div class="bg-gray-200 px-3 py-1 text-xs font-bold text-gray-600 border-b">Changes Preview</div>
<div
class="w-full md:w-2/3 flex flex-col overflow-hidden border rounded bg-gray-50"
>
<div
class="bg-gray-200 px-3 py-1 text-xs font-bold text-gray-600 border-b"
>
Changes Preview
</div>
<div class="flex-1 overflow-auto p-2">
{#if loading}
<div class="flex items-center justify-center h-full text-gray-500">Loading diff...</div>
<div
class="flex items-center justify-center h-full text-gray-500"
>
Loading diff...
</div>
{:else if diff}
<pre class="text-xs font-mono whitespace-pre-wrap">{diff}</pre>
<pre
class="text-xs font-mono whitespace-pre-wrap">{diff}</pre>
{:else}
<div class="flex items-center justify-center h-full text-gray-500 italic">No changes detected</div>
<div
class="flex items-center justify-center h-full text-gray-500 italic"
>
No changes detected
</div>
{/if}
</div>
</div>
</div>
<div class="flex justify-end space-x-3 mt-6 pt-4 border-t">
<button
on:click={() => show = false}
<button
on:click={() => (show = false)}
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
>
Cancel
</button>
<button
on:click={handleCommit}
disabled={committing || !message || loading || (!status?.is_dirty && status?.staged_files?.length === 0)}
disabled={committing ||
!message ||
loading ||
(!status?.is_dirty &&
status?.staged_files?.length === 0)}
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
>
{committing ? 'Committing...' : 'Commit'}
{committing ? "Committing..." : "Commit"}
</button>
</div>
</div>
@@ -203,10 +265,4 @@
{/if}
<!-- [/SECTION] -->
<style>
pre {
tab-size: 4;
}
</style>
<!-- [/DEF:CommitModal:Component] -->
<!-- [/DEF:CommitModal:Component] -->

View File

@@ -10,20 +10,23 @@
<script>
// [SECTION: IMPORTS]
import { createEventDispatcher } from 'svelte';
import { addToast as toast } from '../../lib/toasts.js';
import { createEventDispatcher } from "svelte";
import { addToast as toast } from "../../lib/toasts.js";
// [/SECTION]
// [SECTION: PROPS]
/** @type {Array<{file_path: string, mine: string, theirs: string}>} */
export let conflicts = [];
export let show = false;
let {
conflicts = [],
show = false,
} = $props();
// [/SECTION]
// [SECTION: STATE]
const dispatch = createEventDispatcher();
/** @type {Object.<string, 'mine' | 'theirs' | 'manual'>} */
let resolutions = {};
let resolutions = {};
// [/SECTION]
// [DEF:resolve:Function]
@@ -36,7 +39,9 @@
* @side_effect Updates resolutions state.
*/
function resolve(file, strategy) {
console.log(`[ConflictResolver][Action] Resolving ${file} with ${strategy}`);
console.log(
`[ConflictResolver][Action] Resolving ${file} with ${strategy}`,
);
resolutions[file] = strategy;
resolutions = { ...resolutions }; // Trigger update
}
@@ -51,16 +56,21 @@
*/
function handleSave() {
// 1. Guard Clause (@PRE)
const unresolved = conflicts.filter(c => !resolutions[c.file_path]);
const unresolved = conflicts.filter((c) => !resolutions[c.file_path]);
if (unresolved.length > 0) {
console.warn(`[ConflictResolver][Coherence:Failed] ${unresolved.length} unresolved conflicts`);
toast(`Please resolve all conflicts first. (${unresolved.length} remaining)`, 'error');
console.warn(
`[ConflictResolver][Coherence:Failed] ${unresolved.length} unresolved conflicts`,
);
toast(
`Please resolve all conflicts first. (${unresolved.length} remaining)`,
"error",
);
return;
}
// 2. Implementation
console.log(`[ConflictResolver][Coherence:OK] All conflicts resolved`);
dispatch('resolve', resolutions);
dispatch("resolve", resolutions);
show = false;
}
// [/DEF:handleSave:Function]
@@ -68,43 +78,78 @@
<!-- [SECTION: TEMPLATE] -->
{#if show}
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4">
<div class="bg-white p-6 rounded-lg shadow-xl w-full max-w-5xl max-h-[90vh] flex flex-col">
<h2 class="text-xl font-bold mb-4 text-red-600">Merge Conflicts Detected</h2>
<p class="text-gray-600 mb-4">The following files have conflicts. Please choose how to resolve them.</p>
<div
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4"
>
<div
class="bg-white p-6 rounded-lg shadow-xl w-full max-w-5xl max-h-[90vh] flex flex-col"
>
<h2 class="text-xl font-bold mb-4 text-red-600">
Merge Conflicts Detected
</h2>
<p class="text-gray-600 mb-4">
The following files have conflicts. Please choose how to resolve
them.
</p>
<div class="flex-1 overflow-y-auto space-y-6 mb-4 pr-2">
{#each conflicts as conflict}
<div class="border rounded-lg overflow-hidden">
<div class="bg-gray-100 px-4 py-2 font-medium border-b flex justify-between items-center">
<div
class="bg-gray-100 px-4 py-2 font-medium border-b flex justify-between items-center"
>
<span>{conflict.file_path}</span>
{#if resolutions[conflict.file_path]}
<span class="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full uppercase font-bold">
<span
class="text-xs bg-blue-100 text-blue-700 px-2 py-0.5 rounded-full uppercase font-bold"
>
Resolved: {resolutions[conflict.file_path]}
</span>
{/if}
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-0 divide-x">
<div
class="grid grid-cols-1 md:grid-cols-2 gap-0 divide-x"
>
<div class="p-0 flex flex-col">
<div class="bg-blue-50 px-4 py-1 text-[10px] font-bold text-blue-600 uppercase border-b">Your Changes (Mine)</div>
<div class="p-4 bg-white flex-1 overflow-auto">
<pre class="text-xs font-mono whitespace-pre">{conflict.mine}</pre>
<div
class="bg-blue-50 px-4 py-1 text-[10px] font-bold text-blue-600 uppercase border-b"
>
Your Changes (Mine)
</div>
<button
class="w-full py-2 text-sm font-medium border-t transition-colors {resolutions[conflict.file_path] === 'mine' ? 'bg-blue-600 text-white' : 'bg-gray-50 hover:bg-blue-50 text-blue-600'}"
on:click={() => resolve(conflict.file_path, 'mine')}
<div class="p-4 bg-white flex-1 overflow-auto">
<pre
class="text-xs font-mono whitespace-pre">{conflict.mine}</pre>
</div>
<button
class="w-full py-2 text-sm font-medium border-t transition-colors {resolutions[
conflict.file_path
] === 'mine'
? 'bg-blue-600 text-white'
: 'bg-gray-50 hover:bg-blue-50 text-blue-600'}"
on:click={() =>
resolve(conflict.file_path, "mine")}
>
Keep Mine
</button>
</div>
<div class="p-0 flex flex-col">
<div class="bg-green-50 px-4 py-1 text-[10px] font-bold text-green-600 uppercase border-b">Remote Changes (Theirs)</div>
<div class="p-4 bg-white flex-1 overflow-auto">
<pre class="text-xs font-mono whitespace-pre">{conflict.theirs}</pre>
<div
class="bg-green-50 px-4 py-1 text-[10px] font-bold text-green-600 uppercase border-b"
>
Remote Changes (Theirs)
</div>
<button
class="w-full py-2 text-sm font-medium border-t transition-colors {resolutions[conflict.file_path] === 'theirs' ? 'bg-green-600 text-white' : 'bg-gray-50 hover:bg-green-50 text-green-600'}"
on:click={() => resolve(conflict.file_path, 'theirs')}
<div class="p-4 bg-white flex-1 overflow-auto">
<pre
class="text-xs font-mono whitespace-pre">{conflict.theirs}</pre>
</div>
<button
class="w-full py-2 text-sm font-medium border-t transition-colors {resolutions[
conflict.file_path
] === 'theirs'
? 'bg-green-600 text-white'
: 'bg-gray-50 hover:bg-green-50 text-green-600'}"
on:click={() =>
resolve(conflict.file_path, "theirs")}
>
Keep Theirs
</button>
@@ -115,13 +160,13 @@
</div>
<div class="flex justify-end space-x-3 pt-4 border-t">
<button
on:click={() => show = false}
<button
on:click={() => (show = false)}
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded transition-colors"
>
Cancel
</button>
<button
<button
on:click={handleSave}
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors shadow-sm"
>
@@ -133,10 +178,4 @@
{/if}
<!-- [/SECTION] -->
<style>
pre {
tab-size: 4;
}
</style>
<!-- [/DEF:ConflictResolver:Component] -->
<!-- [/DEF:ConflictResolver:Component] -->

View File

@@ -11,19 +11,19 @@
<script>
// [SECTION: IMPORTS]
import { onMount, createEventDispatcher } from 'svelte';
import { gitService } from '../../services/gitService';
import { addToast as toast } from '../../lib/toasts.js';
import { onMount, createEventDispatcher } from "svelte";
import { gitService } from "../../services/gitService";
import { addToast as toast } from "../../lib/toasts.js";
// [/SECTION]
// [SECTION: PROPS]
export let dashboardId;
export let show = false;
let { dashboardId, show = false } = $props();
// [/SECTION]
// [SECTION: STATE]
let environments = [];
let selectedEnv = '';
let selectedEnv = "";
let loading = false;
let deploying = false;
// [/SECTION]
@@ -31,7 +31,9 @@
const dispatch = createEventDispatcher();
// [DEF:loadStatus:Watcher]
$: if (show) loadEnvironments();
$effect(() => {
if (show) loadEnvironments();
});
// [/DEF:loadStatus:Watcher]
// [DEF:loadEnvironments:Function]
@@ -48,10 +50,12 @@
if (environments.length > 0) {
selectedEnv = environments[0].id;
}
console.log(`[DeploymentModal][Coherence:OK] Loaded ${environments.length} environments`);
console.log(
`[DeploymentModal][Coherence:OK] Loaded ${environments.length} environments`,
);
} catch (e) {
console.error(`[DeploymentModal][Coherence:Failed] ${e.message}`);
toast('Failed to load environments', 'error');
toast("Failed to load environments", "error");
} finally {
loading = false;
}
@@ -71,13 +75,16 @@
deploying = true;
try {
const result = await gitService.deploy(dashboardId, selectedEnv);
toast(result.message || 'Deployment triggered successfully', 'success');
dispatch('deploy');
toast(
result.message || "Deployment triggered successfully",
"success",
);
dispatch("deploy");
show = false;
console.log(`[DeploymentModal][Coherence:OK] Deployment triggered`);
} catch (e) {
console.error(`[DeploymentModal][Coherence:Failed] ${e.message}`);
toast(e.message, 'error');
toast(e.message, "error");
} finally {
deploying = false;
}
@@ -87,17 +94,21 @@
<!-- [SECTION: TEMPLATE] -->
{#if show}
<div class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<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-xl w-96">
<h2 class="text-xl font-bold mb-4">Deploy Dashboard</h2>
{#if loading}
<p class="text-gray-500">Loading environments...</p>
{:else if environments.length === 0}
<p class="text-red-500 mb-4">No deployment environments configured.</p>
<p class="text-red-500 mb-4">
No deployment environments configured.
</p>
<div class="flex justify-end">
<button
on:click={() => show = false}
<button
on:click={() => (show = false)}
class="px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300"
>
Close
@@ -105,33 +116,53 @@
</div>
{:else}
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Select Target Environment</label>
<select
<label class="block text-sm font-medium text-gray-700 mb-2"
>Select Target Environment</label
>
<select
bind:value={selectedEnv}
class="w-full border rounded p-2 focus:ring-2 focus:ring-blue-500 outline-none bg-white"
>
{#each environments as env}
<option value={env.id}>{env.name} ({env.superset_url})</option>
<option value={env.id}
>{env.name} ({env.superset_url})</option
>
{/each}
</select>
</div>
<div class="flex justify-end space-x-3">
<button
on:click={() => show = false}
<button
on:click={() => (show = false)}
class="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded"
>
Cancel
</button>
<button
<button
on:click={handleDeploy}
disabled={deploying || !selectedEnv}
class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:opacity-50 flex items-center"
>
{#if deploying}
<svg class="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
<svg
class="animate-spin -ml-1 mr-2 h-4 w-4 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
class="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
stroke-width="4"
></circle>
<path
class="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Deploying...
{:else}
@@ -145,4 +176,4 @@
{/if}
<!-- [/SECTION] -->
<!-- [/DEF:DeploymentModal:Component] -->
<!-- [/DEF:DeploymentModal:Component] -->

View File

@@ -26,9 +26,12 @@
// [/SECTION]
// [SECTION: PROPS]
export let dashboardId;
export let dashboardTitle = "";
export let show = false;
let {
dashboardId,
dashboardTitle = "",
show = false,
} = $props();
// [/SECTION]
// [SECTION: STATE]