feat: external iframes, container startup UX, release notes modal

- Add https: to CSP frame-src so external site iframes (BotFights,
  484 Kitchen, etc.) load without being blocked by Content-Security-Policy
- Show spinner + "Starting..." on marketplace cards for containers that
  are booting up, preventing users from re-installing running apps
- Add spinner to transitional state badges (starting/stopping/installing)
  on installed app cards in Apps view
- Add "What's New" button to Settings version card with release notes
  modal covering recent highlights in layman-friendly language

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-18 11:15:32 +00:00
parent e4089287a3
commit bccef62585
4 changed files with 115 additions and 22 deletions

View File

@@ -54,9 +54,59 @@
</svg>
<p class="text-xs font-semibold text-white/60 uppercase tracking-wide">{{ t('common.version') }}</p>
</div>
<p class="text-lg font-semibold text-white/95">{{ version }}</p>
<div class="flex items-center justify-between">
<p class="text-lg font-semibold text-white/95">{{ version }}</p>
<button
@click="showReleaseNotes = true"
class="glass-button px-3 py-1.5 text-xs"
>What's New</button>
</div>
</div>
<!-- Release Notes Modal -->
<Teleport to="body">
<Transition name="modal">
<div v-if="showReleaseNotes" class="fixed inset-0 z-[3000] flex items-center justify-center p-4" @click="showReleaseNotes = false">
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm"></div>
<div @click.stop class="glass-card p-6 max-w-lg w-full relative z-10 max-h-[80vh] overflow-y-auto">
<div class="flex items-start justify-between gap-4 mb-5">
<h3 class="text-xl font-semibold text-white">What's New in {{ version }}</h3>
<button @click="showReleaseNotes = false" class="p-2 rounded-lg hover:bg-white/10 text-white/70 hover:text-white transition-colors" aria-label="Close">
<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="M6 18L18 6M6 6l12 12" /></svg>
</button>
</div>
<div class="space-y-4 text-sm text-white/80">
<div>
<h4 class="text-white font-medium mb-1">Mesh Radio Connection</h4>
<p>Your LoRa mesh radio now connects reliably every time. If the USB port changes, the system automatically finds the device. A new "Connect" button lets you pick which radio to use.</p>
</div>
<div>
<h4 class="text-white font-medium mb-1">Rock-Solid Containers</h4>
<p>Fixed crash loops that were causing apps to restart thousands of times. All containers now start cleanly and stay stable across reboots.</p>
</div>
<div>
<h4 class="text-white font-medium mb-1">Smarter App Store</h4>
<p>Apps that are still starting up now show their progress instead of appearing as available to install again. No more accidentally re-installing running apps.</p>
</div>
<div>
<h4 class="text-white font-medium mb-1">Security Hardening</h4>
<p>Backend services now run with strict sandboxing: read-only filesystem, restricted system calls, and least-privilege access. Your node is locked down by default.</p>
</div>
<div>
<h4 class="text-white font-medium mb-1">Tor by Default</h4>
<p>Bitcoin and Lightning now route through Tor automatically for maximum privacy. Your node's network traffic is protected out of the box.</p>
</div>
<div>
<h4 class="text-white font-medium mb-1">Off-Grid Bitcoin</h4>
<p>Receive Bitcoin block headers over mesh radio. A dead man's switch can broadcast your location to trusted contacts if you go silent. GPS sharing is opt-in only.</p>
</div>
</div>
<button @click="showReleaseNotes = false" class="glass-button w-full mt-6 py-2 text-sm">Close</button>
</div>
</div>
</Transition>
</Teleport>
<!-- Session Card -->
<div class="bg-black/20 rounded-xl px-5 py-4 border border-white/10 md:col-span-2">
<div class="flex items-center gap-3 mb-2">
@@ -965,6 +1015,7 @@ async function saveServerName() {
}
const version = computed(() => store.serverInfo?.version || '0.0.0')
const showReleaseNotes = ref(false)
const serverTorAddressFromStore = computed(() => store.serverInfo?.['tor-address'] || null)
const torAddressFromRpc = ref<string | null>(null)
const serverTorAddress = computed(() => serverTorAddressFromStore.value || torAddressFromRpc.value)