slug first logic

This commit is contained in:
2026-03-01 13:17:05 +03:00
parent 0a09fb5e6d
commit 73cd29a237
20 changed files with 1739 additions and 379 deletions

View File

@@ -21,6 +21,7 @@
// [SECTION: PROPS]
let {
dashboardId,
envId = null,
currentBranch = 'main',
} = $props();
@@ -56,7 +57,7 @@
console.log(`[BranchSelector][Action] Loading branches for dashboard ${dashboardId}`);
loading = true;
try {
branches = await gitService.getBranches(dashboardId);
branches = await gitService.getBranches(dashboardId, envId);
console.log(`[BranchSelector][Coherence:OK] Loaded ${branches.length} branches`);
} catch (e) {
console.error(`[BranchSelector][Coherence:Failed] ${e.message}`);
@@ -87,7 +88,7 @@
async function handleCheckout(branchName) {
console.log(`[BranchSelector][Action] Checking out branch ${branchName}`);
try {
await gitService.checkoutBranch(dashboardId, branchName);
await gitService.checkoutBranch(dashboardId, branchName, envId);
currentBranch = branchName;
dispatch('change', { branch: branchName });
toast($t.git?.switched_to?.replace('{branch}', branchName), 'success');
@@ -109,7 +110,7 @@
if (!newBranchName) return;
console.log(`[BranchSelector][Action] Creating branch ${newBranchName} from ${currentBranch}`);
try {
await gitService.createBranch(dashboardId, newBranchName, currentBranch);
await gitService.createBranch(dashboardId, newBranchName, currentBranch, envId);
toast($t.git?.created_branch?.replace('{branch}', newBranchName), 'success');
showCreate = false;
newBranchName = '';

View File

@@ -18,6 +18,7 @@
// [SECTION: PROPS]
let {
dashboardId,
envId = null,
} = $props();
// [/SECTION]
@@ -48,7 +49,7 @@
console.log(`[CommitHistory][Action] Loading history for dashboard ${dashboardId}`);
loading = true;
try {
history = await gitService.getHistory(dashboardId);
history = await gitService.getHistory(dashboardId, 50, envId);
console.log(`[CommitHistory][Coherence:OK] Loaded ${history.length} commits`);
} catch (e) {
console.error(`[CommitHistory][Coherence:Failed] ${e.message}`);

View File

@@ -20,7 +20,7 @@
// [/SECTION]
// [SECTION: PROPS]
let { dashboardId, show = false } = $props();
let { dashboardId, envId = null, show = false } = $props();
// [/SECTION]
@@ -47,7 +47,7 @@
);
// postApi returns the JSON data directly or throws an error
const data = await api.postApi(
`/git/repositories/${dashboardId}/generate-message`,
`/git/repositories/${encodeURIComponent(String(dashboardId))}/generate-message${envId ? `?env_id=${encodeURIComponent(String(envId))}` : ""}`,
);
message = data.message;
toast($t.git?.commit_message_generated, "success");
@@ -72,17 +72,19 @@
console.log(
`[CommitModal][Action] Loading status and diff for ${dashboardId}`,
);
status = await gitService.getStatus(dashboardId);
status = await gitService.getStatus(dashboardId, envId);
// Fetch both unstaged and staged diffs to show complete picture
const unstagedDiff = await gitService.getDiff(
dashboardId,
null,
false,
envId,
);
const stagedDiff = await gitService.getDiff(
dashboardId,
null,
true,
envId,
);
diff = "";
@@ -114,7 +116,7 @@
);
committing = true;
try {
await gitService.commit(dashboardId, message, []);
await gitService.commit(dashboardId, message, [], envId);
toast($t.git?.commit_success, "success");
dispatch("commit");
show = false;

View File

@@ -18,7 +18,7 @@
// [/SECTION]
// [SECTION: PROPS]
let { dashboardId, show = false } = $props();
let { dashboardId, envId = null, show = false } = $props();
// [/SECTION]
@@ -75,7 +75,7 @@
console.log(`[DeploymentModal][Action] Deploying to ${selectedEnv}`);
deploying = true;
try {
const result = await gitService.deploy(dashboardId, selectedEnv);
const result = await gitService.deploy(dashboardId, selectedEnv, envId);
toast(
result.message || $t.git?.deploy_success,
"success",

View File

@@ -28,6 +28,7 @@
// [SECTION: PROPS]
let {
dashboardId,
envId = null,
dashboardTitle = "",
show = false,
} = $props();
@@ -49,8 +50,19 @@
let configs = $state([]);
let selectedConfigId = $state("");
let remoteUrl = $state("");
let creatingRemoteRepo = $state(false);
// [/SECTION]
// [DEF:isNumericDashboardRef:Function]
/**
* @purpose Checks whether current dashboard reference is numeric ID.
* @post Returns true when dashboardId is digits-only.
*/
function isNumericDashboardRef() {
return /^\d+$/.test(String(dashboardId || "").trim());
}
// [/DEF:isNumericDashboardRef:Function]
// [DEF:checkStatus:Function]
/**
* @purpose Проверяет, инициализирован ли репозиторий для данного дашборда.
@@ -58,16 +70,23 @@
* @post initialized state is set; configs loaded if not initialized.
*/
async function checkStatus() {
if (isNumericDashboardRef()) {
checkingStatus = false;
initialized = false;
toast('GitManager requires dashboard slug. Numeric ID is forbidden.', 'error');
return;
}
checkingStatus = true;
try {
// If we can get branches, it means repo exists
await gitService.getBranches(dashboardId);
await gitService.getBranches(dashboardId, envId);
initialized = true;
} catch (e) {
initialized = false;
// Load configs if not initialized
configs = await gitService.getConfigs();
if (configs.length > 0) selectedConfigId = configs[0].id;
const defaultConfig = resolveDefaultConfig(configs);
if (defaultConfig?.id) selectedConfigId = defaultConfig.id;
} finally {
checkingStatus = false;
}
@@ -81,13 +100,17 @@
* @post Repository is created on backend; initialized set to true.
*/
async function handleInit() {
if (isNumericDashboardRef()) {
toast('GitManager requires dashboard slug. Numeric ID is forbidden.', 'error');
return;
}
if (!selectedConfigId || !remoteUrl) {
toast($t.git?.init_validation_error, 'error');
return;
}
loading = true;
try {
await gitService.initRepository(dashboardId, selectedConfigId, remoteUrl);
await gitService.initRepository(dashboardId, selectedConfigId, remoteUrl, envId);
toast($t.git?.init_success, 'success');
initialized = true;
} catch (e) {
@@ -98,6 +121,91 @@
}
// [/DEF:handleInit:Function]
// [DEF:getSelectedConfig:Function]
/**
* @purpose Returns currently selected Git server config.
* @post Config object or null.
*/
function getSelectedConfig() {
return configs.find((item) => item.id === selectedConfigId) || null;
}
// [/DEF:getSelectedConfig:Function]
// [DEF:resolveDefaultConfig:Function]
/**
* @purpose Resolves default Git config for current dashboard session.
* @post Returns config by priority: selected -> is_default -> CONNECTED -> first.
*/
function resolveDefaultConfig(configList) {
if (!Array.isArray(configList) || configList.length === 0) return null;
const selected = configList.find((item) => item.id === selectedConfigId);
if (selected) return selected;
const explicitDefault = configList.find((item) => item?.is_default);
if (explicitDefault) return explicitDefault;
const connected = configList.find((item) => item?.status === 'CONNECTED');
return connected || configList[0];
}
// [/DEF:resolveDefaultConfig:Function]
// [DEF:buildSuggestedRepoName:Function]
/**
* @purpose Builds deterministic repository name from dashboard title/id.
* @post Returns kebab-case name.
*/
function buildSuggestedRepoName() {
const source = (dashboardTitle || `dashboard-${dashboardId}`).toLowerCase();
const slug = source
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 48);
return `${slug || 'dashboard'}-${dashboardId}`;
}
// [/DEF:buildSuggestedRepoName:Function]
// [DEF:handleCreateRemoteRepo:Function]
/**
* @purpose Creates remote repository on selected Git server and fills remote URL.
* @pre selectedConfigId is selected.
* @post remoteUrl is set from created repository clone URL.
*/
async function handleCreateRemoteRepo() {
const config = getSelectedConfig() || resolveDefaultConfig(configs);
if (!config) {
toast($t.git?.init_validation_error || 'Select Git server first', 'error');
return;
}
if (!selectedConfigId && config.id) selectedConfigId = config.id;
const suggestedName = buildSuggestedRepoName();
const inputName = prompt(
`Repository name for ${config.provider}:`,
suggestedName,
);
const repoName = String(inputName || '').trim();
if (!repoName) return;
creatingRemoteRepo = true;
try {
const repo = await gitService.createRemoteRepository(config.id, {
name: repoName,
private: true,
description: `Superset dashboard ${dashboardId}: ${dashboardTitle || repoName}`,
auto_init: true,
default_branch: 'main',
});
const resolvedRemoteUrl = repo?.clone_url || repo?.html_url || '';
if (!resolvedRemoteUrl) {
throw new Error('Remote repository created, but URL is empty');
}
remoteUrl = resolvedRemoteUrl;
toast(`Repository created on ${config.provider}`, 'success');
} catch (e) {
toast(e.message, 'error');
} finally {
creatingRemoteRepo = false;
}
}
// [/DEF:handleCreateRemoteRepo:Function]
// [DEF:handleSync:Function]
/**
* @purpose Синхронизирует состояние Superset с локальным Git-репозиторием.
@@ -105,11 +213,15 @@
* @post Dashboard YAMLs are exported to Git and staged.
*/
async function handleSync() {
if (isNumericDashboardRef()) {
toast('GitManager requires dashboard slug. Numeric ID is forbidden.', 'error');
return;
}
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);
await gitService.sync(dashboardId, sourceEnvId, envId);
toast($t.git?.sync_success, 'success');
} catch (e) {
toast(e.message, 'error');
@@ -126,9 +238,13 @@
* @post Changes are pushed to origin.
*/
async function handlePush() {
if (isNumericDashboardRef()) {
toast('GitManager requires dashboard slug. Numeric ID is forbidden.', 'error');
return;
}
loading = true;
try {
await gitService.push(dashboardId);
await gitService.push(dashboardId, envId);
toast($t.git?.push_success, 'success');
} catch (e) {
toast(e.message, 'error');
@@ -145,9 +261,13 @@
* @post Local branch is updated with remote changes.
*/
async function handlePull() {
if (isNumericDashboardRef()) {
toast('GitManager requires dashboard slug. Numeric ID is forbidden.', 'error');
return;
}
loading = true;
try {
await gitService.pull(dashboardId);
await gitService.pull(dashboardId, envId);
toast($t.git?.pull_success, 'success');
} catch (e) {
toast(e.message, 'error');
@@ -240,10 +360,19 @@
bind:value={remoteUrl}
placeholder={$t.git?.remote_url_placeholder}
/>
<Button
variant="secondary"
onclick={handleCreateRemoteRepo}
disabled={creatingRemoteRepo || configs.length === 0 || !selectedConfigId}
isLoading={creatingRemoteRepo}
class="w-full"
>
Create repo
</Button>
<Button
onclick={handleInit}
disabled={loading || configs.length === 0}
disabled={loading || configs.length === 0 || creatingRemoteRepo}
isLoading={loading}
class="w-full"
>
@@ -258,7 +387,7 @@
<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 />
<BranchSelector {dashboardId} {envId} bind:currentBranch />
</section>
<section class="space-y-3">
@@ -313,7 +442,7 @@
<!-- Right Column: History -->
<div class="md:col-span-2 border-l pl-6">
<CommitHistory {dashboardId} />
<CommitHistory {dashboardId} {envId} />
</div>
</div>
{/if}
@@ -323,12 +452,14 @@
<CommitModal
{dashboardId}
{envId}
bind:show={showCommitModal}
on:commit={() => { /* Refresh history */ }}
/>
<DeploymentModal
{dashboardId}
{envId}
bind:show={showDeployModal}
/>