feat: add deploy dashboard page with components

This commit is contained in:
F4ilji
2026-07-02 12:48:49 +05:00
parent 73d123a62d
commit 13d7d36088
4 changed files with 178 additions and 0 deletions
@@ -0,0 +1,83 @@
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import DeployButton from './components/DeployButton.vue'
import DeployProgress from './components/DeployProgress.vue'
import DeployLogs from './components/DeployLogs.vue'
const status = ref({
running: false,
step: 0,
total_steps: 12,
current_step: '',
logs: [],
error: null,
})
let pollingInterval = null
const fetchStatus = async () => {
try {
const response = await fetch('/api/deploy/status')
status.value = await response.json()
if (!status.value.running && pollingInterval) {
clearInterval(pollingInterval)
pollingInterval = null
}
} catch (error) {
console.error('Failed to fetch status:', error)
}
}
const startDeploy = async () => {
try {
const response = await fetch('/api/deploy', { method: 'POST' })
const data = await response.json()
if (response.ok) {
startPolling()
} else {
alert(data.error || 'Failed to start deploy')
}
} catch (error) {
alert('Failed to start deploy')
}
}
const startPolling = () => {
if (pollingInterval) return
pollingInterval = setInterval(fetchStatus, 2000)
}
onMounted(() => {
fetchStatus()
})
onUnmounted(() => {
if (pollingInterval) {
clearInterval(pollingInterval)
}
})
</script>
<template>
<div class="max-w-4xl mx-auto p-6">
<h1 class="text-2xl font-bold mb-6">Deploy</h1>
<DeployButton
:running="status.running"
@deploy="startDeploy"
/>
<DeployProgress
v-if="status.running || status.step > 0"
:step="status.step"
:total-steps="status.total_steps"
:current-step="status.current_step"
:success="status.success"
:error="status.error"
/>
<DeployLogs :logs="status.logs" />
</div>
</template>
@@ -0,0 +1,20 @@
<script setup>
defineProps({
running: Boolean,
})
defineEmits(['deploy'])
</script>
<template>
<button
@click="$emit('deploy')"
:disabled="running"
class="px-6 py-3 bg-blue-600 text-white rounded-lg font-medium
hover:bg-blue-700 disabled:bg-gray-400 disabled:cursor-not-allowed
transition-colors"
>
<span v-if="running">Deploying...</span>
<span v-else>🚀 Deploy to Production</span>
</button>
</template>
@@ -0,0 +1,33 @@
<script setup>
import { ref, watch, nextTick } from 'vue'
const props = defineProps({
logs: Array,
})
const logsContainer = ref(null)
watch(() => props.logs, async () => {
await nextTick()
if (logsContainer.value) {
logsContainer.value.scrollTop = logsContainer.value.scrollHeight
}
}, { deep: true })
</script>
<template>
<div class="mt-6">
<h3 class="text-lg font-medium mb-3">Logs</h3>
<div
ref="logsContainer"
class="bg-gray-900 text-green-400 p-4 rounded-lg h-64 overflow-y-auto font-mono text-sm"
>
<div v-if="logs.length === 0" class="text-gray-500">
No logs yet...
</div>
<div v-for="(log, index) in logs" :key="index">
{{ log }}
</div>
</div>
</div>
</template>
@@ -0,0 +1,42 @@
<script setup>
import { computed } from 'vue'
const props = defineProps({
step: Number,
totalSteps: Number,
currentStep: String,
success: Boolean,
error: String,
})
const progress = computed(() => (props.step / props.totalSteps) * 100)
</script>
<template>
<div class="mt-6 p-4 bg-white rounded-lg shadow">
<div class="mb-2 flex justify-between text-sm">
<span>{{ currentStep }}</span>
<span>{{ step }}/{{ totalSteps }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-3">
<div
class="h-3 rounded-full transition-all duration-500"
:class="{
'bg-blue-600': !success && !error,
'bg-green-600': success,
'bg-red-600': error
}"
:style="{ width: progress + '%' }"
/>
</div>
<div v-if="error" class="mt-3 p-3 bg-red-100 text-red-700 rounded">
{{ error }}
</div>
<div v-else-if="success" class="mt-3 p-3 bg-green-100 text-green-700 rounded">
Deploy completed successfully
</div>
</div>
</template>