Initial commit: Veritas Suite Main Server and Core Engine

This commit is contained in:
2026-07-28 20:42:29 +02:00
commit 542bc6471b
18 changed files with 2890 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
[target.x86_64-pc-windows-gnullvm]
rustflags = [
"-C", "link-arg=-static",
"-C", "link-arg=-static-libgcc",
"-C", "link-self-contained=yes"
]
+42
View File
@@ -0,0 +1,42 @@
[workspace]
resolver = "2"
members = [
"crates/veritas-core",
"crates/veritas-veritas/vre",
"crates/veritas-veritas/vim",
"crates/veritas-veritas/van",
"crates/veritas-veritas/registry-explorer",
"crates/veritas-aegis/auditor-wipe",
"crates/veritas-aegis/ransomguard",
"crates/veritas-aegis/triage",
"crates/veritas-aegis/processguard",
"crates/veritas-cura/one-click-clean",
"crates/veritas-cura/easyrescue",
"crates/veritas-cura/privacy-shield",
"crates/veritas-cura/systemdoktor",
"crates/veritas-cura/spooler-reset",
"crates/veritas-cura/update-fix",
"crates/veritas-cura/system-integrator",
"crates/veritas-cura/port-doctor",
"crates/veritas-cura/association-fix",
"crates/veritas-server",
]
[workspace.package]
version = "1.0.0"
edition = "2021"
authors = ["Veritas Suite Team"]
license = "MIT OR Apache-2.0"
[workspace.dependencies]
tokio = { version = "1.38", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0"
tracing = "0.1"
tracing-subscriber = "0.3"
sha2 = "0.10"
rsa = "0.9"
chrono = { version = "0.4", default-features = false, features = ["serde", "std", "clock"] }
axum = "0.7"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "chrono", "uuid"] }
+13
View File
@@ -0,0 +1,13 @@
[package]
name = "veritas-core"
version.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
serde.workspace = true
serde_json.workspace = true
sha2.workspace = true
rsa.workspace = true
chrono.workspace = true
anyhow.workspace = true
+17
View File
@@ -0,0 +1,17 @@
use sha2::{Digest, Sha256};
/// Computes a unique hardware fingerprint (CPU / Machine identifier).
pub fn generate_hardware_id() -> String {
let mut hasher = Sha256::new();
let hostname = std::env::var("COMPUTERNAME")
.or_else(|_| std::env::var("HOSTNAME"))
.unwrap_or_else(|_| "UNKNOWN-HOST".to_string());
let username = std::env::var("USERNAME")
.or_else(|_| std::env::var("USER"))
.unwrap_or_else(|_| "UNKNOWN-USER".to_string());
let raw_id = format!("{}:{}", hostname, username);
hasher.update(raw_id.as_bytes());
format!("{:x}", hasher.finalize())
}
+115
View File
@@ -0,0 +1,115 @@
use serde::{Deserialize, Serialize};
pub mod hwid;
pub mod license;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActivationRequest {
pub license_key: String,
pub hardware_id: String,
pub app_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActivationResponse {
pub success: bool,
pub message: String,
pub signed_token: Option<String>,
pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RegisterRequest {
pub email: String,
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LoginRequest {
pub email: String,
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthResponse {
pub success: bool,
pub message: String,
pub user_id: Option<i32>,
pub email: Option<String>,
pub auth_token: Option<String>,
pub is_admin: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserLicenseInfo {
pub license_key: String,
pub plan_name: String,
pub max_devices: i32,
pub activated_devices: i64,
pub active: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserDashboardResponse {
pub success: bool,
pub email: String,
pub licenses: Vec<UserLicenseInfo>,
}
// Admin Panel Structures
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdminUserItem {
pub id: i32,
pub email: String,
pub is_admin: bool,
pub created_at: String,
pub license_count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdminLicenseItem {
pub id: i32,
pub user_id: Option<i32>,
pub user_email: Option<String>,
pub license_key: String,
pub plan_name: String,
pub max_devices: i32,
pub activated_devices: i64,
pub active: bool,
pub created_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AdminStatsResponse {
pub success: bool,
pub total_users: i64,
pub total_licenses: i64,
pub total_activations: i64,
pub users: Vec<AdminUserItem>,
pub licenses: Vec<AdminLicenseItem>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateLicenseRequest {
pub user_id: i32,
pub plan_name: String,
pub max_devices: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToggleLicenseRequest {
pub license_key: String,
pub active: bool,
}
// Download Center Structures
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SoftwareDownloadItem {
pub name: String,
pub version: String,
pub category: String,
pub file_size: String,
pub download_url: String,
pub description: String,
pub is_available: bool,
}
+82
View File
@@ -0,0 +1,82 @@
use crate::{hwid::generate_hardware_id, ActivationRequest};
use sha2::{Digest, Sha256};
pub struct LicenseClient {
pub server_url: String,
pub app_name: String,
pub secret: String,
}
impl LicenseClient {
pub fn new(server_url: impl Into<String>, app_name: impl Into<String>) -> Self {
Self {
server_url: server_url.into(),
app_name: app_name.into(),
secret: "VERITAS_SECRET_KEY_SIGNING_2026".to_string(),
}
}
pub fn generate_request(&self, license_key: &str) -> ActivationRequest {
ActivationRequest {
license_key: license_key.to_string(),
hardware_id: generate_hardware_id(),
app_name: self.app_name.clone(),
}
}
pub fn verify_token_offline(&self, token: &str, license_key: &str, target_hwid: &str) -> bool {
let parts: Vec<&str> = token.split(':').collect();
if parts.len() != 4 {
return false;
}
let token_key = parts[0];
let token_hwid = parts[1];
let token_exp: i64 = match parts[2].parse() {
Ok(exp) => exp,
Err(_) => return false,
};
let token_sig = parts[3];
if token_key != license_key || token_hwid != target_hwid {
return false;
}
if chrono::Utc::now().timestamp() > token_exp {
return false;
}
let signature_input = format!(
"{}:{}:{}:{}",
token_key, token_hwid, token_exp, self.secret
);
let mut hasher = Sha256::new();
hasher.update(signature_input.as_bytes());
let expected_sig = format!("{:x}", hasher.finalize());
token_sig == expected_sig
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_token_verification() {
let client = LicenseClient::new("http://45.156.84.238", "VRE");
let hwid = "WINDOWS-TEST-HWID-001";
let key = "VERITAS-PRO-DEMO-2026";
let exp = chrono::Utc::now().timestamp() + 3600;
let sig_input = format!("{}:{}:{}:{}", key, hwid, exp, client.secret);
let mut hasher = Sha256::new();
hasher.update(sig_input.as_bytes());
let sig = format!("{:x}", hasher.finalize());
let token = format!("{}:{}:{}:{}", key, hwid, exp, sig);
assert!(client.verify_token_offline(&token, key, hwid));
assert!(!client.verify_token_offline(&token, "WRONG-KEY", hwid));
}
}
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "veritas-server"
version.workspace = true
edition.workspace = true
authors.workspace = true
[dependencies]
veritas-core = { path = "../veritas-core" }
tokio.workspace = true
axum.workspace = true
serde.workspace = true
serde_json.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
anyhow.workspace = true
chrono.workspace = true
rsa.workspace = true
sha2.workspace = true
sqlx = { workspace = true }
tower-http = { version = "0.5", features = ["fs", "cors"] }
+813
View File
@@ -0,0 +1,813 @@
use axum::{
extract::{Query, State},
http::{header, HeaderMap, HeaderValue, StatusCode},
middleware::{self, Next},
response::{Html, IntoResponse, Redirect, Response},
routing::{get, post},
Json, Router,
};
use serde::Deserialize;
use sha2::{Digest, Sha256};
use sqlx::{PgPool, Row};
use std::{collections::HashMap, net::SocketAddr, path::Path, sync::Arc};
use tokio::sync::Mutex as AsyncMutex;
use tower_http::services::ServeDir;
use tracing_subscriber::fmt;
use veritas_core::{
ActivationRequest, ActivationResponse, AdminLicenseItem, AdminStatsResponse, AdminUserItem,
AuthResponse, CreateLicenseRequest, LoginRequest, RegisterRequest, SoftwareDownloadItem,
ToggleLicenseRequest, UserDashboardResponse, UserLicenseInfo,
};
#[derive(Clone)]
pub struct AppState {
pub db: Option<PgPool>,
pub secret: String,
pub static_dir: String,
pub rate_limiter: Arc<AsyncMutex<HashMap<String, (u32, i64)>>>,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
fmt::init();
let db_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| {
"postgres://veritas:veritas_db_password_2026!@localhost/veritas_db".to_string()
});
let db_pool = match PgPool::connect(&db_url).await {
Ok(pool) => {
println!("✅ Connected to PostgreSQL database successfully.");
Some(pool)
}
Err(e) => {
println!("⚠️ PostgreSQL connection failed ({}), running in fallback mode.", e);
None
}
};
let static_dir = if Path::new("crates/veritas-server/static").exists() {
"crates/veritas-server/static".to_string()
} else {
"static".to_string()
};
println!("📁 Serving Web Portal static files from: {}", static_dir);
let state = Arc::new(AppState {
db: db_pool,
secret: "VERITAS_SECRET_KEY_SIGNING_2026_ENTERPRISE_HMAC".to_string(),
static_dir,
rate_limiter: Arc::new(AsyncMutex::new(HashMap::new())),
});
let app = Router::new()
// Clean Pretty URLs (NO .html in URL bar)
.route("/", get(serve_index))
.route("/downloads", get(serve_downloads))
.route("/portal", get(serve_portal))
.route("/admin", get(serve_admin))
// Redirect legacy .html URLs to clean URLs
.route("/index.html", get(|| async { Redirect::permanent("/") }))
.route("/downloads.html", get(|| async { Redirect::permanent("/downloads") }))
.route("/portal.html", get(|| async { Redirect::permanent("/portal") }))
.route("/admin.html", get(|| async { Redirect::permanent("/admin") }))
// Core API Endpoints
.route("/health", get(health_check))
.route("/api/v1/activate", post(activate_license))
.route("/api/v1/auth/register", post(register_user))
.route("/api/v1/auth/login", post(login_user))
.route("/api/v1/account/dashboard", get(get_dashboard))
.route("/api/v1/downloads/list", get(get_downloads))
// Server-Side Protected Admin Endpoints (Requires Admin JWT Token)
.route("/api/v1/admin/stats", get(get_admin_stats))
.route("/api/v1/admin/create-license", post(admin_create_license))
.route("/api/v1/admin/toggle-license", post(admin_toggle_license))
.fallback_service(ServeDir::new(&state.static_dir))
.layer(middleware::from_fn(add_security_headers))
.with_state(state);
let port: u16 = std::env::var("PORT")
.unwrap_or_else(|_| "8080".to_string())
.parse()
.unwrap_or(8080);
let addr = SocketAddr::from(([0, 0, 0, 0], port));
println!("🌐 Veritas Suite Enterprise Secure Server running on http://{}", addr);
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(listener, app).await?;
Ok(())
}
// TASK 3: HTTP Security Headers Middleware
async fn add_security_headers(req: axum::extract::Request, next: Next) -> Response {
let mut res = next.run(req).await;
let headers = res.headers_mut();
headers.insert(
header::STRICT_TRANSPORT_SECURITY,
HeaderValue::from_static("max-age=31536000; includeSubDomains; preload"),
);
headers.insert(
header::X_FRAME_OPTIONS,
HeaderValue::from_static("DENY"),
);
headers.insert(
header::X_CONTENT_TYPE_OPTIONS,
HeaderValue::from_static("nosniff"),
);
headers.insert(
header::X_XSS_PROTECTION,
HeaderValue::from_static("1; mode=block"),
);
headers.insert(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://fonts.gstatic.com; img-src 'self' data:; font-src 'self' https://fonts.gstatic.com;"),
);
res
}
async fn serve_index(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let path = Path::new(&state.static_dir).join("index.html");
match std::fs::read_to_string(path) {
Ok(html) => Html(html).into_response(),
Err(_) => (StatusCode::NOT_FOUND, "Index page not found").into_response(),
}
}
async fn serve_downloads(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let path = Path::new(&state.static_dir).join("downloads.html");
match std::fs::read_to_string(path) {
Ok(html) => Html(html).into_response(),
Err(_) => (StatusCode::NOT_FOUND, "Downloads page not found").into_response(),
}
}
async fn serve_portal(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let path = Path::new(&state.static_dir).join("portal.html");
match std::fs::read_to_string(path) {
Ok(html) => Html(html).into_response(),
Err(_) => (StatusCode::NOT_FOUND, "Portal page not found").into_response(),
}
}
async fn serve_admin(State(state): State<Arc<AppState>>) -> impl IntoResponse {
let path = Path::new(&state.static_dir).join("admin.html");
match std::fs::read_to_string(path) {
Ok(html) => Html(html).into_response(),
Err(_) => (StatusCode::NOT_FOUND, "Admin page not found").into_response(),
}
}
async fn health_check() -> &'static str {
"OK - Veritas Suite Enterprise Secure Backend v1.4"
}
fn hash_password(password: &str) -> String {
let mut hasher = Sha256::new();
hasher.update(format!("VERITAS_SALT_2026:{}", password).as_bytes());
format!("{:x}", hasher.finalize())
}
// TASK 2: Cryptographic JWT Generator & Validator with Expiration
fn create_jwt_token(user_id: i32, email: &str, is_admin: bool, secret: &str) -> String {
let exp = (chrono::Utc::now() + chrono::Duration::days(30)).timestamp();
let payload = format!("{}:{}:{}:{}", user_id, email, is_admin, exp);
let mut hasher = Sha256::new();
hasher.update(format!("{}:{}", payload, secret).as_bytes());
let sig = format!("{:x}", hasher.finalize());
format!("{}.{}", payload, sig)
}
fn verify_jwt_token(token: &str, secret: &str) -> Option<(i32, String, bool)> {
let parts: Vec<&str> = token.split('.').collect();
if parts.len() != 2 {
return None;
}
let payload = parts[0];
let sig = parts[1];
let mut hasher = Sha256::new();
hasher.update(format!("{}:{}", payload, secret).as_bytes());
let expected_sig = format!("{:x}", hasher.finalize());
if sig != expected_sig {
return None;
}
let fields: Vec<&str> = payload.split(':').collect();
if fields.len() != 4 {
return None;
}
let uid: i32 = fields[0].parse().ok()?;
let email = fields[1].to_string();
let is_admin: bool = fields[2].parse().ok()?;
let exp: i64 = fields[3].parse().ok()?;
// Check expiration timestamp
if chrono::Utc::now().timestamp() > exp {
return None;
}
Some((uid, email, is_admin))
}
// Verify Admin Token Guard
async fn verify_admin_token(headers: &HeaderMap, state: &AppState) -> bool {
let auth_header = match headers.get("Authorization") {
Some(val) => match val.to_str() {
Ok(s) => s,
Err(_) => return false,
},
None => return false,
};
let token = auth_header.trim_start_matches("Bearer ").trim();
if token.is_empty() {
return false;
}
if let Some((_uid, _email, is_admin)) = verify_jwt_token(token, &state.secret) {
return is_admin;
}
false
}
// TASK 1: API Rate Limiter Guard (Brute Force Protection)
async fn check_rate_limit(state: &AppState, key: &str) -> bool {
let mut limiter = state.rate_limiter.lock().await;
let now = chrono::Utc::now().timestamp();
let entry = limiter.entry(key.to_string()).or_insert((0, now));
// Reset window after 60 seconds
if now - entry.1 > 60 {
entry.0 = 0;
entry.1 = now;
}
entry.0 += 1;
entry.0 <= 5 // Max 5 attempts per 60s
}
async fn register_user(
State(state): State<Arc<AppState>>,
Json(payload): Json<RegisterRequest>,
) -> Json<AuthResponse> {
if payload.email.trim().is_empty() || payload.password.trim().is_empty() {
return Json(AuthResponse {
success: false,
message: "Email and password are required.".to_string(),
user_id: None,
email: None,
auth_token: None,
is_admin: None,
});
}
if let Some(pool) = &state.db {
let pass_hash = hash_password(&payload.password);
let insert_res = sqlx::query(
"INSERT INTO users (email, password_hash) VALUES ($1, $2) RETURNING id, is_admin"
)
.bind(&payload.email)
.bind(&pass_hash)
.fetch_one(pool)
.await;
match insert_res {
Ok(row) => {
let user_id: i32 = row.get("id");
let is_admin: bool = row.get("is_admin");
let lic_key = format!("VERITAS-PRO-{}-{}", user_id, &pass_hash[..8].to_uppercase());
let _ = sqlx::query(
"INSERT INTO licenses (user_id, license_key, plan_name, max_devices) VALUES ($1, $2, $3, $4)"
)
.bind(user_id)
.bind(&lic_key)
.bind("Veritas Suite Pro")
.bind(5)
.execute(pool)
.await;
let jwt = create_jwt_token(user_id, &payload.email, is_admin, &state.secret);
Json(AuthResponse {
success: true,
message: "User registered successfully. License key generated.".to_string(),
user_id: Some(user_id),
email: Some(payload.email),
auth_token: Some(jwt),
is_admin: Some(is_admin),
})
}
Err(_) => Json(AuthResponse {
success: false,
message: "Email already registered or registration failed.".to_string(),
user_id: None,
email: None,
auth_token: None,
is_admin: None,
}),
}
} else {
Json(AuthResponse {
success: false,
message: "Database unavailable.".to_string(),
user_id: None,
email: None,
auth_token: None,
is_admin: None,
})
}
}
async fn login_user(
State(state): State<Arc<AppState>>,
Json(payload): Json<LoginRequest>,
) -> Json<AuthResponse> {
// Check Rate Limiting for IP / email combination
if !check_rate_limit(&state, &payload.email).await {
return Json(AuthResponse {
success: false,
message: "⚠️ Zu viele Anmeldeversuche. Bitte warte 60 Sekunden (Brute-Force Schutz).".to_string(),
user_id: None,
email: None,
auth_token: None,
is_admin: None,
});
}
if let Some(pool) = &state.db {
let pass_hash = hash_password(&payload.password);
let user_res = sqlx::query(
"SELECT id, email, is_admin FROM users WHERE email = $1 AND password_hash = $2"
)
.bind(&payload.email)
.bind(&pass_hash)
.fetch_optional(pool)
.await;
match user_res {
Ok(Some(row)) => {
let user_id: i32 = row.get("id");
let email: String = row.get("email");
let is_admin: bool = row.get("is_admin");
let jwt = create_jwt_token(user_id, &email, is_admin, &state.secret);
Json(AuthResponse {
success: true,
message: "Login successful.".to_string(),
user_id: Some(user_id),
email: Some(email),
auth_token: Some(jwt),
is_admin: Some(is_admin),
})
}
_ => Json(AuthResponse {
success: false,
message: "Invalid email or password.".to_string(),
user_id: None,
email: None,
auth_token: None,
is_admin: None,
}),
}
} else {
Json(AuthResponse {
success: false,
message: "Database unavailable.".to_string(),
user_id: None,
email: None,
auth_token: None,
is_admin: None,
})
}
}
#[derive(Deserialize)]
pub struct DashboardQuery {
pub user_id: i32,
}
async fn get_dashboard(
State(state): State<Arc<AppState>>,
Query(query): Query<DashboardQuery>,
) -> Json<UserDashboardResponse> {
if let Some(pool) = &state.db {
let user_res = sqlx::query("SELECT email FROM users WHERE id = $1")
.bind(query.user_id)
.fetch_optional(pool)
.await;
if let Ok(Some(u_row)) = user_res {
let email: String = u_row.get("email");
let lics = sqlx::query(
"SELECT license_key, plan_name, max_devices, active FROM licenses WHERE user_id = $1"
)
.bind(query.user_id)
.fetch_all(pool)
.await
.unwrap_or_default();
let mut license_infos = Vec::new();
for l in lics {
let lic_key: String = l.get("license_key");
let plan_name: String = l.get("plan_name");
let max_devices: i32 = l.get("max_devices");
let active: bool = l.get("active");
let count_res = sqlx::query(
"SELECT COUNT(*) as count FROM activations WHERE license_key = $1"
)
.bind(&lic_key)
.fetch_one(pool)
.await;
let count: i64 = count_res.map(|r| r.get("count")).unwrap_or(0);
license_infos.push(UserLicenseInfo {
license_key: lic_key,
plan_name,
max_devices,
activated_devices: count,
active,
});
}
return Json(UserDashboardResponse {
success: true,
email,
licenses: license_infos,
});
}
}
Json(UserDashboardResponse {
success: false,
email: "".to_string(),
licenses: vec![],
})
}
async fn get_downloads() -> Json<Vec<SoftwareDownloadItem>> {
let items = vec![
SoftwareDownloadItem {
name: "Veritas SpoolerReset".to_string(),
version: "v1.2.0 (Commercial Edition)".to_string(),
category: "Windows Fixer (Cura)".to_string(),
file_size: "14.2 MB".to_string(),
download_url: "/spooler-reset.exe".to_string(),
description: "Autonomous Windows Druckspooler & Warteschlangen-Reparatur mit GPU GUI, UAC Elevation & VPS Lizenzprüfung.".to_string(),
is_available: true,
},
SoftwareDownloadItem {
name: "Veritas RansomGuard".to_string(),
version: "v1.0.0 (Commercial Edition)".to_string(),
category: "IT-Sicherheit (Aegis)".to_string(),
file_size: "14.4 MB".to_string(),
download_url: "/ransomguard.exe".to_string(),
description: "Echtzeit Shannon-Entropie Scanner & Kanarienvogel-Köder gegen Krypto-Trojaner mit Autostart & VPS Aktivierung.".to_string(),
is_available: true,
},
];
Json(items)
}
// Server-Side Protected Admin APIs (Requires valid Admin JWT token)
async fn get_admin_stats(
headers: HeaderMap,
State(state): State<Arc<AppState>>,
) -> Json<AdminStatsResponse> {
if !verify_admin_token(&headers, &state).await {
return Json(AdminStatsResponse {
success: false,
total_users: 0,
total_licenses: 0,
total_activations: 0,
users: vec![],
licenses: vec![],
});
}
if let Some(pool) = &state.db {
let u_count: i64 = sqlx::query("SELECT COUNT(*) as count FROM users")
.fetch_one(pool)
.await
.map(|r| r.get("count"))
.unwrap_or(0);
let l_count: i64 = sqlx::query("SELECT COUNT(*) as count FROM licenses")
.fetch_one(pool)
.await
.map(|r| r.get("count"))
.unwrap_or(0);
let a_count: i64 = sqlx::query("SELECT COUNT(*) as count FROM activations")
.fetch_one(pool)
.await
.map(|r| r.get("count"))
.unwrap_or(0);
let users_rows = sqlx::query(
"SELECT u.id, u.email, u.is_admin, u.created_at, COUNT(l.id) as l_count FROM users u LEFT JOIN licenses l ON u.id = l.user_id GROUP BY u.id, u.email, u.is_admin, u.created_at ORDER BY u.id DESC"
)
.fetch_all(pool)
.await
.unwrap_or_default();
let mut users_list = Vec::new();
for r in users_rows {
let created: chrono::NaiveDateTime = r.get("created_at");
users_list.push(AdminUserItem {
id: r.get("id"),
email: r.get("email"),
is_admin: r.get("is_admin"),
created_at: created.format("%Y-%m-%d %H:%M").to_string(),
license_count: r.get("l_count"),
});
}
let lics_rows = sqlx::query(
"SELECT l.id, l.user_id, u.email as user_email, l.license_key, l.plan_name, l.max_devices, l.active, l.created_at FROM licenses l LEFT JOIN users u ON l.user_id = u.id ORDER BY l.id DESC"
)
.fetch_all(pool)
.await
.unwrap_or_default();
let mut lics_list = Vec::new();
for r in lics_rows {
let lic_key: String = r.get("license_key");
let created: chrono::NaiveDateTime = r.get("created_at");
let count_res = sqlx::query("SELECT COUNT(*) as count FROM activations WHERE license_key = $1")
.bind(&lic_key)
.fetch_one(pool)
.await;
let act_count: i64 = count_res.map(|cr| cr.get("count")).unwrap_or(0);
lics_list.push(AdminLicenseItem {
id: r.get("id"),
user_id: r.get("user_id"),
user_email: r.get("user_email"),
license_key: lic_key,
plan_name: r.get("plan_name"),
max_devices: r.get("max_devices"),
activated_devices: act_count,
active: r.get("active"),
created_at: created.format("%Y-%m-%d %H:%M").to_string(),
});
}
return Json(AdminStatsResponse {
success: true,
total_users: u_count,
total_licenses: l_count,
total_activations: a_count,
users: users_list,
licenses: lics_list,
});
}
Json(AdminStatsResponse {
success: false,
total_users: 0,
total_licenses: 0,
total_activations: 0,
users: vec![],
licenses: vec![],
})
}
async fn admin_create_license(
headers: HeaderMap,
State(state): State<Arc<AppState>>,
Json(payload): Json<CreateLicenseRequest>,
) -> Json<ActivationResponse> {
if !verify_admin_token(&headers, &state).await {
return Json(ActivationResponse {
success: false,
message: "Unauthorized: Admin privileges required.".to_string(),
signed_token: None,
expires_at: None,
});
}
if let Some(pool) = &state.db {
let lic_key = format!(
"VERITAS-{}-{}-{}",
payload.plan_name.to_uppercase().replace(" ", ""),
payload.user_id,
&sha2::Sha256::digest(chrono::Utc::now().to_string().as_bytes())[..4]
.iter()
.map(|b| format!("{:02X}", b))
.collect::<String>()
);
let res = sqlx::query(
"INSERT INTO licenses (user_id, license_key, plan_name, max_devices) VALUES ($1, $2, $3, $4)"
)
.bind(payload.user_id)
.bind(&lic_key)
.bind(&payload.plan_name)
.bind(payload.max_devices)
.execute(pool)
.await;
if res.is_ok() {
return Json(ActivationResponse {
success: true,
message: format!("License created: {}", lic_key),
signed_token: Some(lic_key),
expires_at: None,
});
}
}
Json(ActivationResponse {
success: false,
message: "Failed to create license.".to_string(),
signed_token: None,
expires_at: None,
})
}
async fn admin_toggle_license(
headers: HeaderMap,
State(state): State<Arc<AppState>>,
Json(payload): Json<ToggleLicenseRequest>,
) -> Json<ActivationResponse> {
if !verify_admin_token(&headers, &state).await {
return Json(ActivationResponse {
success: false,
message: "Unauthorized: Admin privileges required.".to_string(),
signed_token: None,
expires_at: None,
});
}
if let Some(pool) = &state.db {
let res = sqlx::query("UPDATE licenses SET active = $1 WHERE license_key = $2")
.bind(payload.active)
.bind(&payload.license_key)
.execute(pool)
.await;
if res.is_ok() {
return Json(ActivationResponse {
success: true,
message: format!("License active state set to {}", payload.active),
signed_token: None,
expires_at: None,
});
}
}
Json(ActivationResponse {
success: false,
message: "Failed to update license state.".to_string(),
signed_token: None,
expires_at: None,
})
}
async fn activate_license(
State(state): State<Arc<AppState>>,
Json(payload): Json<ActivationRequest>,
) -> Json<ActivationResponse> {
tracing::info!(
"Activation request: key={}, hwid={}, app={}",
payload.license_key,
payload.hardware_id,
payload.app_name
);
if payload.license_key.trim().is_empty() || payload.hardware_id.trim().is_empty() {
return Json(ActivationResponse {
success: false,
message: "License key and Hardware ID are required.".to_string(),
signed_token: None,
expires_at: None,
});
}
if let Some(pool) = &state.db {
let lic_result = sqlx::query(
"SELECT max_devices, active FROM licenses WHERE license_key = $1"
)
.bind(&payload.license_key)
.fetch_optional(pool)
.await;
match lic_result {
Ok(Some(row)) => {
let active: bool = row.get("active");
let max_devices: i32 = row.get("max_devices");
if !active {
return Json(ActivationResponse {
success: false,
message: "License key has been deactivated.".to_string(),
signed_token: None,
expires_at: None,
});
}
let count_result = sqlx::query(
"SELECT COUNT(*) as count FROM activations WHERE license_key = $1"
)
.bind(&payload.license_key)
.fetch_one(pool)
.await;
let activated_count: i64 = count_result.map(|r| r.get("count")).unwrap_or(0);
let max_devices = max_devices as i64;
let is_already_active = sqlx::query(
"SELECT id FROM activations WHERE license_key = $1 AND hardware_id = $2"
)
.bind(&payload.license_key)
.bind(&payload.hardware_id)
.fetch_optional(pool)
.await
.ok()
.flatten()
.is_some();
if !is_already_active && activated_count >= max_devices {
return Json(ActivationResponse {
success: false,
message: format!(
"Device limit reached ({}/{} max devices).",
activated_count, max_devices
),
signed_token: None,
expires_at: None,
});
}
let _ = sqlx::query(
"INSERT INTO activations (license_key, hardware_id, app_name) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING"
)
.bind(&payload.license_key)
.bind(&payload.hardware_id)
.bind(&payload.app_name)
.execute(pool)
.await;
}
Ok(None) => {
return Json(ActivationResponse {
success: false,
message: "Invalid license key.".to_string(),
signed_token: None,
expires_at: None,
});
}
Err(e) => {
tracing::error!("DB Query Error: {}", e);
return Json(ActivationResponse {
success: false,
message: "Internal server error validating license.".to_string(),
signed_token: None,
expires_at: None,
});
}
}
}
let expires = chrono::Utc::now() + chrono::Duration::days(365);
let signature_input = format!(
"{}:{}:{}:{}",
payload.license_key,
payload.hardware_id,
expires.timestamp(),
state.secret
);
let mut hasher = Sha256::new();
hasher.update(signature_input.as_bytes());
let sig_hex = format!("{:x}", hasher.finalize());
let token = format!(
"{}:{}:{}:{}",
payload.license_key,
payload.hardware_id,
expires.timestamp(),
sig_hex
);
Json(ActivationResponse {
success: true,
message: "License activated successfully.".to_string(),
signed_token: Some(token),
expires_at: Some(expires),
})
}
+169
View File
@@ -0,0 +1,169 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Administrator Kontrollzentrum | Veritas Suite</title>
<meta name="description" content="Geschütztes Administrator-Portal der Veritas Suite: B2B-Statistiken, Benutzerverwaltung, Lizenzerstellung und Geräte-Logs.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body class="admin-page">
<!-- Background Orbs -->
<div class="glow-orb orb-purple"></div>
<div class="glow-orb orb-cyan" style="background: radial-gradient(circle, rgba(245, 158, 11, 0.15) 0%, rgba(0, 0, 0, 0) 70%);"></div>
<!-- Header Navigation -->
<header class="navbar">
<div class="nav-container">
<a href="/" class="logo">
<div class="logo-shield" style="background: linear-gradient(135deg, #f59e0b, #b45309);">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
</div>
<div class="logo-brand">
<span class="brand-name" style="color: #fbbf24;">VERITAS ADMIN</span>
<span class="brand-tag" style="background: rgba(245, 158, 11, 0.2); color: #fef08a;">CONTROL CENTER</span>
</div>
</a>
<nav class="nav-links">
<a href="/" class="nav-item">🌐 Startseite</a>
<a href="/downloads" class="nav-item">📥 Downloads</a>
<a href="/portal" class="nav-item">👤 Kundenportal</a>
<a href="https://mail.kanjiv.at/SOGo/index/" target="_blank" class="nav-item" style="color: #38bdf8;">📧 E-Mail Dienst</a>
<a href="https://git.kanjiv.at/" target="_blank" class="nav-item" style="color: #a855f7;">🐙 Git Server</a>
<a href="/admin" class="nav-item active" style="color: #fbbf24; border-bottom: 2px solid #f59e0b;">👑 Admin-Zentrale</a>
</nav>
<div class="server-status-pill" id="status-pill">
<span class="status-indicator"></span>
<span id="status-text">Server: Verbinde...</span>
</div>
</div>
</header>
<!-- Admin Section -->
<section class="section" style="padding-top: 7rem;">
<div class="container">
<!-- Global Alert Box -->
<div id="admin-alert" class="alert-box" style="display: none; margin-bottom: 1.5rem; padding: 12px 16px; border-radius: 8px; font-weight: 500;"></div>
<!-- STATE 1: ACCESS DENIED CARD (If not logged in as Admin in Customer Portal) -->
<div id="admin-logged-out-state" class="portal-card glass-panel" style="max-width: 520px; margin: 3rem auto; padding: 2.5rem; border-radius: 16px; border: 1px solid rgba(245, 158, 11, 0.3); background: rgba(24, 34, 53, 0.85); text-align: center;">
<div style="font-size: 3.5rem; margin-bottom: 1rem;">🔒</div>
<h2 style="color: #fbbf24; margin-bottom: 0.5rem;">Zugriff geschützt</h2>
<p style="color: #cbd5e1; font-size: 0.95rem; line-height: 1.6; margin-bottom: 1.8rem;">
Die Admin-Zentrale ist geschützt. Bitte melde dich zuerst im <strong>Kundenportal</strong> mit deinem Administrator-Konto an.
</p>
<a href="/portal" class="btn btn-glow btn-block" style="display: inline-block; background: linear-gradient(135deg, #f59e0b, #b45309); color: #fff; font-weight: 700; padding: 12px 24px; border-radius: 8px; text-decoration: none;">
👤 Zum Kundenportal & Admin-Login
</a>
</div>
<!-- STATE 2: FULL ADMIN CONTROL DASHBOARD (If logged in as Admin in Customer Portal) -->
<div id="admin-logged-in-state" style="display: none;">
<!-- Admin Header Bar -->
<div class="dash-user-bar glass-panel" style="display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.5rem; border-radius: 12px; background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.3); margin-bottom: 2rem;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="font-size: 2rem;">👑</span>
<div>
<span style="color: #fef08a; font-size: 0.8rem; font-weight: 700; text-transform: uppercase;">Administrator Kontrollzentrum</span>
<h3 id="admin-user-email-display" style="color: #fff; margin: 0;">admin@kanjiv.at</h3>
</div>
</div>
<button class="btn btn-outline-danger" onclick="logoutAdmin()">Admin Abmelden</button>
</div>
<!-- KPI Metrics Cards -->
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1.2rem; margin-bottom: 2rem;">
<div class="glass-panel" style="padding: 1.5rem; border-radius: 12px; background: rgba(24, 34, 53, 0.7); text-align: center; border: 1px solid rgba(255,255,255,0.08);">
<span style="font-size: 2.2rem; font-weight: 800; color: #fff;" id="admin-stat-users">0</span>
<span style="display: block; color: #94a3b8; font-size: 0.85rem; margin-top: 4px;">👥 Registrierte Benutzer</span>
</div>
<div class="glass-panel" style="padding: 1.5rem; border-radius: 12px; background: rgba(24, 34, 53, 0.7); text-align: center; border: 1px solid rgba(255,255,255,0.08);">
<span style="font-size: 2.2rem; font-weight: 800; color: #38bdf8;" id="admin-stat-licenses">0</span>
<span style="display: block; color: #94a3b8; font-size: 0.85rem; margin-top: 4px;">🔑 Aktive Lizenzen</span>
</div>
<div class="glass-panel" style="padding: 1.5rem; border-radius: 12px; background: rgba(24, 34, 53, 0.7); text-align: center; border: 1px solid rgba(255,255,255,0.08);">
<span style="font-size: 2.2rem; font-weight: 800; color: #34d399;" id="admin-stat-activations">0</span>
<span style="display: block; color: #94a3b8; font-size: 0.85rem; margin-top: 4px;">⚡ Geräte-Aktivierungen</span>
</div>
</div>
<!-- Create License Form Card -->
<div class="glass-panel" style="padding: 1.8rem; border-radius: 14px; background: rgba(24, 34, 53, 0.8); border: 1px solid rgba(245, 158, 11, 0.3); margin-bottom: 2rem;">
<h3 style="color: #fbbf24; margin-bottom: 1rem; font-size: 1.2rem;"> Neue Manuelle B2B-Lizenz Erstellen</h3>
<form onsubmit="handleAdminCreateLicense(event)" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.2rem; align-items: end;">
<div class="form-group">
<label style="display: block; margin-bottom: 6px; color: #cbd5e1; font-size: 0.9rem;">Benutzer-ID (Ziel-User)</label>
<input type="number" id="admin-create-uid" value="1" required style="width: 100%; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); background: rgba(3, 7, 18, 0.8); color: #fff;">
</div>
<div class="form-group">
<label style="display: block; margin-bottom: 6px; color: #cbd5e1; font-size: 0.9rem;">Plan-Bezeichnung</label>
<select id="admin-create-plan" style="width: 100%; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); background: rgba(3, 7, 18, 0.8); color: #fff;">
<option value="Veritas Suite Pro">Veritas Suite Pro (5 Geräte)</option>
<option value="Veritas Enterprise Forensics">Veritas Enterprise Forensics (25 Geräte)</option>
<option value="Veritas Ultimate Care">Veritas Ultimate Care (10 Geräte)</option>
</select>
</div>
<div class="form-group">
<label style="display: block; margin-bottom: 6px; color: #cbd5e1; font-size: 0.9rem;">Max Geräte-Slots</label>
<input type="number" id="admin-create-max" value="5" required style="width: 100%; padding: 10px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); background: rgba(3, 7, 18, 0.8); color: #fff;">
</div>
<div class="form-group">
<button type="submit" class="btn btn-emerald" style="width: 100%; padding: 10px;">🔑 Lizenz Schlüssel Generieren</button>
</div>
</form>
</div>
<!-- Admin System Licenses Table -->
<div class="glass-panel" style="padding: 1.8rem; border-radius: 14px; background: rgba(24, 34, 53, 0.8); border: 1px solid rgba(255,255,255,0.08);">
<h3 style="color: #fff; margin-bottom: 1rem; font-size: 1.2rem;">📋 Alle Lizenzen & Sperr-Status im System</h3>
<div style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse; text-align: left;">
<thead>
<tr style="background: rgba(3, 7, 18, 0.8); color: #94a3b8; font-size: 0.9rem;">
<th style="padding: 12px;">ID</th>
<th style="padding: 12px;">Lizenzschlüssel</th>
<th style="padding: 12px;">Kunden E-Mail</th>
<th style="padding: 12px;">Plan</th>
<th style="padding: 12px;">Geräte</th>
<th style="padding: 12px;">Status</th>
<th style="padding: 12px;">Aktion</th>
</tr>
</thead>
<tbody id="admin-licenses-table-body" style="color: #cbd5e1;">
<tr><td colspan="7" style="padding: 16px; text-align: center;">Lade Systemdaten...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container footer-content">
<div class="footer-brand">
<span class="logo-shield" style="background: linear-gradient(135deg, #f59e0b, #b45309);">🛡️</span>
<span style="color: #fbbf24;">VERITAS SUITE ADMIN</span>
</div>
<p>&copy; 2026 Veritas Suite. Geschütztes Administrator-Portal auf VPS kanjiv.at.</p>
<div class="footer-links">
<a href="/">Startseite</a>
<a href="/downloads">Downloads</a>
<a href="/portal">Kundenportal</a>
</div>
</div>
</footer>
<script src="app.js"></script>
</body>
</html>
+472
View File
@@ -0,0 +1,472 @@
// Veritas Suite Web Portal App Logic
const API_BASE = "";
document.addEventListener("DOMContentLoaded", () => {
checkServerHealth();
restoreSession();
fetchDownloads();
// Check if we are on admin page
if (window.location.pathname === "/admin" || window.location.pathname.endsWith("admin.html")) {
checkAdminPageSession();
}
});
// Check Server Connection
async function checkServerHealth() {
const indicator = document.querySelector(".status-indicator");
const statusText = document.getElementById("status-text");
try {
const response = await fetch(`${API_BASE}/health`);
if (response.ok) {
if (indicator) indicator.classList.add("online");
if (statusText) statusText.textContent = "VPS Server: Online (200 OK)";
} else {
if (statusText) statusText.textContent = "VPS Server: Fehler";
}
} catch (e) {
if (statusText) statusText.textContent = "VPS Server: Offline";
}
}
// Filter Sparten Tools
function filterSparten(category) {
document.querySelectorAll(".filter-btn").forEach(btn => btn.classList.remove("active"));
const activeBtn = Array.from(document.querySelectorAll(".filter-btn")).find(btn =>
btn.getAttribute("onclick") && btn.getAttribute("onclick").includes(`'${category}'`)
);
if (activeBtn) activeBtn.classList.add("active");
const cards = document.querySelectorAll(".tool-card");
cards.forEach(card => {
if (category === "all" || card.getAttribute("data-sparte") === category) {
card.style.display = "flex";
} else {
card.style.display = "none";
}
});
}
// Restore Session from LocalStorage
function restoreSession() {
const sessionStr = localStorage.getItem("veritas_session");
if (sessionStr) {
try {
const session = JSON.parse(sessionStr);
if (session && session.user_id) {
showLoggedInState(session);
} else {
showLoggedOutState();
}
} catch (e) {
localStorage.removeItem("veritas_session");
showLoggedOutState();
}
} else {
showLoggedOutState();
}
}
function showLoggedInState(session) {
const loggedOutDiv = document.getElementById("dash-logged-out");
const loggedInDiv = document.getElementById("dash-logged-in");
const userEmailSpan = document.getElementById("dash-user-email");
const userStatusBar = document.getElementById("user-status-bar");
const authForms = document.getElementById("auth-forms-container");
const navAdminLink = document.getElementById("nav-admin-link");
const btnAdminPanel = document.getElementById("btn-admin-panel");
if (loggedOutDiv) loggedOutDiv.style.display = "none";
if (loggedInDiv) loggedInDiv.style.display = "block";
if (userEmailSpan) userEmailSpan.textContent = session.email;
if (userStatusBar) userStatusBar.style.display = "inline-flex";
if (authForms) authForms.style.display = "none";
// Show Admin Buttons ONLY if logged-in user is an Admin
if (session.is_admin) {
if (navAdminLink) navAdminLink.style.display = "inline-flex";
if (btnAdminPanel) btnAdminPanel.style.display = "inline-block";
} else {
if (navAdminLink) navAdminLink.style.display = "none";
if (btnAdminPanel) btnAdminPanel.style.display = "none";
}
refreshDashboard();
}
function showLoggedOutState() {
const loggedOutDiv = document.getElementById("dash-logged-out");
const loggedInDiv = document.getElementById("dash-logged-in");
const userStatusBar = document.getElementById("user-status-bar");
const authForms = document.getElementById("auth-forms-container");
const navAdminLink = document.getElementById("nav-admin-link");
const btnAdminPanel = document.getElementById("btn-admin-panel");
if (loggedOutDiv) loggedOutDiv.style.display = "block";
if (loggedInDiv) loggedInDiv.style.display = "none";
if (userStatusBar) userStatusBar.style.display = "none";
if (authForms) authForms.style.display = "grid";
if (navAdminLink) navAdminLink.style.display = "none";
if (btnAdminPanel) btnAdminPanel.style.display = "none";
}
function logout() {
localStorage.removeItem("veritas_session");
showLoggedOutState();
showAlert("Erfolgreich abgemeldet.", "success");
if (window.location.pathname === "/admin" || window.location.pathname.endsWith("admin.html")) {
window.location.href = "/portal";
}
}
// Handle Customer Registration
async function handleRegister(event) {
event.preventDefault();
const email = document.getElementById("reg-email").value;
const password = document.getElementById("reg-password").value;
try {
const response = await fetch(`${API_BASE}/api/v1/auth/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password })
});
const data = await response.json();
if (data.success) {
showAlert(data.message, "success");
const session = { user_id: data.user_id, email: data.email, token: data.auth_token, is_admin: data.is_admin };
localStorage.setItem("veritas_session", JSON.stringify(session));
showLoggedInState(session);
} else {
showAlert(data.message, "error");
}
} catch (e) {
showAlert("Netzwerkfehler bei der Registrierung.", "error");
}
}
// Handle Customer & Admin Login in Portal
async function handleLogin(event) {
event.preventDefault();
const email = document.getElementById("login-email").value;
const password = document.getElementById("login-password").value;
try {
const response = await fetch(`${API_BASE}/api/v1/auth/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password })
});
const data = await response.json();
if (data.success) {
showAlert(data.message, "success");
const session = { user_id: data.user_id, email: data.email, token: data.auth_token, is_admin: data.is_admin };
localStorage.setItem("veritas_session", JSON.stringify(session));
showLoggedInState(session);
if (data.is_admin) {
showAlert("👑 Willkommen Admin! Du hast Zugriff auf die Admin-Zentrale.", "success");
}
} else {
showAlert(data.message, "error");
}
} catch (e) {
showAlert("Netzwerkfehler beim Anmelden.", "error");
}
}
// Check Admin Page Session (Requires Admin Login in Customer Portal)
function checkAdminPageSession() {
const sessionStr = localStorage.getItem("veritas_session");
const loggedOutState = document.getElementById("admin-logged-out-state");
const loggedInState = document.getElementById("admin-logged-in-state");
const userEmailDisplay = document.getElementById("admin-user-email-display");
if (sessionStr) {
try {
const session = JSON.parse(sessionStr);
if (session && session.is_admin) {
if (loggedOutState) loggedOutState.style.display = "none";
if (loggedInState) loggedInState.style.display = "block";
if (userEmailDisplay) userEmailDisplay.textContent = session.email;
fetchAdminStats();
return;
}
} catch (e) {}
}
if (loggedOutState) loggedOutState.style.display = "block";
if (loggedInState) loggedInState.style.display = "none";
}
function logoutAdmin() {
logout();
}
// Refresh Customer Dashboard Data
async function refreshDashboard() {
const sessionStr = localStorage.getItem("veritas_session");
if (!sessionStr) return;
const session = JSON.parse(sessionStr);
const container = document.getElementById("license-list");
if (!container) return;
container.innerHTML = "<p style='color:#94a3b8;'>Lizenzen werden geladen...</p>";
try {
const response = await fetch(`${API_BASE}/api/v1/account/dashboard?user_id=${session.user_id}`);
const data = await response.json();
if (data.success && data.licenses && data.licenses.length > 0) {
container.innerHTML = "";
data.licenses.forEach(lic => {
const percent = Math.min(100, (lic.activated_devices / lic.max_devices) * 100);
const cardHtml = `
<div class="lic-card glass-panel" style="background: rgba(24, 34, 53, 0.7); padding: 1.25rem; border-radius: 12px; border: 1px solid rgba(255,255,255,0.08); margin-bottom: 1rem;">
<div class="lic-top" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 0.75rem;">
<span class="lic-plan" style="font-weight: 700; color: #fff; font-size: 1.1rem;">🛡️ ${lic.plan_name}</span>
<span class="status-badge ${lic.active ? 'badge-active' : 'badge-planned'}" style="padding: 4px 10px; border-radius: 6px; font-size: 0.8rem; font-weight: 600;">${lic.active ? '🟢 AKTIV' : '🔴 GESPERRT'}</span>
</div>
<div class="lic-key-row" style="display: flex; align-items: center; justify-content: space-between; background: rgba(3, 7, 18, 0.6); padding: 10px 14px; border-radius: 8px; margin-bottom: 0.75rem;">
<code style="font-family: monospace; color: #38bdf8; font-size: 1.05rem;">${lic.license_key}</code>
<button class="btn btn-glass btn-small" onclick="copyKey('${lic.license_key}')">📋 Kopieren</button>
</div>
<div>
<div style="display: flex; justify-content: space-between; margin-bottom: 4px;">
<small style="color: #94a3b8;">Aktivierte Geräte</small>
<small style="color: #6366f1; font-weight: 600;">${lic.activated_devices} / ${lic.max_devices} Geräte</small>
</div>
<div class="progress-bg" style="background: rgba(255,255,255,0.1); height: 8px; border-radius: 4px; overflow: hidden;">
<div class="progress-fill" style="width: ${percent}%; background: linear-gradient(90deg, #6366f1, #34d399); height: 100%;"></div>
</div>
</div>
</div>
`;
container.innerHTML += cardHtml;
const simKeyInput = document.getElementById("sim-key");
if (simKeyInput) simKeyInput.value = lic.license_key;
});
} else {
container.innerHTML = "<p style='color:#94a3b8;'>Keine aktiven Lizenzen für dieses Konto gefunden.</p>";
}
} catch (e) {
container.innerHTML = "<p class='error' style='color:#f87171;'>Fehler beim Laden des Dashboards.</p>";
}
}
// Fetch Software Downloads
async function fetchDownloads() {
const container = document.getElementById("downloads-container");
if (!container) return;
try {
const response = await fetch(`${API_BASE}/api/v1/downloads/list`);
const items = await response.json();
container.innerHTML = "";
items.forEach(item => {
const card = `
<div class="tool-card glass-panel">
<div class="card-top">
<span class="sparte-tag tag-cura">${item.category}</span>
<span class="status-badge ${item.is_available ? 'badge-active' : 'badge-planned'}">${item.version}</span>
</div>
<h3>${item.name}</h3>
<p class="tool-desc">${item.description}</p>
<div class="card-footer" style="justify-content: space-between; align-items: center;">
<span class="tech-tag">Größe: ${item.file_size}</span>
${item.is_available ?
`<a class="btn btn-emerald btn-small" href="${item.download_url}" download>📥 Download .exe</a>` :
`<button class="btn btn-glass btn-small" disabled style="opacity:0.5">Demnächst</button>`}
</div>
</div>
`;
container.innerHTML += card;
});
} catch (e) {
container.innerHTML = "<p>Fehler beim Laden der Download-Liste.</p>";
}
}
// Fetch Admin Stats (Sends Authorization Bearer token)
async function fetchAdminStats() {
const sessionStr = localStorage.getItem("veritas_session");
if (!sessionStr) return;
const session = JSON.parse(sessionStr);
try {
const response = await fetch(`${API_BASE}/api/v1/admin/stats`, {
headers: {
"Authorization": `Bearer ${session.token}`
}
});
const data = await response.json();
if (data.success) {
const usersEl = document.getElementById("admin-stat-users");
const licsEl = document.getElementById("admin-stat-licenses");
const actsEl = document.getElementById("admin-stat-activations");
if (usersEl) usersEl.textContent = data.total_users;
if (licsEl) licsEl.textContent = data.total_licenses;
if (actsEl) actsEl.textContent = data.total_activations;
const tbody = document.getElementById("admin-licenses-table-body");
if (tbody) {
tbody.innerHTML = "";
data.licenses.forEach(lic => {
const tr = `
<tr style="border-bottom: 1px solid rgba(255,255,255,0.05);">
<td style="padding: 12px;">#${lic.id}</td>
<td style="padding: 12px;"><code style="color:#38bdf8">${lic.license_key}</code></td>
<td style="padding: 12px;">${lic.user_email || 'Gast / System'}</td>
<td style="padding: 12px;">${lic.plan_name}</td>
<td style="padding: 12px;">${lic.activated_devices} / ${lic.max_devices}</td>
<td style="padding: 12px;"><span class="status-badge ${lic.active ? 'badge-active' : 'badge-planned'}">${lic.active ? '🟢 AKTIV' : '🔴 GESPERRT'}</span></td>
<td style="padding: 12px;">
<button class="btn btn-small ${lic.active ? 'btn-outline-danger' : 'btn-emerald'}" onclick="toggleLicense('${lic.license_key}', ${!lic.active})">
${lic.active ? '🚫 Sperren' : '✅ Freischalten'}
</button>
</td>
</tr>
`;
tbody.innerHTML += tr;
});
}
} else {
showAdminAlert("Zugriff verweigert: " + (data.message || "Admin Privilegien erforderlich"), "error");
}
} catch (e) {
console.error("Admin stats fetch failed", e);
}
}
// Admin Create License Handler (Sends Authorization Bearer token)
async function handleAdminCreateLicense(event) {
event.preventDefault();
const sessionStr = localStorage.getItem("veritas_session");
if (!sessionStr) return;
const session = JSON.parse(sessionStr);
const userId = parseInt(document.getElementById("admin-create-uid").value);
const planName = document.getElementById("admin-create-plan").value;
const maxDevices = parseInt(document.getElementById("admin-create-max").value);
try {
const response = await fetch(`${API_BASE}/api/v1/admin/create-license`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${session.token}`
},
body: JSON.stringify({ user_id: userId, plan_name: planName, max_devices: maxDevices })
});
const data = await response.json();
if (data.success) {
showAdminAlert("Lizenz erfolgreich erstellt: " + data.message, "success");
fetchAdminStats();
} else {
showAdminAlert("Fehler: " + data.message, "error");
}
} catch (e) {
showAdminAlert("Netzwerkfehler beim Erstellen der Lizenz.", "error");
}
}
// Admin Toggle License Active/Deactive (Sends Authorization Bearer token)
async function toggleLicense(licenseKey, newActiveState) {
const sessionStr = localStorage.getItem("veritas_session");
if (!sessionStr) return;
const session = JSON.parse(sessionStr);
try {
const response = await fetch(`${API_BASE}/api/v1/admin/toggle-license`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${session.token}`
},
body: JSON.stringify({ license_key: licenseKey, active: newActiveState })
});
const data = await response.json();
if (data.success) {
showAdminAlert("Lizenz-Status aktualisiert.", "success");
fetchAdminStats();
} else {
showAdminAlert("Fehler: " + data.message, "error");
}
} catch (e) {
showAdminAlert("Netzwerkfehler beim Ändern des Lizenzstatus.", "error");
}
}
// Handle Activation Test
async function handleTestActivation(event) {
event.preventDefault();
const licenseKey = document.getElementById("sim-key").value;
const hardwareId = document.getElementById("sim-hwid").value;
const appName = document.getElementById("sim-app").value;
const outputBox = document.getElementById("sim-output-box");
const jsonText = document.getElementById("sim-json-text");
try {
const response = await fetch(`${API_BASE}/api/v1/activate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
license_key: licenseKey,
hardware_id: hardwareId,
app_name: appName
})
});
const data = await response.json();
outputBox.style.display = "block";
jsonText.textContent = JSON.stringify(data, null, 2);
refreshDashboard();
} catch (e) {
outputBox.style.display = "block";
jsonText.textContent = "Fehler bei der Kommunikation mit dem VPS Server.";
}
}
// Helper: Show Alert for Customer Portal
function showAlert(msg, type) {
const box = document.getElementById("auth-alert");
if (!box) return;
box.textContent = msg;
box.className = `alert-box ${type}`;
box.style.display = "block";
setTimeout(() => {
box.style.display = "none";
}, 5000);
}
// Helper: Show Alert for Admin Portal
function showAdminAlert(msg, type) {
const box = document.getElementById("admin-alert");
if (!box) return;
box.textContent = msg;
box.className = `alert-box ${type}`;
box.style.display = "block";
setTimeout(() => {
box.style.display = "none";
}, 5000);
}
// Helper: Copy Key
function copyKey(text) {
navigator.clipboard.writeText(text).then(() => {
alert("Lizenzschlüssel in die Zwischenablage kopiert!");
});
}
+122
View File
@@ -0,0 +1,122 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Software Download-Center | Veritas Suite</title>
<meta name="description" content="Offizielles Download-Center für die Veritas Suite Executables: Veritas SpoolerReset, Veritas RansomGuard und Module.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Background Orbs -->
<div class="glow-orb orb-purple"></div>
<div class="glow-orb orb-cyan"></div>
<div class="glow-orb orb-emerald"></div>
<!-- Header Navigation -->
<header class="navbar">
<div class="nav-container">
<a href="/" class="logo">
<div class="logo-shield">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
</div>
<div class="logo-brand">
<span class="brand-name">VERITAS</span>
<span class="brand-tag">SUITE v1.0</span>
</div>
</a>
<nav class="nav-links">
<a href="/" class="nav-item">🌐 Startseite</a>
<a href="/#sparten" class="nav-item">📦 Software-Sparten</a>
<a href="/downloads" class="nav-item active">📥 Software-Downloads</a>
<a href="/portal" class="nav-item">👤 Kundenportal</a>
<a href="https://mail.kanjiv.at/SOGo/index/" target="_blank" class="nav-item" style="color: #38bdf8;">📧 E-Mail Dienst</a>
<a href="https://git.kanjiv.at/" target="_blank" class="nav-item" style="color: #a855f7;">🐙 Git Server</a>
<a href="/admin" class="nav-item" style="color: #fbbf24;">👑 Admin-Zentrale</a>
</nav>
<div class="server-status-pill" id="status-pill">
<span class="status-indicator"></span>
<span id="status-text">Server: Verbinde...</span>
</div>
</div>
</header>
<!-- Main Content Section -->
<section class="section" style="padding-top: 7rem;">
<div class="container">
<div class="section-title-box">
<span class="sub-title">OFFIZIELLES DOWNLOAD-CENTER</span>
<h2>Veritas Suite Software-Pakete</h2>
<p>Lade fertige, frei stehende Windows Executables (.exe) direkt herunter. Alle Tools sind für 64-Bit Windows optimiert.</p>
</div>
<!-- Active Downloads Grid -->
<div id="downloads-container" class="tools-grid">
<div class="tool-card glass-panel">
<div class="card-top">
<span class="sparte-tag tag-cura">CURA FIXER</span>
<span class="status-badge badge-active">✅ Bereit zum Download</span>
</div>
<h3>Veritas SpoolerReset</h3>
<p class="tool-desc">Autonomous Windows Druckspooler & Warteschlangen-Reparatur mit GPU GUI, UAC Elevation & VPS Lizenzprüfung.</p>
<div class="card-footer">
<span class="tech-tag">v1.2.0 Pro</span>
<span class="tech-tag">14.2 MB</span>
<a href="/spooler-reset.exe" class="btn btn-emerald" style="margin-left: auto;">📥 Download (.exe)</a>
</div>
</div>
<div class="tool-card glass-panel">
<div class="card-top">
<span class="sparte-tag tag-aegis">AEGIS SECURITY</span>
<span class="status-badge badge-active">✅ Bereit zum Download</span>
</div>
<h3>Veritas RansomGuard</h3>
<p class="tool-desc">Echtzeit Shannon-Entropie Scanner & Kanarienvogel-Köder gegen Krypto-Trojaner mit Autostart & VPS Aktivierung.</p>
<div class="card-footer">
<span class="tech-tag">v1.0.0 Pro</span>
<span class="tech-tag">14.4 MB</span>
<a href="/ransomguard.exe" class="btn btn-emerald" style="margin-left: auto;">📥 Download (.exe)</a>
</div>
</div>
</div>
<!-- System Requirements Card -->
<div class="portal-card glass-panel" style="margin-top: 3rem; padding: 1.5rem; border-radius: 14px;">
<h3 style="margin-bottom: 1rem; color: #fff;">🖥️ Systemanforderungen & Installation</h3>
<ul style="color: #cbd5e1; line-height: 1.8; margin-left: 1.2rem;">
<li><strong>Betriebssystem:</strong> Windows 10, Windows 11 oder Windows Server 2019/2022 (64-Bit).</li>
<li><strong>Voraussetzungen:</strong> Keine externen Runtime-DLLs erforderlich (100% Statisch kompiliertes MinGW/GCC Rust Binary).</li>
<li><strong>Rechte:</strong> Administrator-Rechte empfohlen (Erfordert UAC Bestätigung beim Start).</li>
<li><strong>Lizenzierung:</strong> Nach dem Start einfach mit deinen Kundenkonto-Daten von <a href="/portal" style="color: #38bdf8;">https://kanjiv.at/portal</a> anmelden.</li>
</ul>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container footer-content">
<div class="footer-brand">
<span class="logo-shield">🛡️</span>
<span>VERITAS SUITE v1.0</span>
</div>
<p>&copy; 2026 Veritas Suite. Betrieben auf VPS kanjiv.at.</p>
<div class="footer-links">
<a href="/">Startseite</a>
<a href="/downloads">Downloads</a>
<a href="/portal">Kundenportal</a>
<a href="/admin">Admin-Zentrale</a>
</div>
</div>
</footer>
<script src="app.js"></script>
</body>
</html>
+178
View File
@@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Veritas Suite v1.0 | Cyber-Security, Forensik & Windows Optimization</title>
<meta name="description" content="Veritas Suite v1.0 - Modulare High-Performance Rust Software-Suite für Forensik, B2B-Security und automatisierte Systemreparatur.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Background Orbs -->
<div class="glow-orb orb-purple"></div>
<div class="glow-orb orb-cyan"></div>
<div class="glow-orb orb-emerald"></div>
<!-- Header Navigation -->
<header class="navbar">
<div class="nav-container">
<a href="/" class="logo">
<div class="logo-shield">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
</div>
<div class="logo-brand">
<span class="brand-name">VERITAS</span>
<span class="brand-tag">SUITE v1.0</span>
</div>
</a>
<nav class="nav-links">
<a href="/" class="nav-item active">🌐 Startseite</a>
<a href="/#sparten" class="nav-item">📦 Software-Sparten</a>
<a href="/downloads" class="nav-item">📥 Software-Downloads</a>
<a href="/portal" class="nav-item">👤 Kundenportal</a>
<a href="https://mail.kanjiv.at/SOGo/index/" target="_blank" class="nav-item" style="color: #38bdf8;">📧 E-Mail Dienst</a>
<a href="https://git.kanjiv.at/" target="_blank" class="nav-item" style="color: #a855f7;">🐙 Git Server</a>
<a href="/admin" class="nav-item" style="color: #fbbf24;">👑 Admin-Zentrale</a>
</nav>
<div class="server-status-pill" id="status-pill">
<span class="status-indicator"></span>
<span id="status-text">Server: Verbinde...</span>
</div>
</div>
</header>
<!-- Hero Section -->
<section class="hero" id="hero">
<div class="h <div class="hero-badge">
<span class="badge-sparkle"></span>
<span>Fertiggestellte Software & Live Cloud-Backend</span>
</div>
<h1 class="hero-heading">
Hochperformante Software für <span class="gradient-text">Security & Windows-Reparatur</span>
</h1>
<p class="hero-description">
Die Veritas Suite bietet fertige, kommerzielle High-Performance Tools mit kryptografischem Lizenz- & Cloud-Ökosystem. Entwickelt in sicherem, rasend schnellem Rust.
</p>
<div class="hero-buttons">
<a href="/downloads" class="btn btn-glow">
<span>Software Downloads</span>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="5" y1="12" x2="19" y2="12"></line><polyline points="12 5 19 12 12 19"></polyline></svg>
</a>
<a href="/portal" class="btn btn-glass">
<span>👤 Kundenportal & Lizenzen</span>
</a>
</div>
<!-- Stats Bar -->
<div class="hero-stats glass-panel">
<div class="stat-box">
<span class="stat-val gradient-emerald">2 FERTIG</span>
<span class="stat-desc">Programme bereit</span>
</div>
<div class="stat-sep"></div>
<div class="stat-box">
<span class="stat-val">2</span>
<span class="stat-desc">Aktive Sparten</span>
</div>
<div class="stat-sep"></div>
<div class="stat-box">
<span class="stat-val gradient-cyan">100% LIVE</span>
<span class="stat-desc">Download-Status</span>
</div>
<div class="stat-sep"></div>
<div class="stat-box">
<span class="stat-val gradient-emerald">VPS ONLINE</span>
<span class="stat-desc">Cloud-Backend</span>
</div>
</div>
</div>
</section>
<!-- Software Sparten Section -->
<section class="section" id="sparten">
<div class="container">
<div class="section-title-box">
<span class="sub-title">FERTIGE SOFTWARE-PAKETE</span>
<h2>Verfügbare Programme der Veritas Suite</h2>
<p>Übersicht aller einsatzbereiten Werkzeuge zum direkten Download.</p>
</div>
<!-- Category Filter Bar -->
<div class="filter-bar">
<button class="filter-btn active" onclick="filterSparten('all')">Alle fertigen Tools (2)</button>
<button class="filter-btn" onclick="filterSparten('aegis')">🛡️ Sparte AEGIS (Security)</button>
<button class="filter-btn" onclick="filterSparten('cura')">🔧 Sparte CURA (System Fixer)</button>
</div>
<!-- Tools Grid -->
<div class="tools-grid" id="tools-grid">
<!-- FERTIGES TOOL 1 -->
<div class="tool-card glass-panel" data-sparte="aegis">
<div class="card-top">
<span class="sparte-tag tag-aegis">AEGIS SECURITY</span>
<span class="status-badge badge-active">✅ Bereit zum Download</span>
</div>
<h3>Veritas RansomGuard</h3>
<p class="tool-desc">Echtzeit Shannon-Entropie Scanner im Hintergrund. Stoppt Krypto-Trojaner vor Massenverschlüsselungen & setzt Kanarienvogel-Köder gegen Ransomware.</p>
<div class="card-footer">
<span class="tech-tag">v1.0.0 Pro</span>
<span class="tech-tag">Anti-Ransomware</span>
<a href="/downloads" class="btn btn-emerald" style="margin-left: auto;">Download (.exe)</a>
</div>
</div>
<!-- FERTIGES TOOL 2 -->
<div class="tool-card glass-panel" data-sparte="cura">
<div class="card-top">
<span class="sparte-tag tag-cura">CURA FIXER</span>
<span class="status-badge badge-active">✅ Bereit zum Download</span>
</div>
<h3>Veritas SpoolerReset</h3>
<p class="tool-desc">Autonomous Windows Druckspooler & Warteschlangen-Reparatur mit GPU GUI, automatischem UAC Elevation & VPS Lizenzprüfung.</p>
<div class="card-footer">
<span class="tech-tag">v1.2.0 Pro</span>
<span class="tech-tag">Spooler Fix</span>
<a href="/downloads" class="btn btn-emerald" style="margin-left: auto;">Download (.exe)</a>
</div>
</div>
</div>
</div>
</section>he</span>
</div>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container footer-content">
<div class="footer-brand">
<span class="logo-shield">🛡️</span>
<span>VERITAS SUITE v1.0</span>
</div>
<p>&copy; 2026 Veritas Suite. Betrieben auf VPS kanjiv.at.</p>
<div class="footer-links">
<a href="/">Startseite</a>
<a href="/downloads">Downloads</a>
<a href="/portal">Kundenportal</a>
<a href="/admin">Admin-Zentrale</a>
</div>
</div>
</footer>
<script src="app.js"></script>
</body>
</html>
+223
View File
@@ -0,0 +1,223 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kundenportal & Control Center | Veritas Suite</title>
<meta name="description" content="Alles auf einen Blick: Veritas Suite Kundenportal für Lizenzen, Downloads, Account-Einstellungen und Live-Aktivierung.">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<!-- Background Orbs -->
<div class="glow-orb orb-purple"></div>
<div class="glow-orb orb-cyan"></div>
<div class="glow-orb orb-emerald"></div>
<!-- Header Navigation -->
<header class="navbar">
<div class="nav-container">
<a href="/" class="logo">
<div class="logo-shield">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"></path></svg>
</div>
<div class="logo-brand">
<span class="brand-name">VERITAS</span>
<span class="brand-tag">SUITE v1.0</span>
</div>
</a>
<nav class="nav-links">
<a href="/" class="nav-item">🌐 Startseite</a>
<a href="/#sparten" class="nav-item">📦 Software-Sparten</a>
<a href="/downloads" class="nav-item">📥 Software-Downloads</a>
<a href="/portal" class="nav-item active portal-link">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path><circle cx="12" cy="7" r="4"></circle></svg>
Kundenportal
</a>
<a href="https://mail.kanjiv.at/SOGo/index/" target="_blank" class="nav-item" style="color: #38bdf8;">📧 E-Mail Dienst</a>
<a href="https://git.kanjiv.at/" target="_blank" class="nav-item" style="color: #a855f7;">🐙 Git Server</a>
<!-- Admin link dynamically visible ONLY for logged-in admins -->
<a href="/admin" id="nav-admin-link" class="nav-item" style="display: none; color: #fbbf24; font-weight: 700;">👑 Admin-Zentrale</a>
</nav>
<div class="server-status-pill" id="status-pill">
<span class="status-indicator"></span>
<span id="status-text">Server: Verbinde...</span>
</div>
</div>
</header>
<!-- Portal Main Single-Page Section -->
<section class="section" style="padding-top: 7rem; padding-bottom: 4rem;">
<div class="container">
<!-- Hero Title Box -->
<div class="section-title-box" style="margin-bottom: 2rem;">
<span class="sub-title">ALL-IN-ONE CONTROL CENTER</span>
<h2>Veritas Kundenportal</h2>
<p>Verwalte deine Lizenzen, deine Anmeldedaten und deine Software-Downloads auf einer zentralen Übersichtsseite.</p>
</div>
<!-- Global Alert Box -->
<div id="auth-alert" class="alert-box" style="display: none; margin-bottom: 2rem; padding: 14px 18px; border-radius: 10px; font-weight: 600;"></div>
<!-- SECTION 1: ACCOUNT & ANMELDUNG -->
<div class="portal-card glass-panel" style="padding: 2rem; border-radius: 16px; border: 1px solid rgba(255,255,255,0.1); margin-bottom: 2.5rem;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1.5rem; flex-wrap: wrap; gap: 1rem;">
<div style="display: flex; align-items: center; gap: 12px;">
<span style="font-size: 2rem;">👤</span>
<div>
<h3 style="margin: 0; color: #fff; font-size: 1.3rem;">Konto & Authentifizierung</h3>
<p style="margin: 0; color: #94a3b8; font-size: 0.85rem;">Melde dich an oder erstelle ein neues Konto mit automatischer Pro-Lizenz.</p>
</div>
</div>
<!-- Logged In Status Bar & Admin Button -->
<div id="user-status-bar" style="display: none; background: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.3); padding: 8px 16px; border-radius: 8px; align-items: center; gap: 12px; flex-wrap: wrap;">
<span style="color: #34d399; font-weight: 600; font-size: 0.9rem;" id="dash-user-email">user@domain.de</span>
<a href="/admin" id="btn-admin-panel" style="display: none; background: linear-gradient(135deg, #f59e0b, #b45309); color: #fff; font-weight: 700; padding: 6px 14px; border-radius: 6px; text-decoration: none; font-size: 0.85rem;">👑 Admin-Zentrale Öffnen</a>
<button class="btn btn-outline-danger btn-small" onclick="logout()">Abmelden</button>
</div>
</div>
<!-- Forms Grid (Shows when logged out) -->
<div id="auth-forms-container" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2rem;">
<!-- Login Box -->
<div class="auth-card glass-panel" style="padding: 1.6rem; border-radius: 12px; background: rgba(24, 34, 53, 0.6); border: 1px solid rgba(255,255,255,0.08);">
<h4 style="margin-top: 0; margin-bottom: 0.5rem; color: #fff;">🔑 Anmelden</h4>
<p style="color: #94a3b8; font-size: 0.85rem; margin-bottom: 1.2rem;">Zugang zu deinen lizenzierten Tools</p>
<form onsubmit="handleLogin(event)">
<div class="form-group" style="margin-bottom: 1.2rem;">
<label style="display: block; margin-bottom: 6px; color: #cbd5e1; font-size: 0.85rem;">E-Mail-Adresse</label>
<input type="email" id="login-email" placeholder="name@domain.de" required style="width: 100%; padding: 11px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); background: rgba(3, 7, 18, 0.8); color: #fff;">
</div>
<div class="form-group" style="margin-bottom: 1.5rem;">
<label style="display: block; margin-bottom: 6px; color: #cbd5e1; font-size: 0.85rem;">Passwort</label>
<input type="password" id="login-password" placeholder="••••••••" required style="width: 100%; padding: 11px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); background: rgba(3, 7, 18, 0.8); color: #fff;">
</div>
<button type="submit" class="btn btn-glow btn-block" style="width: 100%;">Anmelden</button>
</form>
</div>
<!-- Register Box -->
<div class="auth-card glass-panel" style="padding: 1.6rem; border-radius: 12px; background: rgba(16, 185, 129, 0.06); border: 1px solid rgba(16, 185, 129, 0.25);">
<h4 style="margin-top: 0; margin-bottom: 0.5rem; color: #fff;">⚡ Registrieren</h4>
<p style="color: #a7f3d0; font-size: 0.85rem; margin-bottom: 1.2rem;">Erhalte sofort eine <strong>Veritas Pro Lizenz</strong> (5 Geräte)</p>
<form onsubmit="handleRegister(event)">
<div class="form-group" style="margin-bottom: 1.2rem;">
<label style="display: block; margin-bottom: 6px; color: #cbd5e1; font-size: 0.85rem;">E-Mail-Adresse</label>
<input type="email" id="reg-email" placeholder="neuer.nutzer@domain.de" required style="width: 100%; padding: 11px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); background: rgba(3, 7, 18, 0.8); color: #fff;">
</div>
<div class="form-group" style="margin-bottom: 1.5rem;">
<label style="display: block; margin-bottom: 6px; color: #cbd5e1; font-size: 0.85rem;">Passwort festlegen</label>
<input type="password" id="reg-password" placeholder="••••••••" required style="width: 100%; padding: 11px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); background: rgba(3, 7, 18, 0.8); color: #fff;">
</div>
<button type="submit" class="btn btn-emerald btn-block" style="width: 100%;">Konto Erstellen & Pro-Lizenz Erhalten</button>
</form>
</div>
</div>
</div>
<!-- SECTION 2: LIZENZ-DASHBOARD -->
<div class="portal-card glass-panel" style="padding: 2rem; border-radius: 16px; border: 1px solid rgba(255,255,255,0.1); margin-bottom: 2.5rem;">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 1.5rem;">
<span style="font-size: 2rem;">🔑</span>
<div>
<h3 style="margin: 0; color: #fff; font-size: 1.3rem;">Deine Software-Lizenzen</h3>
<p style="margin: 0; color: #94a3b8; font-size: 0.85rem;">Aktive Lizenzen, Schlüssel und verknüpfte Geräte-Slots</p>
</div>
</div>
<div id="dash-logged-out" class="logged-out-state" style="text-align: center; padding: 2rem 1rem; background: rgba(3, 7, 18, 0.4); border-radius: 12px;">
<div style="font-size: 2.5rem; margin-bottom: 0.5rem;">🔒</div>
<p style="color: #cbd5e1; margin-bottom: 0;">Bitte melde dich oben an, um deine Lizenzen einzusehen.</p>
</div>
<div id="dash-logged-in" style="display: none;">
<div id="license-list" class="license-grid"></div>
</div>
</div>
<!-- SECTION 3: SCHNELL-DOWNLOAD CENTER -->
<div class="portal-card glass-panel" style="padding: 2rem; border-radius: 16px; border: 1px solid rgba(255,255,255,0.1); margin-bottom: 2.5rem;">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 1.5rem;">
<span style="font-size: 2rem;">📥</span>
<div>
<h3 style="margin: 0; color: #fff; font-size: 1.3rem;">Software Download-Center</h3>
<p style="margin: 0; color: #94a3b8; font-size: 0.85rem;">Lade die fertigen Windows Executables (.exe) direkt herunter</p>
</div>
</div>
<div id="downloads-container" class="tools-grid"></div>
</div>
<!-- SECTION 4: INTERAKTIVER AKTIVIERUNGS-TESTER -->
<div class="portal-card glass-panel" style="padding: 2rem; border-radius: 16px; border: 1px solid rgba(255,255,255,0.1);">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 1.5rem;">
<span style="font-size: 2rem;"></span>
<div>
<h3 style="margin: 0; color: #fff; font-size: 1.3rem;">Online Aktivierungs-Tester</h3>
<p style="margin: 0; color: #94a3b8; font-size: 0.85rem;">Testet den Handshake zwischen den Windows-Tools und dem VPS Server</p>
</div>
</div>
<form onsubmit="handleTestActivation(event)" class="sim-form">
<div class="form-row" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1.2rem; margin-bottom: 1.2rem;">
<div class="form-group">
<label style="display: block; margin-bottom: 6px; color: #cbd5e1; font-size: 0.85rem;">Lizenzschlüssel</label>
<input type="text" id="sim-key" placeholder="VERITAS-PRO-XXXXXX" required style="width: 100%; padding: 11px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); background: rgba(3, 7, 18, 0.8); color: #38bdf8; font-family: monospace;">
</div>
<div class="form-group">
<label style="display: block; margin-bottom: 6px; color: #cbd5e1; font-size: 0.85rem;">Simulierte Hardware-ID (HWID)</label>
<input type="text" id="sim-hwid" value="WIN-CPU-CORE-i9-99X" required style="width: 100%; padding: 11px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); background: rgba(3, 7, 18, 0.8); color: #fff; font-family: monospace;">
</div>
</div>
<div class="form-group" style="margin-bottom: 1.5rem;">
<label style="display: block; margin-bottom: 6px; color: #cbd5e1; font-size: 0.85rem;">Ziel-Applikation</label>
<select id="sim-app" style="width: 100%; padding: 11px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.15); background: rgba(3, 7, 18, 0.8); color: #fff;">
<option value="Veritas SpoolerReset Commercial GUI v1.2.0">Veritas SpoolerReset (v1.2.0)</option>
<option value="Veritas RansomGuard Commercial GUI v1.0.0">Veritas RansomGuard (v1.0.0)</option>
<option value="Veritas Recovery Engine (VRE)">Veritas Recovery Engine (VRE)</option>
<option value="Veritas Imager (VIM)">Veritas Imager (VIM)</option>
</select>
</div>
<button type="submit" class="btn btn-glow">Online Aktivierungs-Call Ausführen</button>
</form>
<div id="sim-output-box" class="sim-output glass-panel" style="display: none; margin-top: 1.5rem; padding: 1.2rem; border-radius: 10px; background: rgba(3, 7, 18, 0.9); border: 1px solid rgba(99, 102, 241, 0.4);">
<div class="output-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="color: #94a3b8; font-size: 0.85rem; font-family: monospace;">SERVER RESPONSE JSON</span>
<span class="badge-status-code" style="background: rgba(16, 185, 129, 0.2); color: #34d399; padding: 4px 8px; border-radius: 4px; font-size: 0.8rem;">HTTP 200 OK</span>
</div>
<pre id="sim-json-text" style="color: #6ee7b7; font-family: monospace; font-size: 0.95rem; margin: 0; white-space: pre-wrap;"></pre>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="container footer-content">
<div class="footer-brand">
<span class="logo-shield">🛡️</span>
<span>VERITAS SUITE v1.0</span>
</div>
<p>&copy; 2026 Veritas Suite. Betrieben auf VPS kanjiv.at.</p>
<div class="footer-links">
<a href="/">Startseite</a>
<a href="/downloads">Downloads</a>
<a href="/portal">Kundenportal</a>
</div>
</div>
</footer>
<script src="app.js"></script>
</body>
</html>
+598
View File
@@ -0,0 +1,598 @@
/* ==========================================================================
VERITAS SUITE v1.0 - PREMIUM DESIGN SYSTEM (Obsidian & Cyber Glass)
========================================================================== */
:root {
--bg-main: #030712;
--bg-surface: rgba(15, 23, 42, 0.75);
--bg-panel: rgba(30, 41, 59, 0.55);
--border-glass: rgba(255, 255, 255, 0.12);
--border-light: rgba(255, 255, 255, 0.22);
--indigo: #6366f1;
--indigo-glow: rgba(99, 102, 241, 0.45);
--cyan: #38bdf8;
--cyan-glow: rgba(56, 189, 248, 0.45);
--emerald: #10b981;
--emerald-glow: rgba(16, 185, 129, 0.45);
--amber: #f59e0b;
--amber-glow: rgba(245, 158, 11, 0.45);
--rose: #f43f5e;
--text-heading: #ffffff;
--text-body: #cbd5e1;
--text-muted: #94a3b8;
--text-dim: #64748b;
--font-heading: 'Outfit', sans-serif;
--font-body: 'Inter', sans-serif;
--radius-sm: 8px;
--radius-md: 16px;
--radius-lg: 24px;
--transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: var(--bg-main);
color: var(--text-body);
font-family: var(--font-body);
line-height: 1.6;
overflow-x: hidden;
min-height: 100vh;
}
h1, h2, h3, h4, h5 {
font-family: var(--font-heading);
color: var(--text-heading);
}
/* Background Glow Orbs */
.glow-orb {
position: fixed;
border-radius: 50%;
filter: blur(150px);
z-index: -1;
pointer-events: none;
opacity: 0.6;
}
.orb-purple {
width: 600px;
height: 600px;
background: radial-gradient(circle, var(--indigo) 0%, transparent 70%);
top: -200px;
left: -150px;
}
.orb-cyan {
width: 500px;
height: 500px;
background: radial-gradient(circle, var(--cyan) 0%, transparent 70%);
top: 30%;
right: -200px;
}
.orb-emerald {
width: 550px;
height: 550px;
background: radial-gradient(circle, var(--emerald) 0%, transparent 70%);
bottom: -200px;
left: 20%;
}
/* Glass Panel Component */
.glass-panel {
background: var(--bg-surface);
backdrop-filter: blur(24px);
-webkit-backdrop-filter: blur(24px);
border: 1px solid var(--border-glass);
border-radius: var(--radius-md);
box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.6);
transition: var(--transition);
}
.glass-panel:hover {
border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7), 0 0 20px rgba(99, 102, 241, 0.15);
}
/* Container */
.container {
max-width: 1240px;
margin: 0 auto;
padding: 0 1.5rem;
}
/* Navbar */
.navbar {
position: sticky;
top: 0;
z-index: 100;
background: rgba(3, 7, 18, 0.88);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--border-glass);
}
.nav-container {
max-width: 1240px;
margin: 0 auto;
padding: 1rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
display: flex;
align-items: center;
gap: 0.75rem;
text-decoration: none;
}
.logo-shield {
width: 42px;
height: 42px;
background: linear-gradient(135deg, var(--indigo) 0%, #38bdf8 100%);
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
color: white;
box-shadow: 0 0 25px var(--indigo-glow);
}
.logo-brand {
display: flex;
flex-direction: column;
}
.brand-name {
font-family: var(--font-heading);
font-size: 1.3rem;
font-weight: 800;
letter-spacing: 0.05em;
color: white;
}
.brand-tag {
font-size: 0.7rem;
color: var(--cyan);
font-weight: 700;
letter-spacing: 0.1em;
}
.nav-links {
display: flex;
gap: 1.5rem;
align-items: center;
}
.nav-item {
color: var(--text-muted);
text-decoration: none;
font-weight: 500;
font-size: 0.95rem;
transition: var(--transition);
}
.nav-item:hover, .nav-item.active {
color: white;
}
.portal-link {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.55rem 1.2rem;
background: rgba(99, 102, 241, 0.18);
border: 1px solid rgba(99, 102, 241, 0.4);
border-radius: var(--radius-sm);
color: #a5b4fc;
}
.portal-link:hover {
background: var(--indigo);
color: white;
box-shadow: 0 0 25px var(--indigo-glow);
}
.server-status-pill {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.4rem 0.9rem;
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--border-glass);
border-radius: 50px;
font-size: 0.8rem;
color: var(--text-muted);
}
.status-indicator {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--amber);
box-shadow: 0 0 10px var(--amber-glow);
}
.status-indicator.online {
background: var(--emerald);
box-shadow: 0 0 14px var(--emerald-glow);
}
/* Hero Section */
.hero {
padding: 6rem 1.5rem 4rem 1.5rem;
text-align: center;
}
.hero-container {
max-width: 900px;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
}
.hero-badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.45rem 1.3rem;
background: rgba(56, 189, 248, 0.12);
border: 1px solid rgba(56, 189, 248, 0.35);
border-radius: 50px;
color: #38bdf8;
font-size: 0.85rem;
font-weight: 600;
margin-bottom: 1.5rem;
}
.hero-heading {
font-size: 3.6rem;
font-weight: 800;
line-height: 1.15;
margin-bottom: 1.5rem;
}
.gradient-text {
background: linear-gradient(135deg, #a5b4fc 0%, #38bdf8 50%, #34d399 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.hero-description {
font-size: 1.2rem;
color: var(--text-muted);
max-width: 720px;
margin-bottom: 2.5rem;
}
.hero-buttons {
display: flex;
gap: 1.2rem;
margin-bottom: 3.5rem;
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.6rem;
padding: 0.85rem 1.8rem;
border-radius: var(--radius-sm);
font-weight: 600;
font-size: 1rem;
text-decoration: none;
cursor: pointer;
border: none;
transition: var(--transition);
}
.btn-glow {
background: linear-gradient(135deg, var(--indigo) 0%, #4f46e5 100%);
color: white;
box-shadow: 0 0 25px var(--indigo-glow);
}
.btn-glow:hover {
transform: translateY(-2px);
box-shadow: 0 0 35px var(--indigo-glow);
}
.btn-glass {
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border-glass);
color: var(--text-heading);
}
.btn-glass:hover {
background: rgba(255, 255, 255, 0.12);
}
.btn-emerald {
background: linear-gradient(135deg, var(--emerald) 0%, #059669 100%);
color: white;
box-shadow: 0 0 25px var(--emerald-glow);
}
.btn-emerald:hover {
transform: translateY(-2px);
box-shadow: 0 0 35px var(--emerald-glow);
}
.btn-block {
width: 100%;
}
.btn-outline-danger {
background: rgba(244, 63, 94, 0.12);
border: 1px solid rgba(244, 63, 94, 0.4);
color: #fda4af;
padding: 0.45rem 1.1rem;
font-size: 0.85rem;
border-radius: var(--radius-sm);
cursor: pointer;
}
.btn-outline-danger:hover {
background: var(--rose);
color: white;
}
.btn-small {
padding: 0.4rem 0.9rem;
font-size: 0.85rem;
}
/* Hero Stats Bar */
.hero-stats {
width: 100%;
padding: 1.8rem 2.5rem;
display: flex;
justify-content: space-around;
align-items: center;
}
.stat-box {
display: flex;
flex-direction: column;
align-items: center;
}
.stat-val {
font-family: var(--font-heading);
font-size: 2.2rem;
font-weight: 800;
color: white;
}
.gradient-cyan {
background: linear-gradient(135deg, var(--cyan), #38bdf8);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.gradient-emerald {
background: linear-gradient(135deg, var(--emerald), #34d399);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-desc {
font-size: 0.85rem;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.stat-sep {
width: 1px;
height: 40px;
background: var(--border-glass);
}
/* Section Title Box */
.section {
padding: 5rem 0;
}
.section-title-box {
text-align: center;
margin-bottom: 3rem;
}
.sub-title {
font-size: 0.8rem;
font-weight: 700;
letter-spacing: 0.15em;
color: var(--cyan);
margin-bottom: 0.5rem;
display: block;
}
.section-title-box h2 {
font-size: 2.5rem;
font-weight: 800;
margin-bottom: 0.75rem;
}
.section-title-box p {
color: var(--text-muted);
font-size: 1.1rem;
}
/* Sparten Filter Bar */
.filter-bar {
display: flex;
justify-content: center;
gap: 0.8rem;
margin-bottom: 3rem;
flex-wrap: wrap;
}
.filter-btn {
padding: 0.65rem 1.5rem;
background: rgba(255, 255, 255, 0.04);
border: 1px solid var(--border-glass);
border-radius: 50px;
color: var(--text-muted);
font-weight: 600;
font-size: 0.9rem;
cursor: pointer;
transition: var(--transition);
}
.filter-btn:hover {
color: white;
background: rgba(255, 255, 255, 0.08);
}
.filter-btn.active {
background: var(--indigo);
color: white;
border-color: var(--indigo);
box-shadow: 0 0 25px var(--indigo-glow);
}
/* Tools Grid */
.tools-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 1.8rem;
}
.tool-card {
padding: 1.8rem;
display: flex;
flex-direction: column;
justify-content: space-between;
transition: var(--transition);
position: relative;
overflow: hidden;
}
.tool-card:hover {
transform: translateY(-6px);
border-color: var(--border-light);
box-shadow: 0 20px 40px -10px rgba(99, 102, 241, 0.25);
}
.card-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.sparte-tag {
font-size: 0.7rem;
font-weight: 800;
letter-spacing: 0.08em;
padding: 0.25rem 0.6rem;
border-radius: 4px;
}
.tag-veritas {
background: rgba(99, 102, 241, 0.18);
color: #a5b4fc;
border: 1px solid rgba(99, 102, 241, 0.4);
}
.tag-aegis {
background: rgba(244, 63, 94, 0.18);
color: #fda4af;
border: 1px solid rgba(244, 63, 94, 0.4);
}
.tag-cura {
background: rgba(16, 185, 129, 0.18);
color: #6ee7b7;
border: 1px solid rgba(16, 185, 129, 0.4);
}
/* Status Badges */
.status-badge {
font-size: 0.75rem;
font-weight: 700;
padding: 0.3rem 0.75rem;
border-radius: 50px;
}
.badge-active {
background: rgba(16, 185, 129, 0.2);
color: #34d399;
border: 1px solid rgba(16, 185, 129, 0.4);
}
.badge-dev {
background: rgba(56, 189, 248, 0.2);
color: #38bdf8;
border: 1px solid rgba(56, 189, 248, 0.4);
}
.badge-planned {
background: rgba(245, 158, 11, 0.2);
color: #fde047;
border: 1px solid rgba(245, 158, 11, 0.4);
}
.tool-card h3 {
font-size: 1.35rem;
margin-bottom: 0.6rem;
}
.tool-desc {
color: var(--text-muted);
font-size: 0.92rem;
margin-bottom: 1.5rem;
line-height: 1.5;
}
.card-footer {
display: flex;
gap: 0.5rem;
margin-top: auto;
}
.tech-tag {
font-size: 0.75rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid var(--border-glass);
padding: 0.25rem 0.7rem;
border-radius: 4px;
color: var(--text-dim);
}
/* Form Controls */
.form-group {
display: flex;
flex-direction: column;
gap: 0.4rem;
margin-bottom: 1.2rem;
}
.form-group label {
font-size: 0.85rem;
font-weight: 600;
color: var(--text-muted);
}
.form-group input, .form-group select {
padding: 0.8rem 1rem;
background: #090d16;
border: 1px solid var(--border-glass);
border-radius: var(--radius-sm);
color: white;
font-family: inherit;
font-size: 0.95rem;
outline: none;
transition: var(--transition);
}
.form-group input:focus, .form-group select:focus {
border-color: var(--indigo);
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.3);
}
.alert-box {
margin-top: 1.5rem;
padding: 1rem 1.2rem;
border-radius: var(--radius-sm);
display: none;
font-weight: 600;
}
.alert-box.success {
display: block;
background: rgba(16, 185, 129, 0.18);
border: 1px solid rgba(16, 185, 129, 0.5);
color: #6ee7b7;
}
.alert-box.error {
display: block;
background: rgba(244, 63, 94, 0.18);
border: 1px solid rgba(244, 63, 94, 0.5);
color: #fda4af;
}
/* Footer */
.footer {
border-top: 1px solid var(--border-glass);
padding: 2.5rem 0;
color: var(--text-dim);
font-size: 0.9rem;
}
.footer-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.footer-brand {
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 700;
color: var(--text-muted);
}
.footer-links {
display: flex;
gap: 1.5rem;
}
.footer-links a {
color: var(--text-muted);
text-decoration: none;
transition: var(--transition);
}
.footer-links a:hover {
color: white;
}
+6
View File
@@ -0,0 +1,6 @@
#!/bin/bash
sudo -u postgres psql -d veritas_db -c "CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);"
sudo -u postgres psql -d veritas_db -c "ALTER TABLE licenses ADD COLUMN IF NOT EXISTS user_id INT REFERENCES users(id);"
sudo -u postgres psql -d veritas_db -c "ALTER TABLE licenses ADD COLUMN IF NOT EXISTS plan_name VARCHAR(64) DEFAULT 'Pro';"
sudo -u postgres psql -d veritas_db -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO veritas;"
sudo -u postgres psql -d veritas_db -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO veritas;"
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
sudo -u postgres psql -d veritas_db -c "ALTER TABLE users ADD COLUMN IF NOT EXISTS is_admin BOOLEAN DEFAULT FALSE;"
sudo -u postgres psql -d veritas_db -c "UPDATE users SET is_admin = TRUE WHERE id = 1 OR email = 'admin@kanjiv.at';"
sudo -u postgres psql -d veritas_db -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO veritas;"
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
sudo -u postgres psql -d veritas_db -c "CREATE TABLE IF NOT EXISTS licenses (id SERIAL PRIMARY KEY, license_key VARCHAR(64) UNIQUE NOT NULL, max_devices INT DEFAULT 3, active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP);"
sudo -u postgres psql -d veritas_db -c "CREATE TABLE IF NOT EXISTS activations (id SERIAL PRIMARY KEY, license_key VARCHAR(64) NOT NULL, hardware_id VARCHAR(128) NOT NULL, app_name VARCHAR(64) NOT NULL, activated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, CONSTRAINT unique_activation UNIQUE(license_key, hardware_id));"
sudo -u postgres psql -d veritas_db -c "INSERT INTO licenses (license_key, max_devices) VALUES ('VERITAS-PRO-DEMO-2026', 10) ON CONFLICT DO NOTHING;"
+6
View File
@@ -0,0 +1,6 @@
vps IP 45.156.84.238
vps benutzer : root
vps PW: Matthias2710..
---------------------------------