Compare commits

...

2 Commits

Author SHA1 Message Date
vasyansk 614d7fea06 Add cloud download icon and improve update UI handling
Build / Build & push landing (push) Successful in 14s
Build / Deploy to prod (push) Successful in 6s
Build / Notify Max (push) Successful in 2s
Update UI to use CloudDownload icon instead of RefreshCw
Improve error handling for update checks
Add better visual feedback for update states
Change GitHub link to RealManual repository
2026-06-15 14:36:30 +07:00
vasyansk 74abea5467 fix(deploy): stable container_name for the proxy (avoid pinned-IP collision)
container_name: spacesh-proxy lets NPM forward by name instead of the fragile
pinned 172.18.0.28, which another webproxy container could grab — sending NPM
to the wrong target (or itself) and looping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 14:25:31 +07:00
4 changed files with 27 additions and 26 deletions
+17 -21
View File
@@ -449,32 +449,28 @@ fn parse_ver(v: &str) -> (u64, u64, u64) {
/// which `make dmg` bumps on every build. /// which `make dmg` bumps on every build.
#[tauri::command] #[tauri::command]
pub async fn check_update(app: AppHandle) -> Result<UpdateInfo, String> { pub async fn check_update(app: AppHandle) -> Result<UpdateInfo, String> {
// The local version is always known; the server may be unreachable (no manifest
// yet, offline). Never fail the command for that — return the current version with
// an empty `latest` so the UI can show "couldn't check" instead of blanking out.
let current = app.package_info().version.to_string(); let current = app.package_info().version.to_string();
let fallback_url = "https://spaceshell.ru/download/spacesh.dmg".to_string();
let manifest_url = let manifest_url =
std::env::var("SPACESH_UPDATE_URL").ok().filter(|s| !s.is_empty()).unwrap_or_else(|| DEFAULT_UPDATE_URL.to_string()); std::env::var("SPACESH_UPDATE_URL").ok().filter(|s| !s.is_empty()).unwrap_or_else(|| DEFAULT_UPDATE_URL.to_string());
let client = reqwest::Client::builder() let fetch = || async {
.timeout(Duration::from_secs(10)) let client = reqwest::Client::builder().timeout(Duration::from_secs(10)).build()?;
.build() client.get(&manifest_url).send().await?.error_for_status()?.json::<Value>().await
.map_err(|e| e.to_string())?; };
let manifest: Value = client
.get(&manifest_url)
.send()
.await
.map_err(|e| e.to_string())?
.json()
.await
.map_err(|e| e.to_string())?;
let latest = manifest.get("version").and_then(|v| v.as_str()).unwrap_or("").to_string(); match fetch().await {
let url = manifest Ok(manifest) => {
.get("url") let latest = manifest.get("version").and_then(|v| v.as_str()).unwrap_or("").to_string();
.and_then(|v| v.as_str()) let url = manifest.get("url").and_then(|v| v.as_str()).unwrap_or(&fallback_url).to_string();
.unwrap_or("https://spaceshell.ru/download/spacesh.dmg") let has_update = !latest.is_empty() && parse_ver(&latest) > parse_ver(&current);
.to_string(); Ok(UpdateInfo { current, latest, has_update, url })
let has_update = !latest.is_empty() && parse_ver(&latest) > parse_ver(&current); }
Err(_) => Ok(UpdateInfo { current, latest: String::new(), has_update: false, url: fallback_url }),
Ok(UpdateInfo { current, latest, has_update, url }) }
} }
/// Open a URL in the default browser (macOS `open`). Used by the update popover's /// Open a URL in the default browser (macOS `open`). Used by the update popover's
+6 -4
View File
@@ -1,5 +1,5 @@
import { useState } from "react"; import { useState } from "react";
import { FolderGit2, PanelLeft, PanelRight, Search, Bell, Settings, ChevronDown, RefreshCw, Download } from "lucide-react"; import { FolderGit2, PanelLeft, PanelRight, Search, Bell, Settings, ChevronDown, CloudDownload, Download } from "lucide-react";
import { COLORS, FONT } from "./theme"; import { COLORS, FONT } from "./theme";
import type { WorkspaceView } from "./layoutTypes"; import type { WorkspaceView } from "./layoutTypes";
import { leafIds } from "./layoutTypes"; import { leafIds } from "./layoutTypes";
@@ -51,7 +51,7 @@ function UpdateControl({ update, checking, onCheck }: { update: UpdateInfo | nul
animation: hasUpdate ? "spaceshPulse 2s ease-in-out infinite" : "none", animation: hasUpdate ? "spaceshPulse 2s ease-in-out infinite" : "none",
}} }}
> >
<RefreshCw size={15} style={{ animation: checking ? "spaceshSpin 0.8s linear infinite" : "none" }} /> <CloudDownload size={15} style={{ animation: checking ? "spaceshBlink 1s ease-in-out infinite" : "none" }} />
</button> </button>
{open && ( {open && (
@@ -83,8 +83,10 @@ function UpdateControl({ update, checking, onCheck }: { update: UpdateInfo | nul
> >
<Download size={14} /> Скачать {update?.latest} <Download size={14} /> Скачать {update?.latest}
</button> </button>
) : ( ) : update?.latest ? (
<div style={{ marginTop: 10, fontSize: 12, color: COLORS.stDone }}>Установлена последняя версия</div> <div style={{ marginTop: 10, fontSize: 12, color: COLORS.stDone }}>Установлена последняя версия</div>
) : (
<div style={{ marginTop: 10, fontSize: 12, color: COLORS.textMuted }}>Не удалось проверить сервер</div>
)} )}
<button <button
@@ -151,7 +153,7 @@ export function TopBar({
<div style={{ flex: 1 }} /> <div style={{ flex: 1 }} />
<style>{` <style>{`
@keyframes spaceshSpin { to { transform: rotate(360deg); } } @keyframes spaceshBlink { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
@keyframes spaceshPulse { 0%,100% { box-shadow: 0 0 6px rgba(52,211,194,0.35); } 50% { box-shadow: 0 0 14px rgba(52,211,194,0.7); } } @keyframes spaceshPulse { 0%,100% { box-shadow: 0 0 6px rgba(52,211,194,0.35); } 50% { box-shadow: 0 0 14px rgba(52,211,194,0.7); } }
`}</style> `}</style>
+3
View File
@@ -14,6 +14,9 @@ services:
proxy: proxy:
image: nginx:1.27-alpine image: nginx:1.27-alpine
# Stable name so NPM can forward by name (spacesh-proxy:80) instead of a
# pinned IP that another webproxy container could grab.
container_name: spacesh-proxy
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
- landing - landing
+1 -1
View File
@@ -1105,7 +1105,7 @@
Скачать для macOS Скачать для macOS
</a> </a>
</div> </div>
<a href="https://github.com/spacesh" target="_blank" rel="noopener" class="cta-github"> <a href="https://git.realmanual.ru/realmanual/spaceshell" target="_blank" rel="noopener" class="cta-github">
Исходники на GitHub → Исходники на GitHub →
</a> </a>
</div> </div>