Skip to content

Commit feb577a

Browse files
committed
Working on update system
1 parent 356a6ac commit feb577a

File tree

4 files changed

+206
-36
lines changed

4 files changed

+206
-36
lines changed

public/styles.css

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,11 @@ select {
16251625
background-color: rgb(202 138 4 / 0.1);
16261626
}
16271627

1628+
.bg-green-600 {
1629+
--tw-bg-opacity: 1;
1630+
background-color: rgb(22 163 74 / var(--tw-bg-opacity, 1));
1631+
}
1632+
16281633
.bg-opacity-50 {
16291634
--tw-bg-opacity: 0.5;
16301635
}
@@ -1930,6 +1935,11 @@ select {
19301935
color: rgb(250 204 21 / var(--tw-text-opacity, 1));
19311936
}
19321937

1938+
.text-green-400 {
1939+
--tw-text-opacity: 1;
1940+
color: rgb(74 222 128 / var(--tw-text-opacity, 1));
1941+
}
1942+
19331943
.placeholder-neutral-400::-moz-placeholder {
19341944
--tw-placeholder-opacity: 1;
19351945
color: rgb(163 163 163 / var(--tw-placeholder-opacity, 1));
@@ -2178,6 +2188,11 @@ select {
21782188
background-color: rgb(255 255 255 / 0.05);
21792189
}
21802190

2191+
.hover\:bg-green-700:hover {
2192+
--tw-bg-opacity: 1;
2193+
background-color: rgb(21 128 61 / var(--tw-bg-opacity, 1));
2194+
}
2195+
21812196
.hover\:text-amber-600:hover {
21822197
--tw-text-opacity: 1;
21832198
color: rgb(217 119 6 / var(--tw-text-opacity, 1));

src/handlers/updater.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import axios from 'axios';
2+
import { execSync } from 'child_process';
3+
import fs from 'fs';
4+
import path from 'path';
5+
import logger from './logger';
6+
7+
interface GithubRelease {
8+
tag_name: string;
9+
published_at: string;
10+
}
11+
12+
interface GithubCommit {
13+
sha: string;
14+
commit: {
15+
message: string;
16+
author: {
17+
date: string;
18+
};
19+
};
20+
}
21+
22+
export async function checkForUpdates(): Promise<{
23+
hasUpdate: boolean;
24+
latestVersion: string;
25+
currentVersion: string;
26+
updateInfo?: string;
27+
}> {
28+
try {
29+
const configPath = path.join(process.cwd(), 'storage', 'config.json');
30+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
31+
const currentVersion = config.meta.version;
32+
const isDev = process.env.NODE_ENV === 'development';
33+
34+
if (isDev) {
35+
// Check latest commit on main branch
36+
const response = await axios.get(
37+
'https://api.github.com/repos/airlinklabs/panel/commits/main'
38+
);
39+
const latestCommit: GithubCommit = response.data;
40+
const currentCommit = execSync('git rev-parse HEAD').toString().trim();
41+
42+
return {
43+
hasUpdate: currentCommit !== latestCommit.sha,
44+
latestVersion: latestCommit.sha.substring(0, 7),
45+
currentVersion: currentCommit.substring(0, 7),
46+
updateInfo: latestCommit.commit.message
47+
};
48+
} else {
49+
// Check latest release
50+
const response = await axios.get(
51+
'https://api.github.com/repos/airlinklabs/panel/releases/latest'
52+
);
53+
const latestRelease: GithubRelease = response.data;
54+
const latestVersion = latestRelease.tag_name.replace('v', '');
55+
56+
return {
57+
hasUpdate: latestVersion !== currentVersion,
58+
latestVersion,
59+
currentVersion,
60+
updateInfo: `Release ${latestVersion}`
61+
};
62+
}
63+
} catch (error) {
64+
logger.error('Error checking for updates:', error);
65+
throw error;
66+
}
67+
}
68+
69+
export async function performUpdate(): Promise<boolean> {
70+
try {
71+
const backupDir = path.join(process.cwd(), 'backup');
72+
if (!fs.existsSync(backupDir)) {
73+
fs.mkdirSync(backupDir);
74+
}
75+
76+
const isDev = process.env.NODE_ENV === 'development';
77+
78+
if (isDev) {
79+
// Pull latest commits
80+
execSync('git fetch origin main', { stdio: 'inherit' });
81+
execSync('git reset --hard origin/main', { stdio: 'inherit' });
82+
} else {
83+
// Checkout latest release
84+
const response = await axios.get(
85+
'https://api.github.com/repos/airlinklabs/panel/releases/latest'
86+
);
87+
const latestRelease: GithubRelease = response.data;
88+
execSync(`git fetch && git checkout ${latestRelease.tag_name}`, { stdio: 'inherit' });
89+
}
90+
91+
// Update dependencies and rebuild
92+
execSync('npm install', { stdio: 'inherit' });
93+
execSync('npm run build-ts', { stdio: 'inherit' });
94+
95+
// Restart if using PM2 in production
96+
if (process.env.NODE_ENV === 'production') {
97+
execSync('pm2 restart panel');
98+
}
99+
100+
return true;
101+
} catch (error) {
102+
logger.error('Error performing update:', error);
103+
return false;
104+
}
105+
}

src/modules/admin/overView.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Module } from '../../handlers/moduleInit';
33
import { PrismaClient } from '@prisma/client';
44
import { isAuthenticated } from '../../handlers/utils/auth/authUtil';
55
import logger from '../../handlers/logger';
6+
import { checkForUpdates, performUpdate } from '../../handlers/updater';
67

78
const prisma = new PrismaClient();
89

@@ -60,6 +61,30 @@ const adminModule: Module = {
6061
},
6162
);
6263

64+
router.get('/admin/check-update', isAuthenticated(), async (req: Request, res: Response) => {
65+
try {
66+
const updateInfo = await checkForUpdates();
67+
res.json(updateInfo);
68+
} catch (error) {
69+
logger.error('Error checking for updates:', error);
70+
res.status(500).json({ error: 'Error checking for updates' });
71+
}
72+
});
73+
74+
router.post('/admin/perform-update', isAuthenticated(), async (req: Request, res: Response) => {
75+
try {
76+
const success = await performUpdate();
77+
if (success) {
78+
res.json({ message: 'Update completed successfully' });
79+
} else {
80+
res.status(500).json({ error: 'Error performing update' });
81+
}
82+
} catch (error) {
83+
logger.error('Error performing update:', error);
84+
res.status(500).json({ error: 'Error performing update' });
85+
}
86+
});
87+
6388
return router;
6489
},
6590
};

views/admin/overview/overview.ejs

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -41,43 +41,68 @@
4141
</dd>
4242
</div>
4343
</dl>
44-
<div class="bg-neutral-700/10 rounded-xl pb-4 mt-5 shadow sm:p-6">
45-
<script>
46-
fetch('// we dont have a version api yet')
47-
.then(response => response.json())
48-
.then(data => {
49-
const currentVersion = '<%= airlinkVersion %>';
50-
const latestVersion = data.airlink_canary.panel_latest;
51-
const versionSpan = document.getElementById('currentVersion');
52-
const releaseSpan = document.getElementById('releaseInfo');
53-
const versionStatusSpan = document.getElementById('versionStatus');
54-
55-
versionSpan.textContent = currentVersion;
56-
57-
if (currentVersion === latestVersion) {
58-
versionStatusSpan.textContent = '<%= req.translations.runningLatestVersion %>';
59-
} else {
60-
const link = document.createElement('a');
61-
link.href = 'https://github.com/airlinklabs/panel/releases/tag/' + latestVersion;
62-
link.classList.add('text-amber-500', 'transition', 'hover:text-amber-600', 'font-medium');
63-
link.textContent = '<%= req.translations.here %>';
64-
65-
const linkText = document.createTextNode(`<%= req.translations.newReleaseAirlink1 %> ${latestVersion} <%= req.translations.newReleaseAirlink2 %> `);
66-
versionStatusSpan.appendChild(linkText);
67-
versionStatusSpan.appendChild(link);
68-
versionStatusSpan.appendChild(document.createTextNode(' <%= req.translations.newReleaseAirlink3 %>'));
69-
versionStatusSpan.classList.add('text-amber-500', 'pt-2');
70-
}
71-
})
72-
.catch(error => {
73-
console.error('Error fetching latest version:', error);
74-
});
75-
</script>
76-
<img src="// comming soon" class="h-24">
77-
<p class="text-sm mt-1 font-normal text-neutral-300 mb-2">
78-
<%= req.translations.sysInfoText %> <span id="currentVersion"></span>. <span id="versionStatus"></span>
79-
</p>
44+
<div class="bg-neutral-700/10 rounded-xl pb-4 mt-5 shadow sm:p-6">
45+
<img src="// coming soon" class="h-24">
46+
<p class="text-sm mt-1 font-normal text-neutral-300 mb-2">
47+
<%= req.translations.sysInfoText %> <span id="currentVersion"></span>
48+
<span class="text-xs text-neutral-400">(<%= process.env.NODE_ENV %>)</span>
49+
</p>
50+
<div id="updateStatus" class="mb-4"></div>
51+
<div class="flex space-x-4">
52+
<button id="checkUpdateBtn" class="bg-blue-600 text-white px-4 py-2 rounded-xl hover:bg-blue-700 transition">
53+
Check for Updates
54+
</button>
55+
<button id="performUpdateBtn" class="hidden bg-green-600 text-white px-4 py-2 rounded-xl hover:bg-green-700 transition">
56+
Install Update
57+
</button>
8058
</div>
59+
</div>
60+
61+
<script>
62+
const currentVersion = '<%= airlinkVersion %>';
63+
document.getElementById('currentVersion').textContent = currentVersion;
64+
65+
document.getElementById('checkUpdateBtn').addEventListener('click', async () => {
66+
try {
67+
const response = await fetch('/admin/check-update');
68+
const data = await response.json();
69+
const statusDiv = document.getElementById('updateStatus');
70+
const updateBtn = document.getElementById('performUpdateBtn');
71+
const updateInfo = document.getElementById('updateInfo');
72+
73+
if (data.hasUpdate) {
74+
statusDiv.innerHTML = `A new version (${data.latestVersion}) is available. Current version: ${data.currentVersion}`;
75+
statusDiv.className = 'text-amber-500 mb-4';
76+
updateBtn.classList.remove('hidden');
77+
if (data.updateInfo) {
78+
updateInfo.textContent = data.updateInfo;
79+
updateInfo.classList.remove('hidden');
80+
}
81+
} else {
82+
statusDiv.innerHTML = '<%= req.translations.runningLatestVersion %>';
83+
statusDiv.className = 'text-green-400 mb-4';
84+
updateBtn.classList.add('hidden');
85+
updateInfo.classList.add('hidden');
86+
}
87+
} catch (error) {
88+
console.error('Error:', error);
89+
}
90+
});
91+
92+
document.getElementById('performUpdateBtn').addEventListener('click', async () => {
93+
if (confirm('Do you want to perform the update? The server will restart automatically.')) {
94+
try {
95+
const response = await fetch('/admin/perform-update', { method: 'POST' });
96+
const data = await response.json();
97+
alert(data.message || 'Update completed. Server will restart.');
98+
setTimeout(() => window.location.reload(), 5000);
99+
} catch (error) {
100+
console.error('Error:', error);
101+
alert('Error performing update');
102+
}
103+
}
104+
});
105+
</script>
81106
<div class="mt-5 flex justify-between space-x-8">
82107
<a href="https://discord.gg/BybfXms7JZ" class="block rounded-xl bg-amber-500 px-3 py-2 text-center text-sm font-medium text-white shadow-lg hover:bg-amber-600 transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-600 w-full">
83108
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="w-5 h-5 inline-flex mr-1 mb-0.5">

0 commit comments

Comments
 (0)