Compare commits
5 Commits
ff7d4e4b9a
...
db61521e4a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db61521e4a | ||
|
|
15c4b35ac9 | ||
|
|
c60189383d | ||
|
|
7dbfeee3f6 | ||
|
|
c91a286ffe |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "smartims",
|
"name": "smartims",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.4.0",
|
"version": "0.3.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@ -358,17 +358,22 @@ const initTables = async () => {
|
|||||||
};
|
};
|
||||||
initTables();
|
initTables();
|
||||||
|
|
||||||
const packageJson = require('./package.json');
|
// Dynamic Version Helper (Git Primary, Package.json Secondary)
|
||||||
|
const { execSync } = require('child_process');
|
||||||
app.get('/api/health', (req, res) => {
|
app.get('/api/health', (req, res) => {
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
||||||
res.json({
|
res.json({
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
version: packageJson.version,
|
version: pkg.version.replace(/^v/, ''),
|
||||||
node_version: process.version,
|
node_version: process.version,
|
||||||
platform: process.platform,
|
platform: process.platform,
|
||||||
arch: process.arch,
|
arch: process.arch,
|
||||||
timestamp: new Date().toISOString().replace('T', ' ').split('.')[0]
|
timestamp: new Date().toISOString().replace('T', ' ').split('.')[0]
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
res.json({ status: 'ok', version: 'unknown' });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "server",
|
"name": "server",
|
||||||
"version": "0.4.0",
|
"version": "0.3.5",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -465,9 +465,18 @@ const getGiteaAuth = async () => {
|
|||||||
// 5. Get Version Info (Current, Remote & History from Tags)
|
// 5. Get Version Info (Current, Remote & History from Tags)
|
||||||
router.get('/version/remote', isAuthenticated, hasRole('admin'), async (req, res) => {
|
router.get('/version/remote', isAuthenticated, hasRole('admin'), async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const packageJsonPath = path.join(__dirname, '../package.json');
|
// Current version MUST be read from package.json to detect updates
|
||||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
// If we use git describe, it would change as soon as we fetch tags
|
||||||
const currentVersion = packageJson.version;
|
const getVerFromPkg = () => {
|
||||||
|
try {
|
||||||
|
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
||||||
|
return pkg.version.replace(/^v/, '');
|
||||||
|
} catch (e) {
|
||||||
|
return '0.0.0';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentVersion = getVerFromPkg();
|
||||||
|
|
||||||
// Prepare git fetch command with auth if available
|
// Prepare git fetch command with auth if available
|
||||||
const auth = await getGiteaAuth();
|
const auth = await getGiteaAuth();
|
||||||
@ -530,30 +539,36 @@ router.get('/version/remote', isAuthenticated, hasRole('admin'), async (req, res
|
|||||||
};
|
};
|
||||||
}).filter(Boolean);
|
}).filter(Boolean);
|
||||||
|
|
||||||
// Version Comparison Helper
|
const parseV = (v) => (v || '').replace(/^v/, '').split('.').map(n => parseInt(n) || 0);
|
||||||
const compareVersions = (v1, v2) => {
|
const compare = (v1, v2) => {
|
||||||
const parts1 = v1.split('.').map(Number);
|
const p1 = parseV(v1);
|
||||||
const parts2 = v2.split('.').map(Number);
|
const p2 = parseV(v2);
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
if (parts1[i] > parts2[i]) return 1;
|
const n1 = p1[i] || 0;
|
||||||
if (parts1[i] < parts2[i]) return -1;
|
const n2 = p2[i] || 0;
|
||||||
|
if (n1 > n2) return 1;
|
||||||
|
if (n1 < n2) return -1;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Filter 1: History is ONLY versions <= current
|
const history = allTags.filter(t => compare(t.version, currentVersion) <= 0);
|
||||||
const history = allTags.filter(t => compareVersions(t.version, currentVersion) <= 0);
|
|
||||||
|
|
||||||
// Filter 2: Latest is the absolute newest tag available (could be > current)
|
|
||||||
const latestFromRemote = allTags[0] || null;
|
const latestFromRemote = allTags[0] || null;
|
||||||
const needsUpdate = latestFromRemote ? (compareVersions(latestFromRemote.version, currentVersion) > 0) : false;
|
const needsUpdate = latestFromRemote ? (compare(latestFromRemote.version, currentVersion) > 0) : false;
|
||||||
|
|
||||||
|
console.log(`[VersionCheck] Current: ${currentVersion}, Latest: ${latestFromRemote?.version}, NeedsUpdate: ${needsUpdate}`);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
current: currentVersion,
|
current: currentVersion,
|
||||||
latest: latestFromRemote ? `v${latestFromRemote.version}` : null,
|
latest: latestFromRemote ? `v${latestFromRemote.version}` : null,
|
||||||
needsUpdate: needsUpdate,
|
needsUpdate: needsUpdate,
|
||||||
latestInfo: needsUpdate ? latestFromRemote : null,
|
latestInfo: needsUpdate ? latestFromRemote : null,
|
||||||
history: history
|
history: history,
|
||||||
|
debug: {
|
||||||
|
env: process.env.NODE_ENV,
|
||||||
|
cwd: process.cwd(),
|
||||||
|
platform: process.platform
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -110,11 +110,11 @@ export function VersionPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Source of truth for versioning comes from the API
|
// Source of truth for versioning comes from the API
|
||||||
const currentVersion = remoteInfo?.current || '0.4.0';
|
const currentVersion = remoteInfo?.current || healthIcon?.version || '---';
|
||||||
const buildDate = '2026-01-25';
|
const buildDate = '2026-01-25';
|
||||||
|
|
||||||
// Check if update is needed based on API-supplied needsUpdate flag
|
// Check if update is needed based on API-supplied needsUpdate flag
|
||||||
const needsUpdate = remoteInfo?.needsUpdate || false;
|
const needsUpdate = !!remoteInfo?.needsUpdate;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-container p-6 max-w-4xl mx-auto">
|
<div className="page-container p-6 max-w-4xl mx-auto">
|
||||||
@ -134,51 +134,48 @@ export function VersionPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Update Alert Banner - Based on Frontend Version comparison */}
|
{/* Update Alert Banner - Based on Frontend Version comparison */}
|
||||||
{needsUpdate && !updateResult && (
|
{needsUpdate && !updateResult && remoteInfo?.latestInfo && (
|
||||||
<div className={`mb-8 p-0 border rounded-xl overflow-hidden animate-in fade-in slide-in-from-top-4 duration-500 shadow-lg ${remoteInfo?.latestInfo?.type === 'urgent' ? 'border-red-200 bg-red-50' : 'border-amber-200 bg-amber-50'}`}>
|
<div className={`mb-8 p-6 border-2 rounded-2xl shadow-xl flex flex-col md:flex-row items-center justify-between gap-6 ${remoteInfo.latestInfo.type === 'urgent' ? 'border-red-400 bg-red-50' : 'border-indigo-400 bg-indigo-50'}`}>
|
||||||
<div className="p-5 flex flex-col md:flex-row md:items-center justify-between gap-4">
|
<div className="flex items-start gap-4">
|
||||||
<div className="flex items-start gap-3">
|
<div className={`p-3 rounded-xl ${remoteInfo.latestInfo.type === 'urgent' ? 'bg-red-500 text-white' : 'bg-indigo-600 text-white'}`}>
|
||||||
<div className={`p-2 rounded-lg mt-0.5 ${remoteInfo?.latestInfo?.type === 'urgent' ? 'bg-red-100 text-red-600' : 'bg-amber-100 text-amber-600'}`}>
|
<AlertTriangle size={24} />
|
||||||
<AlertTriangle size={20} />
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="space-y-1">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2">
|
||||||
<h4 className={`font-black text-lg ${remoteInfo?.latestInfo?.type === 'urgent' ? 'text-red-900' : 'text-amber-900'}`}>
|
<h4 className="text-xl font-black text-slate-900 leading-tight">
|
||||||
{remoteInfo?.latestInfo?.type === 'urgent' ? '🚨 긴급 보안/시스템 업데이트 발견' : '✨ 새로운 업데이트가 가능합니다'}
|
{remoteInfo.latestInfo.type === 'urgent' ? '신속한 시스템 업데이트가 필요합니다' : '새로운 플랫폼 기능이 준비되었습니다'}
|
||||||
</h4>
|
</h4>
|
||||||
<span className={`px-2 py-0.5 rounded text-[10px] font-black uppercase tracking-wider ${remoteInfo?.latestInfo?.type === 'urgent' ? 'bg-red-600 text-white' : 'bg-amber-600 text-white'}`}>
|
<span className={`px-2 py-0.5 rounded text-[10px] font-black uppercase tracking-wider ${remoteInfo.latestInfo.type === 'urgent' ? 'bg-red-600 text-white' : 'bg-indigo-600 text-white'}`}>
|
||||||
{remoteInfo?.latestInfo?.type || 'patch'}
|
{remoteInfo.latestInfo.type}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className={`text-sm font-medium ${remoteInfo?.latestInfo?.type === 'urgent' ? 'text-red-700' : 'text-amber-700'}`}>
|
<p className="text-slate-600 font-medium text-sm">
|
||||||
현재 버전: v{currentVersion} → 최신 버전: <span className="font-black underline underline-offset-4">{remoteInfo?.latest}</span>
|
현재: v{currentVersion} <span className="mx-2">→</span> 차기: <span className="font-black text-indigo-700 underline underline-offset-4">v{remoteInfo.latestInfo.version}</span>
|
||||||
</p>
|
</p>
|
||||||
{remoteInfo?.latestInfo && (
|
|
||||||
<div className={`mt-3 p-3 rounded-lg border text-xs leading-relaxed ${remoteInfo?.latestInfo?.type === 'urgent' ? 'bg-white/50 border-red-100 text-red-800' : 'bg-white/50 border-amber-100 text-amber-800'}`}>
|
<div className="mt-4 p-4 bg-white/80 backdrop-blur-sm rounded-xl border border-white shadow-sm">
|
||||||
<p className="font-bold mb-1">[{remoteInfo.latestInfo.title}]</p>
|
<p className="font-bold text-slate-800 text-sm mb-2">{remoteInfo.latestInfo.title}</p>
|
||||||
<ul className="space-y-1 opacity-80">
|
<ul className="space-y-1.5">
|
||||||
{remoteInfo.latestInfo.changes.slice(0, 3).map((c, i) => (
|
{remoteInfo.latestInfo.changes.map((c, i) => (
|
||||||
<li key={i} className="flex items-start gap-1.5">
|
<li key={i} className="flex items-start gap-2 text-xs text-slate-600">
|
||||||
<span className="mt-1 w-1 h-1 rounded-full bg-current opacity-50 shrink-0"></span>
|
<div className="mt-1.5 w-1 h-1 rounded-full bg-slate-400 shrink-0"></div>
|
||||||
<span>{c}</span>
|
<span>{c}</span>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
{remoteInfo.latestInfo.changes.length > 3 && <li>...외 {remoteInfo.latestInfo.changes.length - 3}건</li>}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleUpdate}
|
onClick={handleUpdate}
|
||||||
disabled={updating}
|
disabled={updating}
|
||||||
className={`px-6 py-3 rounded-xl font-black text-base transition-all shadow-md active:scale-95 disabled:opacity-50 flex items-center justify-center gap-2 whitespace-nowrap min-w-[160px] ${remoteInfo?.latestInfo?.type === 'urgent' ? 'bg-red-600 text-white hover:bg-red-700' : 'bg-amber-600 text-white hover:bg-amber-700'}`}
|
className={`px-8 py-4 rounded-xl font-black text-lg transition-all shadow-lg hover:scale-105 active:scale-95 disabled:opacity-50 flex items-center gap-2 whitespace-nowrap min-w-[180px] shadow-indigo-200 ${remoteInfo.latestInfo.type === 'urgent' ? 'bg-red-600 text-white hover:bg-red-700' : 'bg-indigo-600 text-white hover:bg-slate-900'}`}
|
||||||
>
|
>
|
||||||
{updating ? <RefreshCw size={20} className="animate-spin" /> : null}
|
{updating ? <RefreshCw size={22} className="animate-spin" /> : null}
|
||||||
{updating ? '업데이트 중...' : '지금 업데이트'}
|
{updating ? '설치 중...' : '지금 업데이트'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Update Result Message */}
|
{/* Update Result Message */}
|
||||||
@ -237,6 +234,12 @@ export function VersionPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
|
<div className="flex justify-between items-center py-2 border-b border-slate-50">
|
||||||
|
<span className="text-slate-500 text-sm flex items-center gap-2"><Info size={14} /> 현재 버전</span>
|
||||||
|
<span className="font-bold text-emerald-600 bg-emerald-50 px-3 py-1 rounded-full text-xs">
|
||||||
|
{loading ? '...' : healthIcon?.version ? `v${healthIcon.version}` : 'N/A'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div className="flex justify-between items-center py-2 border-b border-slate-50">
|
<div className="flex justify-between items-center py-2 border-b border-slate-50">
|
||||||
<span className="text-slate-500 text-sm flex items-center gap-2"><Cpu size={14} /> 런타임 환경</span>
|
<span className="text-slate-500 text-sm flex items-center gap-2"><Cpu size={14} /> 런타임 환경</span>
|
||||||
<span className="font-medium text-slate-700 text-sm">
|
<span className="font-medium text-slate-700 text-sm">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user