Compare commits

...

2 الالتزامات

المؤلف SHA1 الرسالة التاريخ
4946d19ea7 Add root Dockerfile for deployment 2026-07-29 23:52:27 +03:00
9896be5d9a Add root Dockerfile for deployment 2026-07-29 23:52:27 +03:00
6 ملفات معدلة مع 360 إضافات و2 حذوفات

عرض الملف

@@ -1,6 +1,6 @@
FROM node:16.3.0-alpine
COPY q1-deploy-monitor /app
COPY app.js dashboard.js index.html package.json /app/
RUN mkdir -p /logs && touch /logs/logs.txt
@@ -8,4 +8,5 @@ WORKDIR /app
RUN npm install
CMD ["node", "/app/app.js"]
CMD ["node", "/app/app.js"]

82
app.js Normal file
عرض الملف

@@ -0,0 +1,82 @@
const express = require('express')
const fs = require('fs')
const path = require('path')
const app = express()
const port = 5000
const logDir = '/logs'
const logFile = path.join(logDir, 'logs.txt')
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true })
}
function getStatsFromLogFile() {
if (!fs.existsSync(logFile)) {
return { requestCount: 0, averageResponseTime: 0 }
}
const content = fs.readFileSync(logFile, 'utf8')
const lines = content.split('\n').filter(Boolean)
const requestCount = lines.length
if (requestCount === 0) {
return { requestCount: 0, averageResponseTime: 0 }
}
const totalResponseTime = lines.reduce((total, line) => {
const parts = line.trim().split(' ')
const duration = parseFloat(parts[1])
return total + (Number.isNaN(duration) ? 0 : duration)
}, 0)
return {
requestCount,
averageResponseTime: totalResponseTime / requestCount
}
}
app.use('/dashboard', express.static(path.join(__dirname, 'dashboard'), { index: false }))
app.use((req, res, next) => {
const startTime = Date.now()
const requestTime = new Date(startTime).toISOString()
res.on('finish', () => {
const durationSeconds = (Date.now() - startTime) / 1000
const line = `${req.path} ${durationSeconds.toFixed(3)} ${requestTime} ${res.statusCode}\n`
fs.appendFile(logFile, line, (err) => {
if (err) console.error('Failed to write request log:', err)
})
})
next()
})
app.get('/dashboard', (req, res) => {
res.sendFile(path.join(__dirname, 'dashboard', 'index.html'))
})
app.get('/api/stats', (req, res) => {
const { requestCount, averageResponseTime } = getStatsFromLogFile()
res.json({
requestCount,
averageResponseTime,
status: 'up'
})
})
app.get('/', (req, res) => {
res.send('Hello World from GHAYMAH')
})
app.get('/health', (req, res) => {
res.send('The app up and runing')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
console.log(`Dashboard available at http://localhost:${port}/dashboard`)
})

49
dashboard.js Normal file
عرض الملف

@@ -0,0 +1,49 @@
const requestCountEl = document.getElementById('requestCount')
const averageTimeEl = document.getElementById('averageTime')
const appStatusEl = document.getElementById('appStatus')
const statusBadgeEl = document.getElementById('statusBadge')
const statusTextEl = document.getElementById('statusText')
const lastUpdatedEl = document.getElementById('lastUpdated')
function setStatus(isUp) {
statusBadgeEl.classList.remove('up', 'down')
appStatusEl.classList.remove('up', 'down')
if (isUp) {
statusBadgeEl.classList.add('up')
appStatusEl.classList.add('up')
statusTextEl.textContent = 'Online'
appStatusEl.textContent = 'Up'
return
}
statusBadgeEl.classList.add('down')
appStatusEl.classList.add('down')
statusTextEl.textContent = 'Offline'
appStatusEl.textContent = 'Down'
}
async function fetchStats() {
try {
const response = await fetch('/api/stats')
if (!response.ok) {
throw new Error('Stats request failed')
}
const data = await response.json()
requestCountEl.textContent = data.requestCount.toLocaleString()
averageTimeEl.textContent = `${data.averageResponseTime.toFixed(3)}s`
setStatus(data.status === 'up')
lastUpdatedEl.textContent = `Last updated: ${new Date().toLocaleTimeString()}`
} catch (error) {
requestCountEl.textContent = '—'
averageTimeEl.textContent = '—'
setStatus(false)
lastUpdatedEl.textContent = 'Last updated: failed to connect'
}
}
fetchStats()
setInterval(fetchStats, 5000)

50
index.html Normal file
عرض الملف

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Dashboard</title>
<link rel="stylesheet" href="/dashboard/style.css">
</head>
<body>
<div class="dashboard">
<header class="header">
<div>
<h1>Server Dashboard</h1>
<p class="subtitle">Live monitoring for your Node.js application</p>
</div>
<div class="status-badge" id="statusBadge">
<span class="status-dot"></span>
<span id="statusText">Checking...</span>
</div>
</header>
<main class="cards">
<section class="card">
<p class="card-label">Total Requests</p>
<p class="card-value" id="requestCount"></p>
<p class="card-hint">All requests received by the server</p>
</section>
<section class="card">
<p class="card-label">Average Response Time</p>
<p class="card-value" id="averageTime"></p>
<p class="card-hint">Mean duration in seconds</p>
</section>
<section class="card">
<p class="card-label">Application Status</p>
<p class="card-value status-value" id="appStatus"></p>
<p class="card-hint">Based on health check</p>
</section>
</main>
<footer class="footer">
<span id="lastUpdated">Last updated: —</span>
<span>Auto-refresh every 5 seconds</span>
</footer>
</div>
<script src="/dashboard/dashboard.js"></script>
</body>
</html>

18
package.json Normal file
عرض الملف

@@ -0,0 +1,18 @@
{
"name": "nodeapp",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"nodemon": "^3.1.14"
}
}

158
style.css Normal file
عرض الملف

@@ -0,0 +1,158 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
min-height: 100vh;
color: #e2e8f0;
padding: 2rem;
}
.dashboard {
max-width: 1100px;
margin: 0 auto;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1.5rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.header h1 {
font-size: 2rem;
font-weight: 700;
color: #f8fafc;
}
.subtitle {
margin-top: 0.35rem;
color: #94a3b8;
font-size: 0.95rem;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.6rem;
padding: 0.65rem 1.1rem;
border-radius: 999px;
background: rgba(15, 23, 42, 0.7);
border: 1px solid rgba(148, 163, 184, 0.25);
font-weight: 600;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #64748b;
box-shadow: 0 0 0 0 rgba(100, 116, 139, 0.5);
}
.status-badge.up .status-dot {
background: #22c55e;
animation: pulse 2s infinite;
}
.status-badge.down .status-dot {
background: #ef4444;
}
.status-badge.up {
border-color: rgba(34, 197, 94, 0.35);
color: #bbf7d0;
}
.status-badge.down {
border-color: rgba(239, 68, 68, 0.35);
color: #fecaca;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1.25rem;
}
.card {
background: rgba(30, 41, 59, 0.85);
border: 1px solid rgba(148, 163, 184, 0.15);
border-radius: 16px;
padding: 1.5rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
transition: transform 0.2s ease, border-color 0.2s ease;
}
.card:hover {
transform: translateY(-2px);
border-color: rgba(96, 165, 250, 0.35);
}
.card-label {
color: #94a3b8;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: 0.75rem;
}
.card-value {
font-size: 2.4rem;
font-weight: 700;
color: #f8fafc;
line-height: 1.1;
}
.status-value.up {
color: #4ade80;
}
.status-value.down {
color: #f87171;
}
.card-hint {
margin-top: 0.75rem;
color: #64748b;
font-size: 0.85rem;
}
.footer {
margin-top: 2rem;
display: flex;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
color: #64748b;
font-size: 0.85rem;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.5);
}
70% {
box-shadow: 0 0 0 8px rgba(34, 197, 94, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);
}
}
@media (max-width: 600px) {
body {
padding: 1rem;
}
.card-value {
font-size: 2rem;
}
}