263 lines
13 KiB
Vue
263 lines
13 KiB
Vue
<template>
|
|
<div class="glass-card p-6 mb-6">
|
|
<!-- Desktop: Single Row Layout -->
|
|
<div class="hidden md:flex items-center gap-6">
|
|
<img
|
|
:src="icon"
|
|
:alt="pkg.manifest.title"
|
|
class="app-detail-icon w-20 h-20 shadow-xl flex-shrink-0"
|
|
@error="handleImageError"
|
|
/>
|
|
|
|
<div class="flex-1 min-w-0">
|
|
<h1 class="text-2xl font-bold text-white mb-1">{{ pkg.manifest.title }}</h1>
|
|
<p class="text-white/70 text-sm mb-2">{{ pkg.manifest.description.short }}</p>
|
|
<div class="flex items-center gap-2">
|
|
<span
|
|
class="inline-flex items-center px-2.5 py-1 rounded-lg text-xs font-medium"
|
|
:class="getStatusClass(pkg.state, pkg.health, pkg['exit-code'])"
|
|
>
|
|
<span class="w-1.5 h-1.5 rounded-full mr-1.5" :class="getStatusDotClass(pkg.state, pkg.health, pkg['exit-code'])"></span>
|
|
{{ getStatusLabel(pkg.state, pkg.health, pkg['exit-code']) }}
|
|
</span>
|
|
<span class="text-white/50 text-xs">v{{ pkg.manifest.version }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="flex items-center gap-2 flex-shrink-0">
|
|
<!-- Update available -->
|
|
<button
|
|
v-if="pkg['available-update'] && pkg.state !== 'updating'"
|
|
@click="$emit('update')"
|
|
class="px-4 py-2.5 bg-orange-500/20 border border-orange-500/40 rounded-lg text-orange-200 text-sm font-medium hover:bg-orange-500/30 transition-colors flex items-center gap-2"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
Update to v{{ pkg['available-update'] }}
|
|
</button>
|
|
<!-- Updating in progress -->
|
|
<span
|
|
v-if="pkg.state === 'updating'"
|
|
class="px-4 py-2.5 bg-orange-500/20 border border-orange-500/40 rounded-lg text-orange-200 text-sm font-medium flex items-center gap-2"
|
|
>
|
|
<svg class="animate-spin h-4 w-4" 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>
|
|
Updating...
|
|
</span>
|
|
<button
|
|
v-if="packageKey === 'lnd'"
|
|
@click="$emit('channels')"
|
|
class="glass-button glass-button-sm px-4 py-2.5 rounded-lg text-sm font-medium flex items-center gap-2"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
</svg>
|
|
{{ t('appDetails.channels') }}
|
|
</button>
|
|
<button
|
|
v-if="canLaunch"
|
|
@click="$emit('launch')"
|
|
class="glass-button glass-button-sm px-6 py-2.5 rounded-lg text-sm font-semibold flex items-center gap-2"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
</svg>
|
|
{{ t('common.launch') }}
|
|
</button>
|
|
<template v-if="!isWebOnly">
|
|
<button
|
|
v-if="pkg.state === 'stopped' || pkg.state === 'exited'"
|
|
@click="$emit('start')"
|
|
class="px-4 py-2.5 glass-button rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
|
|
:class="pkg.state === 'exited' ? 'glass-button-danger' : 'glass-button-success'"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
|
</svg>
|
|
{{ pkg.state === 'exited' ? 'Restart' : t('common.start') }}
|
|
</button>
|
|
<button
|
|
@click="$emit('restart')"
|
|
class="px-4 py-2.5 glass-button rounded-lg text-sm font-medium hover:bg-white/15 transition-colors flex items-center gap-2"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
{{ t('common.restart') }}
|
|
</button>
|
|
<button
|
|
v-if="pkg.state === 'running'"
|
|
@click="$emit('stop')"
|
|
class="px-4 py-2.5 bg-yellow-500/20 border border-yellow-500/40 rounded-lg text-yellow-200 text-sm font-medium hover:bg-yellow-500/30 transition-colors flex items-center gap-2"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
|
|
</svg>
|
|
{{ t('common.stop') }}
|
|
</button>
|
|
<button
|
|
@click="$emit('uninstall')"
|
|
class="px-4 py-2.5 glass-button glass-button-danger rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
</svg>
|
|
{{ t('common.uninstall') }}
|
|
</button>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile: Two Column Grid Layout -->
|
|
<div class="md:hidden">
|
|
<div class="flex items-start gap-4 mb-4">
|
|
<img
|
|
:src="icon"
|
|
:alt="pkg.manifest.title"
|
|
class="app-detail-icon w-20 h-20 shadow-xl flex-shrink-0"
|
|
@error="handleImageError"
|
|
/>
|
|
|
|
<div class="flex-1 min-w-0">
|
|
<h1 class="text-xl font-bold text-white mb-1">{{ pkg.manifest.title }}</h1>
|
|
<p class="text-white/70 text-xs mb-2 line-clamp-2">{{ pkg.manifest.description.short }}</p>
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<span
|
|
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium"
|
|
:class="getStatusClass(pkg.state, pkg.health, pkg['exit-code'])"
|
|
>
|
|
<span class="w-1.5 h-1.5 rounded-full mr-1" :class="getStatusDotClass(pkg.state, pkg.health, pkg['exit-code'])"></span>
|
|
{{ getStatusLabel(pkg.state, pkg.health, pkg['exit-code']) }}
|
|
</span>
|
|
<span class="text-white/50 text-xs">v{{ pkg.manifest.version }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<button
|
|
v-if="!isWebOnly"
|
|
@click="$emit('uninstall')"
|
|
class="flex-shrink-0 w-10 h-10 rounded-lg glass-button glass-button-danger transition-colors flex items-center justify-center"
|
|
:title="t('common.uninstall')"
|
|
>
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Action Buttons (Auto Grid) -->
|
|
<div class="grid grid-cols-2 gap-2">
|
|
<!-- Update available (mobile) -->
|
|
<button
|
|
v-if="pkg['available-update'] && pkg.state !== 'updating'"
|
|
@click="$emit('update')"
|
|
class="col-span-2 px-4 py-2.5 bg-orange-500/20 border border-orange-500/40 rounded-lg text-orange-200 text-sm font-medium hover:bg-orange-500/30 transition-colors flex items-center justify-center gap-2"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
Update to v{{ pkg['available-update'] }}
|
|
</button>
|
|
<!-- Updating in progress (mobile) -->
|
|
<span
|
|
v-if="pkg.state === 'updating'"
|
|
class="col-span-2 px-4 py-2.5 bg-orange-500/20 border border-orange-500/40 rounded-lg text-orange-200 text-sm font-medium flex items-center justify-center gap-2"
|
|
>
|
|
<svg class="animate-spin h-4 w-4" 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>
|
|
Updating...
|
|
</span>
|
|
<button
|
|
v-if="canLaunch"
|
|
@click="$emit('launch')"
|
|
:class="isWebOnly ? 'col-span-2' : ''"
|
|
class="glass-button glass-button-sm px-4 py-2.5 rounded-lg text-sm font-semibold flex items-center justify-center gap-2"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
|
</svg>
|
|
{{ t('common.launch') }}
|
|
</button>
|
|
<template v-if="!isWebOnly">
|
|
<button
|
|
v-if="pkg.state === 'stopped' || pkg.state === 'exited'"
|
|
@click="$emit('start')"
|
|
class="px-4 py-2.5 glass-button rounded-lg text-sm font-medium transition-colors flex items-center justify-center gap-2"
|
|
:class="pkg.state === 'exited' ? 'glass-button-danger' : 'glass-button-success'"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
|
</svg>
|
|
{{ pkg.state === 'exited' ? 'Restart' : t('common.start') }}
|
|
</button>
|
|
<button
|
|
v-if="pkg.state === 'running'"
|
|
@click="$emit('stop')"
|
|
class="px-4 py-2.5 bg-yellow-500/20 border border-yellow-500/40 rounded-lg text-yellow-200 text-sm font-medium hover:bg-yellow-500/30 transition-colors flex items-center justify-center gap-2"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z" />
|
|
</svg>
|
|
{{ t('common.stop') }}
|
|
</button>
|
|
<button
|
|
@click="$emit('restart')"
|
|
:class="[canLaunch && (pkg.state === 'stopped' || pkg.state === 'exited' || pkg.state === 'running') ? 'col-span-2' : '']"
|
|
class="px-4 py-2.5 glass-button rounded-lg text-sm font-medium hover:bg-white/15 transition-colors flex items-center justify-center gap-2"
|
|
>
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
</svg>
|
|
{{ t('common.restart') }}
|
|
</button>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { useI18n } from 'vue-i18n'
|
|
import { computed } from 'vue'
|
|
import type { PackageDataEntry } from '@/types/api'
|
|
import { resolveAppIcon } from '@/views/apps/appsConfig'
|
|
import { getStatusClass, getStatusDotClass, getStatusLabel } from './appDetailsData'
|
|
|
|
const { t } = useI18n()
|
|
|
|
const props = defineProps<{
|
|
pkg: PackageDataEntry
|
|
appId: string
|
|
packageKey: string
|
|
canLaunch: boolean
|
|
isWebOnly: boolean
|
|
}>()
|
|
|
|
const icon = computed(() => resolveAppIcon(props.pkg.manifest?.id || props.appId, props.pkg))
|
|
|
|
defineEmits<{
|
|
launch: []
|
|
start: []
|
|
stop: []
|
|
restart: []
|
|
uninstall: []
|
|
update: []
|
|
channels: []
|
|
}>()
|
|
|
|
function handleImageError(e: Event) {
|
|
const target = e.target as HTMLImageElement
|
|
if (!target.src.includes('data:image') && !target.src.includes('logo-archipelago')) {
|
|
target.src = '/assets/img/logo-archipelago.svg'
|
|
}
|
|
}
|
|
</script>
|