kin: KIN-049 Кнопка Deploy на странице задачи после approve. Для каждого проекта настраивается deploy-команда (git push, scp, ssh restart). В Settings проекта.
This commit is contained in:
parent
860ef3f6c9
commit
d50bd703ae
11 changed files with 517 additions and 61 deletions
|
|
@ -4,9 +4,12 @@ import { api, type Project, type ObsidianSyncResult } from '../api'
|
|||
|
||||
const projects = ref<Project[]>([])
|
||||
const vaultPaths = ref<Record<string, string>>({})
|
||||
const deployCommands = ref<Record<string, string>>({})
|
||||
const saving = ref<Record<string, boolean>>({})
|
||||
const savingDeploy = ref<Record<string, boolean>>({})
|
||||
const syncing = ref<Record<string, boolean>>({})
|
||||
const saveStatus = ref<Record<string, string>>({})
|
||||
const saveDeployStatus = ref<Record<string, string>>({})
|
||||
const syncResults = ref<Record<string, ObsidianSyncResult | null>>({})
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
|
|
@ -15,6 +18,7 @@ onMounted(async () => {
|
|||
projects.value = await api.projects()
|
||||
for (const p of projects.value) {
|
||||
vaultPaths.value[p.id] = p.obsidian_vault_path ?? ''
|
||||
deployCommands.value[p.id] = p.deploy_command ?? ''
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = String(e)
|
||||
|
|
@ -34,6 +38,19 @@ async function saveVaultPath(projectId: string) {
|
|||
}
|
||||
}
|
||||
|
||||
async function saveDeployCommand(projectId: string) {
|
||||
savingDeploy.value[projectId] = true
|
||||
saveDeployStatus.value[projectId] = ''
|
||||
try {
|
||||
await api.patchProject(projectId, { deploy_command: deployCommands.value[projectId] })
|
||||
saveDeployStatus.value[projectId] = 'Saved'
|
||||
} catch (e) {
|
||||
saveDeployStatus.value[projectId] = `Error: ${e}`
|
||||
} finally {
|
||||
savingDeploy.value[projectId] = false
|
||||
}
|
||||
}
|
||||
|
||||
async function runSync(projectId: string) {
|
||||
syncing.value[projectId] = true
|
||||
syncResults.value[projectId] = null
|
||||
|
|
@ -70,13 +87,37 @@ async function runSync(projectId: string) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="block text-xs text-gray-400 mb-1">Deploy Command</label>
|
||||
<input
|
||||
v-model="deployCommands[project.id]"
|
||||
type="text"
|
||||
placeholder="git push origin main"
|
||||
class="w-full bg-gray-900 border border-gray-700 rounded px-3 py-2 text-sm text-gray-200 font-mono focus:outline-none focus:border-gray-500"
|
||||
/>
|
||||
<p class="text-xs text-gray-600 mt-1">Команда выполняется через shell в директории проекта. Настраивается только администратором.</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 flex-wrap mb-3">
|
||||
<button
|
||||
@click="saveDeployCommand(project.id)"
|
||||
:disabled="savingDeploy[project.id]"
|
||||
class="px-3 py-1.5 text-sm bg-gray-700 hover:bg-gray-600 text-gray-200 rounded disabled:opacity-50"
|
||||
>
|
||||
{{ savingDeploy[project.id] ? 'Saving…' : 'Save Deploy' }}
|
||||
</button>
|
||||
<span v-if="saveDeployStatus[project.id]" class="text-xs" :class="saveDeployStatus[project.id].startsWith('Error') ? 'text-red-400' : 'text-green-400'">
|
||||
{{ saveDeployStatus[project.id] }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 flex-wrap">
|
||||
<button
|
||||
@click="saveVaultPath(project.id)"
|
||||
:disabled="saving[project.id]"
|
||||
class="px-3 py-1.5 text-sm bg-gray-700 hover:bg-gray-600 text-gray-200 rounded disabled:opacity-50"
|
||||
>
|
||||
{{ saving[project.id] ? 'Saving…' : 'Save' }}
|
||||
{{ saving[project.id] ? 'Saving…' : 'Save Vault' }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { api, type TaskFull, type PipelineStep, type PendingAction } from '../api'
|
||||
import { api, type TaskFull, type PipelineStep, type PendingAction, type DeployResult } from '../api'
|
||||
import Badge from '../components/Badge.vue'
|
||||
import Modal from '../components/Modal.vue'
|
||||
|
||||
|
|
@ -262,6 +262,23 @@ async function changeStatus(newStatus: string) {
|
|||
}
|
||||
}
|
||||
|
||||
// Deploy
|
||||
const deploying = ref(false)
|
||||
const deployResult = ref<DeployResult | null>(null)
|
||||
|
||||
async function runDeploy() {
|
||||
if (!task.value) return
|
||||
deploying.value = true
|
||||
deployResult.value = null
|
||||
try {
|
||||
deployResult.value = await api.deployProject(task.value.project_id)
|
||||
} catch (e: any) {
|
||||
error.value = e.message
|
||||
} finally {
|
||||
deploying.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Edit modal (pending tasks only)
|
||||
const showEdit = ref(false)
|
||||
const editForm = ref({ title: '', briefText: '', priority: 5 })
|
||||
|
|
@ -488,6 +505,27 @@ async function saveEdit() {
|
|||
<span v-if="resolvingManually" class="inline-block w-3 h-3 border-2 border-orange-400 border-t-transparent rounded-full animate-spin mr-1"></span>
|
||||
{{ resolvingManually ? 'Сохраняем...' : '✓ Решить вручную' }}
|
||||
</button>
|
||||
<button v-if="task.status === 'done' && task.project_deploy_command"
|
||||
@click.stop="runDeploy"
|
||||
:disabled="deploying"
|
||||
class="px-4 py-2 text-sm bg-teal-900/50 text-teal-400 border border-teal-800 rounded hover:bg-teal-900 disabled:opacity-50">
|
||||
<span v-if="deploying" class="inline-block w-3 h-3 border-2 border-teal-400 border-t-transparent rounded-full animate-spin mr-1"></span>
|
||||
{{ deploying ? 'Deploying...' : '🚀 Deploy' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Deploy result inline block -->
|
||||
<div v-if="deployResult" class="mx-0 mt-2 p-3 rounded border text-xs font-mono"
|
||||
:class="deployResult.success ? 'border-teal-800 bg-teal-950/30 text-teal-300' : 'border-red-800 bg-red-950/30 text-red-300'">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span :class="deployResult.success ? 'text-teal-400' : 'text-red-400'" class="font-semibold">
|
||||
{{ deployResult.success ? '✓ Deploy succeeded' : '✗ Deploy failed' }}
|
||||
</span>
|
||||
<span class="text-gray-500">exit {{ deployResult.exit_code }} · {{ deployResult.duration_seconds }}s</span>
|
||||
<button @click.stop="deployResult = null" class="ml-auto text-gray-600 hover:text-gray-400 bg-transparent border-none cursor-pointer text-xs">✕</button>
|
||||
</div>
|
||||
<pre v-if="deployResult.stdout" class="whitespace-pre-wrap text-gray-300 max-h-40 overflow-y-auto">{{ deployResult.stdout }}</pre>
|
||||
<pre v-if="deployResult.stderr" class="whitespace-pre-wrap text-red-400/80 max-h-40 overflow-y-auto mt-1">{{ deployResult.stderr }}</pre>
|
||||
</div>
|
||||
|
||||
<!-- Approve Modal -->
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue