refactor: flatten monorepo structure to backend/ frontend/ devops/
Rename subdirectories for a cleaner single-repo layout: - website-monitoring-backend/ → backend/ - website-monitoring-frontend/ → frontend/ - website-monitoring-devops/ → devops/ Update all references in package.json scripts, CI workflows, docker-compose, pre-commit hooks, and documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
FROM node:20-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install
|
||||
|
||||
# Install Chromium for Lighthouse
|
||||
RUN apt-get update && \
|
||||
apt-get install -y wget ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libcups2 libdbus-1-3 libgdk-pixbuf2.0-0 libnspr4 libnss3 libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 xdg-utils chromium && \
|
||||
ln -s /usr/bin/chromium /usr/bin/chromium-browser
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm install -g typescript
|
||||
|
||||
RUN tsc
|
||||
|
||||
EXPOSE 5001
|
||||
|
||||
CMD ["node", "dist/express-worker.js"]
|
||||
@@ -0,0 +1,171 @@
|
||||
import express, { Request, Response } from "express";
|
||||
import { launch } from "chrome-launcher";
|
||||
import lighthouse from "lighthouse";
|
||||
import cors from "cors";
|
||||
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
|
||||
// Health check endpoint
|
||||
app.get("/health", (req: Request, res: Response) => {
|
||||
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
||||
});
|
||||
|
||||
// Enhanced lighthouse endpoint
|
||||
app.post("/lighthouse", async (req: Request, res: Response) => {
|
||||
const { url } = req.body;
|
||||
if (!url) return res.status(400).json({ error: "Missing URL" });
|
||||
|
||||
console.log(`Starting Lighthouse scan for: ${url}`);
|
||||
|
||||
try {
|
||||
// Validate URL format
|
||||
try {
|
||||
new URL(url);
|
||||
} catch (urlError) {
|
||||
return res.status(400).json({ error: "Invalid URL format" });
|
||||
}
|
||||
|
||||
const chrome = await launch({
|
||||
chromeFlags: [
|
||||
"--headless",
|
||||
"--no-sandbox",
|
||||
"--disable-dev-shm-usage",
|
||||
"--disable-gpu",
|
||||
"--no-first-run",
|
||||
"--disable-extensions",
|
||||
"--disable-background-timer-throttling",
|
||||
"--disable-backgrounding-occluded-windows",
|
||||
"--disable-renderer-backgrounding",
|
||||
"--disable-features=TranslateUI",
|
||||
"--disable-ipc-flooding-protection",
|
||||
],
|
||||
});
|
||||
|
||||
console.log(`Chrome launched on port: ${chrome.port}`);
|
||||
|
||||
const runnerResult = await lighthouse(url, {
|
||||
port: chrome.port,
|
||||
output: "json",
|
||||
logLevel: "info",
|
||||
onlyCategories: ["performance", "seo", "accessibility", "best-practices"],
|
||||
formFactor: "desktop",
|
||||
screenEmulation: {
|
||||
mobile: false,
|
||||
width: 1350,
|
||||
height: 940,
|
||||
deviceScaleFactor: 1,
|
||||
disabled: false,
|
||||
},
|
||||
throttling: {
|
||||
rttMs: 40,
|
||||
throughputKbps: 10240,
|
||||
cpuSlowdownMultiplier: 1,
|
||||
requestLatencyMs: 0,
|
||||
downloadThroughputKbps: 0,
|
||||
uploadThroughputKbps: 0,
|
||||
},
|
||||
});
|
||||
|
||||
await chrome.kill();
|
||||
|
||||
if (!runnerResult) throw new Error("Lighthouse returned no result");
|
||||
|
||||
const { categories, audits } = runnerResult.lhr;
|
||||
|
||||
// Extract key metrics for easier access
|
||||
const metrics = {
|
||||
performance: categories.performance?.score
|
||||
? Math.round(categories.performance.score * 100)
|
||||
: null,
|
||||
seo: categories.seo?.score
|
||||
? Math.round(categories.seo.score * 100)
|
||||
: null,
|
||||
accessibility: categories.accessibility?.score
|
||||
? Math.round(categories.accessibility.score * 100)
|
||||
: null,
|
||||
bestPractices: categories["best-practices"]?.score
|
||||
? Math.round(categories["best-practices"].score * 100)
|
||||
: null,
|
||||
firstContentfulPaint:
|
||||
audits["first-contentful-paint"]?.numericValue || null,
|
||||
largestContentfulPaint:
|
||||
audits["largest-contentful-paint"]?.numericValue || null,
|
||||
cumulativeLayoutShift:
|
||||
audits["cumulative-layout-shift"]?.numericValue || null,
|
||||
totalBlockingTime: audits["total-blocking-time"]?.numericValue || null,
|
||||
speedIndex: audits["speed-index"]?.numericValue || null,
|
||||
};
|
||||
|
||||
console.log(
|
||||
`Lighthouse scan completed for: ${url} - Performance: ${metrics.performance}%`,
|
||||
);
|
||||
|
||||
res.json({
|
||||
categories,
|
||||
audits,
|
||||
metrics,
|
||||
raw: runnerResult.lhr,
|
||||
timestamp: new Date().toISOString(),
|
||||
url: url,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Lighthouse scan failed for ${url}:`, error);
|
||||
res.status(500).json({
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
url: url,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Batch lighthouse endpoint for multiple URLs
|
||||
app.post("/lighthouse/batch", async (req: Request, res: Response) => {
|
||||
const { urls } = req.body;
|
||||
if (!urls || !Array.isArray(urls) || urls.length === 0) {
|
||||
return res.status(400).json({ error: "Missing or empty URLs array" });
|
||||
}
|
||||
|
||||
if (urls.length > 10) {
|
||||
return res.status(400).json({ error: "Maximum 10 URLs allowed per batch" });
|
||||
}
|
||||
|
||||
console.log(`Starting batch Lighthouse scan for ${urls.length} URLs`);
|
||||
|
||||
const results = [];
|
||||
|
||||
for (const url of urls) {
|
||||
try {
|
||||
// Make internal request to single lighthouse endpoint
|
||||
const response = await fetch(`http://localhost:${PORT}/lighthouse`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ url }),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
results.push({ url, success: response.ok, data: result });
|
||||
} catch (error) {
|
||||
results.push({
|
||||
url,
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
results,
|
||||
total: urls.length,
|
||||
successful: results.filter((r) => r.success).length,
|
||||
failed: results.filter((r) => !r.success).length,
|
||||
timestamp: new Date().toISOString(),
|
||||
});
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 5001;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Lighthouse worker listening on http://localhost:${PORT}`);
|
||||
console.log(`Health check available at http://localhost:${PORT}/health`);
|
||||
});
|
||||
+3062
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "scanner-worker",
|
||||
"version": "1.0.0",
|
||||
"main": "express-worker.ts",
|
||||
"scripts": {
|
||||
"start": "ts-node express-worker.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@supabase/supabase-js": "^2.50.0",
|
||||
"chrome-launcher": "^0.15.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^4.18.2",
|
||||
"lighthouse": "^12.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.19",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/node": "^20.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import { launch } from "chrome-launcher";
|
||||
import lighthouse from "lighthouse";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const supabase = createClient(
|
||||
process.env.SUPABASE_URL!,
|
||||
process.env.SUPABASE_SERVICE_KEY!,
|
||||
);
|
||||
|
||||
async function main() {
|
||||
// 1. Hole alle offenen Scans
|
||||
const { data: scans, error: scanError } = await supabase
|
||||
.from("scans")
|
||||
.select("id, page_id")
|
||||
.eq("status", "pending");
|
||||
|
||||
if (scanError) {
|
||||
console.error("Fehler beim Laden der Scans:", scanError);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (const scan of scans ?? []) {
|
||||
try {
|
||||
// 2. Hole die URL der Seite
|
||||
const { data: page, error: pageError } = await supabase
|
||||
.from("pages")
|
||||
.select("url")
|
||||
.eq("id", scan.page_id)
|
||||
.single();
|
||||
|
||||
if (pageError || !page) {
|
||||
console.error("Fehler beim Laden der Seite:", pageError);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. Setze Scan-Status auf "running"
|
||||
await supabase
|
||||
.from("scans")
|
||||
.update({ status: "running" })
|
||||
.eq("id", scan.id);
|
||||
|
||||
// 4. Starte Lighthouse
|
||||
const chrome = await launch({
|
||||
chromeFlags: ["--headless", "--no-sandbox", "--disable-dev-shm-usage"],
|
||||
});
|
||||
|
||||
const runnerResult = await lighthouse(page.url, {
|
||||
port: chrome.port,
|
||||
output: "json",
|
||||
logLevel: "info",
|
||||
onlyCategories: [
|
||||
"performance",
|
||||
"seo",
|
||||
"accessibility",
|
||||
"best-practices",
|
||||
],
|
||||
});
|
||||
|
||||
await chrome.kill();
|
||||
|
||||
if (!runnerResult) throw new Error("Lighthouse returned no result");
|
||||
|
||||
// 5. Speichere Ergebnisse
|
||||
await supabase.from("scan_results").insert([
|
||||
{
|
||||
scan_id: scan.id,
|
||||
raw_data: runnerResult.lhr,
|
||||
},
|
||||
]);
|
||||
|
||||
// 6. Setze Scan-Status auf "completed"
|
||||
await supabase
|
||||
.from("scans")
|
||||
.update({ status: "completed" })
|
||||
.eq("id", scan.id);
|
||||
|
||||
console.log(`Scan für ${page.url} abgeschlossen.`);
|
||||
} catch (error) {
|
||||
console.error("Scan-Fehler:", error);
|
||||
await supabase
|
||||
.from("scans")
|
||||
.update({
|
||||
status: "failed",
|
||||
error_message: error instanceof Error ? error.message : String(error),
|
||||
})
|
||||
.eq("id", scan.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"outDir": "dist",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user