initial commit

This commit is contained in:
2026-07-22 01:12:45 +02:00
commit 124f2746ac
357 changed files with 159669 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
<?php
require_once "../config/config.php";
header('Content-Type: application/json; charset=utf-8');
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) throw new RuntimeException('id fehlt');
$stmt = $pdo->prepare("DELETE FROM finance_expenses WHERE id=:id");
$stmt->execute([':id'=>$id]);
echo json_encode(['ok'=>true], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
http_response_code(400);
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
}
+25
View File
@@ -0,0 +1,25 @@
<?php
require_once "../config/config.php";
header('Content-Type: application/json; charset=utf-8');
try {
$from = $_GET['from'] ?? date('Y-m-01');
$to = $_GET['to'] ?? date('Y-m-t');
$stmt = $pdo->prepare("
SELECT id, date, category, title, amount_eur, method, notes
FROM finance_expenses
WHERE date BETWEEN :from AND :to
ORDER BY date DESC, id DESC
");
$stmt->execute([':from' => $from, ':to' => $to]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$sum = 0;
foreach ($rows as $r) $sum += (int)$r['amount_eur'];
echo json_encode(['ok'=>true,'from'=>$from,'to'=>$to,'sum_eur'=>$sum,'items'=>$rows], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
http_response_code(400);
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
}
+47
View File
@@ -0,0 +1,47 @@
<?php
require_once "../config/config.php";
header('Content-Type: application/json; charset=utf-8');
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') throw new RuntimeException('Method not allowed');
$id = isset($_POST['id']) ? (int)$_POST['id'] : 0;
$date = trim($_POST['date'] ?? '');
$category = trim($_POST['category'] ?? '');
$title = trim($_POST['title'] ?? '');
$amount = (int)($_POST['amount_eur'] ?? 0);
$method = trim($_POST['method'] ?? 'bank');
$notes = trim($_POST['notes'] ?? '');
if (!$date || !$category || !$title) throw new RuntimeException('Pflichtfelder fehlen');
if ($amount <= 0) throw new RuntimeException('Betrag muss > 0 sein');
$allowedMethods = ['cash','bank','paypal','card','other'];
if (!in_array($method, $allowedMethods, true)) $method = 'bank';
if ($id > 0) {
$stmt = $pdo->prepare("
UPDATE finance_expenses
SET date=:date, category=:category, title=:title, amount_eur=:amount, method=:method, notes=:notes
WHERE id=:id
");
$stmt->execute([
':date'=>$date, ':category'=>$category, ':title'=>$title, ':amount'=>$amount, ':method'=>$method, ':notes'=>$notes, ':id'=>$id
]);
} else {
$stmt = $pdo->prepare("
INSERT INTO finance_expenses (date, category, title, amount_eur, method, notes)
VALUES (:date,:category,:title,:amount,:method,:notes)
");
$stmt->execute([
':date'=>$date, ':category'=>$category, ':title'=>$title, ':amount'=>$amount, ':method'=>$method, ':notes'=>$notes
]);
$id = (int)$pdo->lastInsertId();
}
echo json_encode(['ok'=>true,'id'=>$id], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
http_response_code(400);
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
}
+71
View File
@@ -0,0 +1,71 @@
<?php
require_once "../config/config.php";
header('Content-Type: application/json; charset=utf-8');
try {
// TODO: Statuswert anpassen:
// Beispiel: 2 = erledigt/abgeschlossen
$STATUS_DONE = 2;
$from = $_GET['from'] ?? null; // YYYY-MM-DD
$to = $_GET['to'] ?? null;
// Default: aktueller Monat
if (!$from || !$to) {
$from = date('Y-m-01');
$to = date('Y-m-t');
}
$fromTs = strtotime($from . ' 00:00:00');
$toTs = strtotime($to . ' 23:59:59');
$sql = "
SELECT
id,
startzeit,
endzeit,
locationtype,
location,
gage,
status,
beschreibung
FROM termine
WHERE startzeit BETWEEN :fromTs AND :toTs
AND status = :done
AND gage > 0
ORDER BY startzeit DESC
";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':fromTs' => $fromTs,
':toTs' => $toTs,
':done' => $STATUS_DONE,
]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
$sum = 0;
foreach ($rows as $r) $sum += (int)$r['gage'];
echo json_encode([
'ok' => true,
'from' => $from,
'to' => $to,
'sum_eur' => $sum,
'items' => array_map(function($r){
return [
'id' => (int)$r['id'],
'date' => date('Y-m-d', (int)$r['startzeit']),
'time' => date('H:i', (int)$r['startzeit']) . '' . date('H:i', (int)$r['endzeit']),
'location' => trim(($r['locationtype'] ?? '') . ' ' . ($r['location'] ?? '')),
'gage' => (int)$r['gage'],
'desc' => (string)($r['beschreibung'] ?? ''),
];
}, $rows),
], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
}
+106
View File
@@ -0,0 +1,106 @@
<?php
header('Content-Type: application/json; charset=utf-8');
require_once "../config/config.php";
require_once __DIR__ . '/../lib/InvoiceNo.php';
global $pdo;
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
throw new RuntimeException('Method not allowed');
}
$terminId = (int)($_POST['termin_id'] ?? 0);
if ($terminId <= 0) {
throw new RuntimeException('termin_id fehlt');
}
// Termin laden
$st = $pdo->prepare("SELECT * FROM termine WHERE id=:id");
$st->execute([':id' => $terminId]);
$t = $st->fetch(PDO::FETCH_ASSOC);
if (!$t) {
throw new RuntimeException('Termin nicht gefunden');
}
// Prüfen ob Rechnung existiert
$st = $pdo->prepare("SELECT id FROM invoices WHERE termine_id=:tid LIMIT 1");
$st->execute([':tid' => $terminId]);
if ($st->fetch()) {
throw new RuntimeException('Für diesen Termin existiert bereits eine Rechnung.');
}
$serviceDate = date('Y-m-d', (int)$t['startzeit']);
$invoiceDate = date('Y-m-d');
$dueDate = date('Y-m-d', strtotime($invoiceDate . ' +14 days'));
$year = (int)date('Y');
$invoiceNo = nextInvoiceNo($pdo, $year);
$customerName = trim($_POST['customer_name'] ?? 'Kunde (bitte bearbeiten)');
$customerStreet = trim($_POST['customer_street'] ?? '');
$customerZip = trim($_POST['customer_zip'] ?? '');
$customerCity = trim($_POST['customer_city'] ?? '');
$customerCountry = trim($_POST['customer_country'] ?? 'DE');
$gage = (int)($t['gage'] ?? 0);
$desc = "DJ-Leistung am " . date('d.m.Y', (int)$t['startzeit']);
$pdo->beginTransaction();
$pdo->prepare("
INSERT INTO invoices
(invoice_no, invoice_date, service_date, due_date, status,
customer_name, customer_street, customer_zip, customer_city, customer_country,
termine_id, amount_net_eur)
VALUES
(:no, :invdate, :servdate, :duedate, 'draft',
:cname, :cstreet, :czip, :ccity, :ccountry,
:tid, :amount)
")->execute([
':no' => $invoiceNo,
':invdate' => $invoiceDate,
':servdate' => $serviceDate,
':duedate' => $dueDate,
':cname' => $customerName,
':cstreet' => $customerStreet,
':czip' => $customerZip,
':ccity' => $customerCity,
':ccountry' => $customerCountry,
':tid' => $terminId,
':amount' => $gage
]);
$invoiceId = (int)$pdo->lastInsertId();
$pdo->prepare("
INSERT INTO invoice_items
(invoice_id, pos, description, qty, unit_price_eur, line_total_eur)
VALUES
(:iid, 1, :d, 1, :p, :p)
")->execute([
':iid' => $invoiceId,
':d' => $desc,
':p' => $gage
]);
$pdo->commit();
echo json_encode([
'ok' => true,
'invoice_id' => $invoiceId,
'invoice_no' => $invoiceNo
], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
if ($pdo && $pdo->inTransaction()) $pdo->rollBack();
http_response_code(400);
echo json_encode([
'ok' => false,
'error' => $e->getMessage()
], JSON_UNESCAPED_UNICODE);
}
+23
View File
@@ -0,0 +1,23 @@
<?php
header('Content-Type: application/json; charset=utf-8');
require_once "../config/config.php";
global $pdo;
try {
$id = (int)($_GET['id'] ?? 0);
if ($id <= 0) throw new RuntimeException('id fehlt');
$st = $pdo->prepare("SELECT * FROM invoices WHERE id=:id");
$st->execute([':id'=>$id]);
$inv = $st->fetch(PDO::FETCH_ASSOC);
if (!$inv) throw new RuntimeException('Rechnung nicht gefunden');
$it = $pdo->prepare("SELECT * FROM invoice_items WHERE invoice_id=:id ORDER BY pos ASC, id ASC");
$it->execute([':id'=>$id]);
$items = $it->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['ok'=>true,'invoice'=>$inv,'items'=>$items], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
http_response_code(400);
echo json_encode(['ok'=>false,'error'=>$e->getMessage()], JSON_UNESCAPED_UNICODE);
}
+24
View File
@@ -0,0 +1,24 @@
<?php
header('Content-Type: application/json; charset=utf-8');
require_once "../config/config.php";
global $pdo;
try {
$from = $_GET['from'] ?? date('Y-m-01');
$to = $_GET['to'] ?? date('Y-m-t');
$stmt = $pdo->prepare("
SELECT id, invoice_no, invoice_date, service_date, due_date, status,
customer_name, amount_net_eur, termine_id, pdf_path
FROM invoices
WHERE invoice_date BETWEEN :from AND :to
ORDER BY invoice_date DESC, id DESC
");
$stmt->execute([':from'=>$from, ':to'=>$to]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['ok'=>true,'from'=>$from,'to'=>$to,'items'=>$rows], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
http_response_code(400);
echo json_encode(['ok'=>false,'error'=>$e->getMessage()], JSON_UNESCAPED_UNICODE);
}
+451
View File
@@ -0,0 +1,451 @@
<?php
ini_set('display_errors', 0);
error_reporting(E_ALL);
header('Content-Type: application/json; charset=utf-8');
set_exception_handler(function(Throwable $e){
http_response_code(500);
echo json_encode([
'ok' => false,
'error' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine()
], JSON_UNESCAPED_UNICODE);
exit;
});
set_error_handler(function($severity, $message, $file, $line){
throw new ErrorException($message, 0, $severity, $file, $line);
});
require_once "../config/config.php";
global $pdo;
$config = require __DIR__ . '/../config/invoice.php';
require_once __DIR__ . '/../vendor/autoload.php';
use Dompdf\Dompdf;
use Dompdf\Options;
function h($s){ return htmlspecialchars((string)$s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); }
function eur($n){ return number_format((int)$n, 0, ',', '.') . ""; }
function imgToDataUri(string $path): string {
if (!file_exists($path)) {
throw new RuntimeException("Logo nicht gefunden: $path");
}
if (!is_readable($path)) {
throw new RuntimeException("Logo nicht lesbar (Rechte): $path");
}
$data = file_get_contents($path);
if ($data === false) {
throw new RuntimeException("Logo konnte nicht gelesen werden: $path");
}
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$mime = match($ext) {
'png' => 'image/png',
'jpg', 'jpeg' => 'image/jpeg',
'svg' => 'image/svg+xml',
default => throw new RuntimeException("Logo-Format nicht unterstützt: .$ext (bitte png/jpg/svg)")
};
return "data:$mime;base64," . base64_encode($data);
}
try {
$id = (int)($_GET['id'] ?? 0);
if ($id <= 0) throw new RuntimeException('id fehlt');
// Load invoice + items
$st = $pdo->prepare("SELECT * FROM invoices WHERE id=:id");
$st->execute([':id'=>$id]);
$inv = $st->fetch(PDO::FETCH_ASSOC);
if (!$inv) throw new RuntimeException('Rechnung nicht gefunden');
$it = $pdo->prepare("SELECT * FROM invoice_items WHERE invoice_id=:id ORDER BY pos ASC, id ASC");
$it->execute([':id'=>$id]);
$items = $it->fetchAll(PDO::FETCH_ASSOC);
// Ensure pdf dir
$pdfDir = $config['pdf_dir'];
if (!is_dir($pdfDir)) {
if (!mkdir($pdfDir, 0775, true)) throw new RuntimeException('pdf_dir kann nicht erstellt werden');
}
$issuer = $config['issuer'];
$pay = $config['payment'];
$branding = $config['branding'] ?? [];
$accent = $branding['accent'] ?? '#111';
$logoData = imgToDataUri($branding['logo_path'] ?? null);
$logoHtml = '';
if ($logoData) {
$logoHtml = '<img src="'.h($logoData).'" style="height:52px;" alt="ANJUMA Logo">';
}
$html = '
<html>
<head>
<meta charset="utf-8">
<style>
@page { margin: 26px 30px; }
body {
font-family: DejaVu Sans, Arial, sans-serif;
font-size: 11px;
color: #111;
}
/* ===== HEADER ===== */
.header {
width:100%;
}
.header-left {
float:left;
width:60%;
}
.header-right {
float:right;
width:38%;
text-align:right;
}
.clear { clear:both; }
.header-name {
font-size:14px;
font-weight:700;
}
.header-address {
font-size:10px;
color:#666;
}
.line {
height:4px;
background: '.$accent.';
margin: 12px 0 18px 0;
}
.title {
font-size:28px;
font-weight:800;
margin-bottom:10px;
}
/* ===== META ===== */
.meta {
width:100%;
margin-bottom:14px;
}
.meta td {
padding:2px 0;
}
.meta .k {
width:140px;
color:#666;
}
.meta .v {
font-weight:600;
}
/* ===== SECTIONS ===== */
.section {
margin-top:18px;
}
.section-title {
font-size:10px;
letter-spacing:.06em;
text-transform:uppercase;
color:#666;
margin-bottom:6px;
}
.section-content {
border-top:2px solid #111;
padding-top:8px;
}
/* ===== ADDRESS ===== */
.addr-left {
float:left;
width:48%;
}
.addr-right {
float:right;
width:48%;
}
/* ===== ITEMS TABLE ===== */
table.items {
width:100%;
border-collapse:collapse;
margin-top:20px;
}
table.items th {
background:#f3f3f3;
padding:8px;
font-size:10px;
text-transform:uppercase;
border-top:1px solid #ccc;
border-bottom:1px solid #ccc;
}
table.items td {
padding:10px 8px;
border-bottom:1px solid #eee;
}
.num {
text-align:right;
white-space:nowrap;
}
/* ===== SUMMARY ===== */
.summary {
margin-top:16px;
}
.summary-note {
float:left;
width:58%;
font-size:10px;
color:#555;
line-height:1.4;
}
.summary-bar {
float:right;
width:40%;
background:#f3f3f3;
border-top:3px solid #111;
padding:10px 12px;
}
.summary-bar table {
width:100%;
}
.summary-bar td {
padding:4px 0;
}
.summary-bar .grand {
font-size:18px;
font-weight:800;
}
/* ===== PAYMENT ===== */
.payment {
margin-top:20px;
border-top:2px solid #111;
padding-top:8px;
}
.payment td {
padding:3px 0;
}
.muted { color:#666; }
/* ===== FOOTER ===== */
.footer {
position: fixed;
left:30px;
right:30px;
bottom:18px;
font-size:9px;
color:#666;
border-top:1px solid #eee;
padding-top:6px;
}
</style>
</head>
<body>
<!-- HEADER -->
<div class="header">
<div class="header-left">
<div class="header-name">'.h($issuer['name']).'</div>
<div class="header-address">
'.h($issuer['street']).' • '.h($issuer['zip']).' '.h($issuer['city']).'
</div>
</div>
<div class="header-right">
'.$logoHtml.'
</div>
<div class="clear"></div>
</div>
<div class="line"></div>
<div class="title">Rechnung</div>
<table class="meta">
<tr><td class="k">Rechnungsnummer</td><td class="v">'.h($inv['invoice_no']).'</td></tr>
<tr><td class="k">Rechnungsdatum</td><td class="v">'.h(date('d.m.Y', strtotime($inv['invoice_date']))).'</td></tr>
<tr><td class="k">Leistungsdatum</td><td class="v">'.h(date('d.m.Y', strtotime($inv['service_date']))).'</td></tr>
<tr><td class="k">Fällig am</td><td class="v">'.h(date('d.m.Y', strtotime($inv['due_date']))).'</td></tr>
</table>
<!-- ADDRESSES -->
<div class="section">
<div class="addr-left">
<div class="section-title">Rechnung an</div>
<div class="section-content">
<b>'.h($inv['customer_name']).'</b><br>
'.($inv['customer_street'] ? h($inv['customer_street']).'<br>' : '').'
'.h($inv['customer_zip']).' '.h($inv['customer_city']).'<br>
'.h($inv['customer_country']).'
</div>
</div>
<div class="addr-right">
<div class="section-title">Aussteller</div>
<div class="section-content">
<b>'.h($issuer['name']).'</b><br>
'.h($issuer['street']).'<br>
'.h($issuer['zip']).' '.h($issuer['city']).'<br>
'.h($issuer['country']).'<br><br>
<span class="muted">Steuernr.:</span> '.h($issuer['tax_no']).'<br>
<span class="muted">E-Mail:</span> '.h($issuer['email']).'<br>
<span class="muted">Tel.:</span> '.h($issuer['phone']).'
</div>
</div>
<div class="clear"></div>
</div>
<!-- ITEMS -->
<table class="items">
<thead>
<tr>
<th style="width:40px;">Pos</th>
<th>Beschreibung</th>
<th class="num" style="width:70px;">Menge</th>
<th class="num" style="width:110px;">Einzelpreis</th>
<th class="num" style="width:110px;">Gesamt</th>
</tr>
</thead>
<tbody>';
// --- ITEMS LOOP (wie gehabt) ---
$pos = 1;
$sum = 0;
foreach ($items as $row) {
$line = (int)$row['line_total_eur'];
$sum += $line;
$html .= '
<tr>
<td>'.h($row['pos'] ?? $pos).'</td>
<td>'.h($row['description']).'</td>
<td class="num">'.h($row['qty']).'</td>
<td class="num">'.h(eur($row['unit_price_eur'])).'</td>
<td class="num"><b>'.h(eur($line)).'</b></td>
</tr>';
$pos++;
}
// --- CLOSE TABLE + SUMMARY + PAYMENT + FOOTER ---
$html .= '
</tbody>
</table>
<!-- SUMMARY -->
<div class="summary">
<div class="summary-note">
'.h($config['kleinunternehmer_text']).'
'.(!empty($inv['notes']) ? '<br><br>'.nl2br(h($inv['notes'])) : '').'
</div>
<div class="summary-bar">
<table>
<tr>
<td class="muted">Zwischensumme</td>
<td class="num">'.h(eur($sum)).'</td>
</tr>
<tr>
<td class="muted">Umsatzsteuer</td>
<td class="num">0 €</td>
</tr>
<tr>
<td style="padding-top:8px;"><b>Gesamtbetrag</b></td>
<td class="num grand">'.h(eur($sum)).'</td>
</tr>
</table>
</div>
<div class="clear"></div>
</div>
<!-- PAYMENT -->
<div class="payment">
<div class="section-title">Zahlungsinformationen</div>
<div class="section-content" style="border-top:0; padding-top:0;">
<div style="margin-bottom:8px;">
Bitte überweisen Sie den Betrag innerhalb von '.h($pay['pay_within_days']).' Tagen.
</div>
<table>
<tr>
<td style="width:70px;" class="muted">IBAN</td>
<td><b>'.h($pay['iban']).'</b></td>
</tr>
<tr>
<td class="muted">BIC</td>
<td><b>'.h($pay['bic']).'</b></td>
</tr>
<tr>
<td class="muted">Bank</td>
<td><b>'.h($pay['bank']).'</b></td>
</tr>
</table>
</div>
</div>
<!-- FOOTER -->
<div class="footer">
'.h($issuer['name']).' • '.h($issuer['street']).', '.h($issuer['zip']).' '.h($issuer['city']).' • Steuernr.: '.h($issuer['tax_no']).'
</div>
</body>
</html>';
// Dompdf options
$options = new Options();
$options->set('isRemoteEnabled', false);
$options->set('defaultFont', 'DejaVu Sans');
$dompdf = new Dompdf($options);
$dompdf->loadHtml($html, 'UTF-8');
$dompdf->setPaper('A4', 'portrait');
$dompdf->render();
$pdfOutput = $dompdf->output();
// Save file
$filename = $inv['invoice_no'] . '.pdf';
$fullPath = rtrim($pdfDir, '/').'/'.$filename;
$relativePath = 'storage/invoices/' . $filename;
if (file_put_contents($fullPath, $pdfOutput) === false) {
throw new RuntimeException('PDF konnte nicht gespeichert werden: ' . $fullPath);
}
// Store pdf_path in DB (relative is nicer)
$pdo->prepare("UPDATE invoices SET pdf_path=:p WHERE id=:id")
->execute([':p'=>$relativePath, ':id'=>$id]);
// Output download
header('Content-Type: application/pdf');
header('Content-Disposition: inline; filename="'.basename($filename).'"');
echo $pdfOutput;
} catch (Throwable $e) {
http_response_code(400);
header('Content-Type: application/json; charset=utf-8');
echo json_encode(['ok'=>false,'error'=>$e->getMessage()], JSON_UNESCAPED_UNICODE);
}
+27
View File
@@ -0,0 +1,27 @@
<?php
require_once __DIR__ . '/../lib/PveApi.php';
$cfg = require __DIR__ . '/../config/pve.php';
$pve = new PveApi($cfg);
header('Content-Type: application/json; charset=utf-8');
try {
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
echo json_encode(['ok' => false, 'error' => 'Method not allowed']);
exit;
}
$vmid = (int)($_POST['vmid'] ?? 0);
$action = (string)($_POST['action'] ?? '');
// optional: hier deine Dashboard-Auth checken!
if ($vmid <= 0) throw new InvalidArgumentException("vmid fehlt/ungültig");
$upid = $pve->powerAction($vmid, $action);
echo json_encode(['ok' => true, 'vmid' => $vmid, 'action' => $action, 'upid' => $upid]);
} catch (Throwable $e) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
}
+55
View File
@@ -0,0 +1,55 @@
<?php
require_once __DIR__ . '/../lib/PveApi.php';
$cfg = require __DIR__ . '/../config/pve.php';
$pve = new PveApi($cfg);
header('Content-Type: application/json; charset=utf-8');
try {
$lxcs = $pve->listLxc(); // array mit vmid, name, status, cpu, mem, etc.
$vms = $pve->listQemu(); // array mit vmid, name, status, cpu, mem, etc.
$all = [];
foreach ($lxcs as $c) {
$vmid = (int)($c['vmid'] ?? 0);
if ($vmid >= 100 && $vmid <= 199) {
$all[] = [
'vmid' => $vmid,
'type' => 'lxc',
'name' => (string)($c['name'] ?? "LXC {$vmid}"),
'status' => (string)($c['status'] ?? 'unknown'),
'uptime' => (int)($c['uptime'] ?? 0),
'cpu' => (float)($c['cpu'] ?? 0),
'mem' => (int)($c['mem'] ?? 0),
'maxmem' => (int)($c['maxmem'] ?? 0),
];
}
}
foreach ($vms as $v) {
$vmid = (int)($v['vmid'] ?? 0);
if ($vmid >= 200 && $vmid <= 299) {
$all[] = [
'vmid' => $vmid,
'type' => 'qemu',
'name' => (string)($v['name'] ?? "VM {$vmid}"),
'status' => (string)($v['status'] ?? 'unknown'),
'uptime' => (int)($v['uptime'] ?? 0),
'cpu' => (float)($v['cpu'] ?? 0),
'mem' => (int)($v['mem'] ?? 0),
'maxmem' => (int)($v['maxmem'] ?? 0),
];
}
}
// Sort: erst LXCs, dann VMs oder nach vmid
usort($all, fn($a, $b) => $a['vmid'] <=> $b['vmid']);
echo json_encode(['ok' => true, 'items' => $all], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
}
+18
View File
@@ -0,0 +1,18 @@
<?php
require_once __DIR__ . '/../lib/PveApi.php';
$cfg = require __DIR__ . '/../config/pve.php';
$pve = new PveApi($cfg);
header('Content-Type: application/json; charset=utf-8');
try {
$vmid = (int)($_GET['vmid'] ?? 0);
if ($vmid <= 0) throw new InvalidArgumentException("vmid fehlt/ungültig");
$data = $pve->statusCurrent($vmid);
echo json_encode(['ok' => true, 'vmid' => $vmid, 'data' => $data]);
} catch (Throwable $e) {
http_response_code(400);
echo json_encode(['ok' => false, 'error' => $e->getMessage()]);
}
+84
View File
@@ -0,0 +1,84 @@
<?php
header('Content-Type: application/json; charset=utf-8');
require_once "../config/config.php";
global $pdo;
if (!($pdo instanceof PDO)) {
http_response_code(500);
echo json_encode(['ok'=>false,'error'=>'PDO nicht initialisiert']);
exit;
}
try {
// Filter
$from = $_GET['from'] ?? null; // YYYY-MM-DD
$to = $_GET['to'] ?? null; // YYYY-MM-DD
$status = $_GET['status'] ?? ''; // optional
// Default: aktueller Monat
if (!$from || !$to) {
$from = date('Y-m-01');
$to = date('Y-m-t');
}
$fromTs = strtotime($from . ' 00:00:00');
$toTs = strtotime($to . ' 23:59:59');
$sql = "
SELECT
id,
startzeit,
endzeit,
locationtype,
location,
gage,
status,
beschreibung,
user_id,
anwesend
FROM termine
WHERE startzeit BETWEEN :fromTs AND :toTs
";
$params = [
':fromTs' => $fromTs,
':toTs' => $toTs,
];
if ($status !== '' && is_numeric($status)) {
$sql .= " AND status = :status ";
$params[':status'] = (int)$status;
}
$sql .= " ORDER BY startzeit DESC ";
$stmt = $pdo->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'ok' => true,
'from' => $from,
'to' => $to,
'count' => count($rows),
'items' => array_map(function($r){
return [
'id' => (int)$r['id'],
'startzeit' => (int)$r['startzeit'],
'endzeit' => (int)$r['endzeit'],
'locationtype' => (string)$r['locationtype'],
'location' => (string)$r['location'],
'gage' => (int)$r['gage'],
'status' => (int)$r['status'],
'beschreibung' => (string)($r['beschreibung'] ?? ''),
'user_id' => (int)$r['user_id'],
'anwesend' => $r['anwesend'], // JSON
];
}, $rows),
], JSON_UNESCAPED_UNICODE);
} catch (Throwable $e) {
http_response_code(400);
echo json_encode(['ok'=>false,'error'=>$e->getMessage()]);
}
+19
View File
@@ -0,0 +1,19 @@
<?php
ini_set('display_errors', 0);
error_reporting(E_ALL);
header('Content-Type: application/json; charset=utf-8');
require_once __DIR__ . '/../config/config.php';
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) { http_response_code(401); echo json_encode(['ok'=>false]); exit; }
if (($_SESSION['role'] ?? 'user') !== 'admin') { http_response_code(403); echo json_encode(['ok'=>false]); exit; }
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'Missing id']); exit; }
if ($id === (int)($_SESSION['id'] ?? 0)) { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'Du kannst dich nicht selbst löschen']); exit; }
$st = $pdo->prepare("DELETE FROM users WHERE id=:id");
$st->execute([':id'=>$id]);
echo json_encode(['ok'=>true]);
+19
View File
@@ -0,0 +1,19 @@
<?php
ini_set('display_errors', 0);
error_reporting(E_ALL);
header('Content-Type: application/json; charset=utf-8');
require_once __DIR__ . '/../config/config.php';
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) { http_response_code(401); echo json_encode(['ok'=>false]); exit; }
if (($_SESSION['role'] ?? 'user') !== 'admin') { http_response_code(403); echo json_encode(['ok'=>false]); exit; }
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'Missing id']); exit; }
if ($id === (int)($_SESSION['id'] ?? 0)) { http_response_code(400); echo json_encode(['ok'=>false,'error'=>'Du kannst dein eigenes Passwort hier nicht resetten']); exit; }
$st = $pdo->prepare("UPDATE users SET password_hash=NULL, first_login=1 WHERE id=:id");
$st->execute([':id'=>$id]);
echo json_encode(['ok'=>true]);
+22
View File
@@ -0,0 +1,22 @@
<?php
ini_set('display_errors', 0);
error_reporting(E_ALL);
header('Content-Type: application/json; charset=utf-8');
require_once __DIR__ . '/../config/config.php';
require_once __DIR__ . '/../Data.class.php';
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
http_response_code(401);
echo json_encode(['ok'=>false,'error'=>'Not logged in']); exit;
}
if (($_SESSION['role'] ?? 'user') !== 'admin') {
http_response_code(403);
echo json_encode(['ok'=>false,'error'=>'Admin only']); exit;
}
$stmt = $pdo->query("SELECT id, name, username, role, first_login, last_login FROM users ORDER BY role DESC, name ASC");
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode(['ok'=>true,'items'=>$rows], JSON_UNESCAPED_UNICODE);
+34
View File
@@ -0,0 +1,34 @@
<?php
require_once __DIR__ . "/../config/session.php";
require_once __DIR__ . "/../config/config.php";
header('Content-Type: application/json');
if (!isset($_SESSION["loggedin"]) || $_SESSION["loggedin"] !== true) {
echo json_encode(['ok' => false, 'error' => 'Not logged in']); exit;
}
if (($_SESSION["role"] ?? "user") !== "admin") {
echo json_encode(['ok' => false, 'error' => 'Forbidden']); exit;
}
// CSRF check
$csrf = $_POST['csrf'] ?? '';
if (empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $csrf)) {
echo json_encode(['ok' => false, 'error' => 'CSRF']); exit;
}
$id = (int)($_POST['id'] ?? 0);
if ($id <= 0) { echo json_encode(['ok'=>false,'error'=>'Invalid id']); exit; }
// Optional: sich selbst nicht
if ($id === (int)($_SESSION['id'] ?? 0)) {
echo json_encode(['ok'=>false,'error'=>'Not allowed for yourself']); exit;
}
try {
$stmt = $pdo->prepare("DELETE FROM remember_tokens WHERE user_id = :uid");
$stmt->execute([':uid' => $id]);
echo json_encode(['ok' => true]);
} catch (Throwable $e) {
echo json_encode(['ok' => false, 'error' => 'DB error']);
}
+67
View File
@@ -0,0 +1,67 @@
<?php
ini_set('display_errors', 0);
error_reporting(E_ALL);
header('Content-Type: application/json; charset=utf-8');
require_once __DIR__ . '/../config/config.php';
session_start();
if (!isset($_SESSION['loggedin']) || $_SESSION['loggedin'] !== true) {
http_response_code(401);
echo json_encode(['ok'=>false,'error'=>'Not logged in']); exit;
}
if (($_SESSION['role'] ?? 'user') !== 'admin') {
http_response_code(403);
echo json_encode(['ok'=>false,'error'=>'Admin only']); exit;
}
$id = isset($_POST['id']) && $_POST['id'] !== '' ? (int)$_POST['id'] : null;
$name = trim((string)($_POST['name'] ?? ''));
$username = trim((string)($_POST['username'] ?? ''));
$role = (($_POST['role'] ?? 'user') === 'admin') ? 'admin' : 'user';
$password = (string)($_POST['password'] ?? '');
if ($name === '' || $username === '') {
http_response_code(400);
echo json_encode(['ok'=>false,'error'=>'Name und Username sind Pflicht']); exit;
}
try {
if ($id === null) {
// CREATE: Passwort optional -> wenn leer: first_login=1 und password_hash=NULL
if ($password === '') {
$st = $pdo->prepare("INSERT INTO users (name, username, password_hash, role, first_login, last_login)
VALUES (:n,:u,NULL,:r,1,NULL)");
$st->execute([':n'=>$name, ':u'=>$username, ':r'=>$role]);
} else {
$hash = password_hash($password, PASSWORD_DEFAULT);
$st = $pdo->prepare("INSERT INTO users (name, username, password_hash, role, first_login, last_login)
VALUES (:n,:u,:p,:r,0,NULL)");
$st->execute([':n'=>$name, ':u'=>$username, ':p'=>$hash, ':r'=>$role]);
}
echo json_encode(['ok'=>true,'id'=>(int)$pdo->lastInsertId()], JSON_UNESCAPED_UNICODE);
exit;
}
// UPDATE
if ($password !== '') {
// Admin setzt neues Passwort -> first_login=0
$hash = password_hash($password, PASSWORD_DEFAULT);
$st = $pdo->prepare("UPDATE users SET name=:n, username=:u, role=:r, password_hash=:p, first_login=0 WHERE id=:id");
$st->execute([':n'=>$name, ':u'=>$username, ':r'=>$role, ':p'=>$hash, ':id'=>$id]);
} else {
$st = $pdo->prepare("UPDATE users SET name=:n, username=:u, role=:r WHERE id=:id");
$st->execute([':n'=>$name, ':u'=>$username, ':r'=>$role, ':id'=>$id]);
}
echo json_encode(['ok'=>true,'id'=>$id], JSON_UNESCAPED_UNICODE);
} catch (PDOException $e) {
if ((int)($e->errorInfo[1] ?? 0) === 1062) {
http_response_code(409);
echo json_encode(['ok'=>false,'error'=>'Username ist schon vergeben']); exit;
}
http_response_code(500);
echo json_encode(['ok'=>false,'error'=>'DB Fehler']); exit;
}