Test zu Fake geändert, weil Namensproblem

Dieser Commit ist enthalten in:
2025-06-21 17:22:12 +02:00
Ursprung fec588ba06
Commit 3d899b1c45
22 geänderte Dateien mit 614 neuen und 347 gelöschten Zeilen

Datei anzeigen

@@ -73,7 +73,8 @@
"Bash(docker compose:*)",
"Bash(true)",
"Bash(git checkout:*)",
"Bash(touch:*)"
"Bash(touch:*)",
"Bash(wget:*)"
],
"deny": []
}

Datei anzeigen

@@ -8,7 +8,7 @@ CREATE TABLE IF NOT EXISTS customers (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT,
is_test BOOLEAN DEFAULT FALSE,
is_fake BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT unique_email UNIQUE (email)
);
@@ -21,7 +21,7 @@ CREATE TABLE IF NOT EXISTS licenses (
valid_from DATE NOT NULL,
valid_until DATE NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
is_test BOOLEAN DEFAULT FALSE,
is_fake BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
@@ -129,7 +129,7 @@ CREATE TABLE IF NOT EXISTS resource_pools (
quarantine_until TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
notes TEXT,
is_test BOOLEAN DEFAULT FALSE,
is_fake BOOLEAN DEFAULT FALSE,
UNIQUE(resource_type, resource_value)
);
@@ -245,48 +245,48 @@ CREATE TABLE IF NOT EXISTS users (
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_reset_token ON users(password_reset_token) WHERE password_reset_token IS NOT NULL;
-- Migration: Add is_test column to licenses if it doesn't exist
-- Migration: Add is_fake column to licenses if it doesn't exist
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'licenses' AND column_name = 'is_test') THEN
ALTER TABLE licenses ADD COLUMN is_test BOOLEAN DEFAULT FALSE;
WHERE table_name = 'licenses' AND column_name = 'is_fake') THEN
ALTER TABLE licenses ADD COLUMN is_fake BOOLEAN DEFAULT FALSE;
-- Mark all existing licenses as test data
UPDATE licenses SET is_test = TRUE;
-- Mark all existing licenses as fake data
UPDATE licenses SET is_fake = TRUE;
-- Add index for better performance when filtering test data
CREATE INDEX idx_licenses_is_test ON licenses(is_test);
-- Add index for better performance when filtering fake data
CREATE INDEX idx_licenses_is_fake ON licenses(is_fake);
END IF;
END $$;
-- Migration: Add is_test column to customers if it doesn't exist
-- Migration: Add is_fake column to customers if it doesn't exist
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'customers' AND column_name = 'is_test') THEN
ALTER TABLE customers ADD COLUMN is_test BOOLEAN DEFAULT FALSE;
WHERE table_name = 'customers' AND column_name = 'is_fake') THEN
ALTER TABLE customers ADD COLUMN is_fake BOOLEAN DEFAULT FALSE;
-- Mark all existing customers as test data
UPDATE customers SET is_test = TRUE;
-- Mark all existing customers as fake data
UPDATE customers SET is_fake = TRUE;
-- Add index for better performance
CREATE INDEX idx_customers_is_test ON customers(is_test);
CREATE INDEX idx_customers_is_fake ON customers(is_fake);
END IF;
END $$;
-- Migration: Add is_test column to resource_pools if it doesn't exist
-- Migration: Add is_fake column to resource_pools if it doesn't exist
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'resource_pools' AND column_name = 'is_test') THEN
ALTER TABLE resource_pools ADD COLUMN is_test BOOLEAN DEFAULT FALSE;
WHERE table_name = 'resource_pools' AND column_name = 'is_fake') THEN
ALTER TABLE resource_pools ADD COLUMN is_fake BOOLEAN DEFAULT FALSE;
-- Mark all existing resources as test data
UPDATE resource_pools SET is_test = TRUE;
-- Mark all existing resources as fake data
UPDATE resource_pools SET is_fake = TRUE;
-- Add index for better performance
CREATE INDEX idx_resource_pools_is_test ON resource_pools(is_test);
CREATE INDEX idx_resource_pools_is_fake ON resource_pools(is_fake);
END IF;
END $$;

Datei anzeigen

@@ -0,0 +1,48 @@
-- Migration script to rename is_test columns to is_fake
-- This separates fake/demo data from test licenses
-- 1. Rename columns in all tables
DO $$
BEGIN
-- Rename is_test to is_fake in customers table
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'customers' AND column_name = 'is_test') THEN
ALTER TABLE customers RENAME COLUMN is_test TO is_fake;
END IF;
-- Rename is_test to is_fake in licenses table
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'licenses' AND column_name = 'is_test') THEN
ALTER TABLE licenses RENAME COLUMN is_test TO is_fake;
END IF;
-- Rename is_test to is_fake in resource_pools table
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'resource_pools' AND column_name = 'is_test') THEN
ALTER TABLE resource_pools RENAME COLUMN is_test TO is_fake;
END IF;
END $$;
-- 2. Rename indexes
DO $$
BEGIN
-- Rename index for customers
IF EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_customers_is_test') THEN
ALTER INDEX idx_customers_is_test RENAME TO idx_customers_is_fake;
END IF;
-- Rename index for licenses
IF EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_licenses_is_test') THEN
ALTER INDEX idx_licenses_is_test RENAME TO idx_licenses_is_fake;
END IF;
-- Rename index for resource_pools
IF EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_resource_pools_is_test') THEN
ALTER INDEX idx_resource_pools_is_test RENAME TO idx_resource_pools_is_fake;
END IF;
END $$;
-- 3. Add comments to clarify the purpose
COMMENT ON COLUMN customers.is_fake IS 'Marks fake/demo data, not to be confused with test licenses';
COMMENT ON COLUMN licenses.is_fake IS 'Marks fake/demo data, not to be confused with test license type';
COMMENT ON COLUMN resource_pools.is_fake IS 'Marks fake/demo resources';

Datei anzeigen

@@ -32,12 +32,12 @@ def get_user_by_username(username):
return None
def get_licenses(show_test=False):
def get_licenses(show_fake=False):
"""Get all licenses from database"""
try:
with get_db_connection() as conn:
with get_db_cursor(conn) as cur:
if show_test:
if show_fake:
cur.execute("""
SELECT l.*, c.name as customer_name
FROM licenses l
@@ -49,7 +49,7 @@ def get_licenses(show_test=False):
SELECT l.*, c.name as customer_name
FROM licenses l
LEFT JOIN customers c ON l.customer_id = c.id
WHERE l.is_test = false
WHERE l.is_fake = false
ORDER BY l.created_at DESC
""")
@@ -86,7 +86,7 @@ def get_license_by_id(license_id):
return None
def get_customers(show_test=False, search=None):
def get_customers(show_fake=False, search=None):
"""Get all customers from database"""
try:
with get_db_connection() as conn:
@@ -102,8 +102,8 @@ def get_customers(show_test=False, search=None):
where_clauses = []
params = []
if not show_test:
where_clauses.append("c.is_test = false")
if not show_fake:
where_clauses.append("c.is_fake = false")
if search:
where_clauses.append("(LOWER(c.name) LIKE LOWER(%s) OR LOWER(c.email) LIKE LOWER(%s))")

Datei anzeigen

@@ -25,18 +25,18 @@ def dashboard():
try:
# Hole Statistiken mit sicheren Defaults
# Anzahl aktiver Lizenzen (nur echte Daten, keine Testdaten)
cur.execute("SELECT COUNT(*) FROM licenses WHERE is_active = true AND is_test = false")
cur.execute("SELECT COUNT(*) FROM licenses WHERE is_active = true AND is_fake = false")
active_licenses = cur.fetchone()[0] if cur.rowcount > 0 else 0
# Anzahl Kunden (nur echte Kunden, keine Testkunden)
cur.execute("SELECT COUNT(*) FROM customers WHERE is_test = false")
# Anzahl Kunden (nur echte Kunden, keine Fake-Kunden)
cur.execute("SELECT COUNT(*) FROM customers WHERE is_fake = false")
total_customers = cur.fetchone()[0] if cur.rowcount > 0 else 0
# Testdaten separat zählen für optionale Anzeige
cur.execute("SELECT COUNT(*) FROM customers WHERE is_test = true")
test_customers_count = cur.fetchone()[0] if cur.rowcount > 0 else 0
cur.execute("SELECT COUNT(*) FROM customers WHERE is_fake = true")
fake_customers_count = cur.fetchone()[0] if cur.rowcount > 0 else 0
cur.execute("SELECT COUNT(*) FROM licenses WHERE is_test = true")
cur.execute("SELECT COUNT(*) FROM licenses WHERE is_fake = true")
test_licenses_count = cur.fetchone()[0] if cur.rowcount > 0 else 0
# Anzahl aktiver Sessions (Admin-Panel)
@@ -61,7 +61,7 @@ def dashboard():
FROM license_heartbeats lh
JOIN licenses l ON l.id = lh.license_id
WHERE lh.timestamp > NOW() - INTERVAL '15 minutes'
AND l.is_test = false
AND l.is_fake = false
""")
active_usage = cur.fetchone()[0] if cur.rowcount > 0 else 0
except Exception as e:
@@ -82,7 +82,7 @@ def dashboard():
FROM licenses l
LEFT JOIN customers c ON l.customer_id = c.id
LEFT JOIN sessions s ON l.id = s.license_id
WHERE l.is_test = false AND c.is_test = false
WHERE l.is_fake = false AND c.is_fake = false
GROUP BY l.license_key, c.name
ORDER BY session_count DESC
LIMIT 10
@@ -109,7 +109,7 @@ def dashboard():
cur.execute("""
SELECT COUNT(*) as full_licenses
FROM licenses
WHERE is_test = false
WHERE is_fake = false
""")
license_count = cur.fetchone()
full_licenses = license_count[0] if license_count and license_count[0] else 0
@@ -123,7 +123,7 @@ def dashboard():
COUNT(CASE WHEN valid_until < NOW() THEN 1 END) as expired,
COUNT(CASE WHEN is_active = false THEN 1 END) as inactive
FROM licenses
WHERE is_test = false
WHERE is_fake = false
""")
license_status = cur.fetchone()
active_licenses_count = license_status[0] if license_status and license_status[0] else 0
@@ -140,8 +140,8 @@ def dashboard():
EXTRACT(DAY FROM (l.valid_until - NOW())) as days_remaining
FROM licenses l
JOIN customers c ON l.customer_id = c.id
WHERE l.is_test = false
AND c.is_test = false
WHERE l.is_fake = false
AND c.is_fake = false
AND l.is_active = true
AND l.valid_until IS NOT NULL
AND l.valid_until > NOW()
@@ -166,8 +166,8 @@ def dashboard():
END as status
FROM licenses l
JOIN customers c ON l.customer_id = c.id
WHERE l.is_test = false
AND c.is_test = false
WHERE l.is_fake = false
AND c.is_fake = false
ORDER BY l.created_at DESC
LIMIT 5
""")
@@ -176,15 +176,15 @@ def dashboard():
# Stats Objekt für Template erstellen
stats = {
'total_customers': total_customers,
'total_licenses': active_licenses, # This was already filtered for is_test = false
'total_licenses': active_licenses, # This was already filtered for is_fake = false
'active_sessions': active_sessions, # Admin-Panel Sessions
'active_usage': active_usage, # Aktive Kunden-Nutzung
'active_licenses': active_licenses_count,
'full_licenses': full_licenses,
'test_licenses': test_version_licenses, # Test versions, not test data
'test_data_count': test_licenses_count, # Actual test data count
'test_customers_count': test_customers_count,
'test_resources_count': 0,
'fake_data_count': test_licenses_count, # Actual test data count
'fake_customers_count': fake_customers_count,
'fake_resources_count': 0,
'expired_licenses': expired_licenses,
'inactive_licenses': inactive_licenses,
'last_backup': None,
@@ -210,7 +210,7 @@ def dashboard():
COUNT(CASE WHEN status = 'quarantine' THEN 1 END) as quarantine,
COUNT(*) as total
FROM resource_pool
WHERE type = %s AND is_test = false
WHERE type = %s AND is_fake = false
""", (resource_type,))
result = cur.fetchone()
@@ -249,9 +249,9 @@ def dashboard():
# Count test resources separately
try:
cur.execute("SELECT COUNT(*) FROM resource_pool WHERE is_test = true")
test_resources_count = cur.fetchone()[0] if cur.rowcount > 0 else 0
stats['test_resources_count'] = test_resources_count
cur.execute("SELECT COUNT(*) FROM resource_pool WHERE is_fake = true")
fake_resources_count = cur.fetchone()[0] if cur.rowcount > 0 else 0
stats['fake_resources_count'] = fake_resources_count
except:
pass

Datei anzeigen

@@ -25,7 +25,7 @@ def api_customers():
try:
# Get all customers (with optional search)
customers = get_customers(show_test=True, search=search)
customers = get_customers(show_fake=True, search=search)
# Pagination
start = (page - 1) * per_page
@@ -396,7 +396,7 @@ def bulk_delete_licenses():
for license_id in license_ids:
# Hole vollständige Lizenz-Info
cur.execute("""
SELECT l.id, l.license_key, l.is_active, l.is_test,
SELECT l.id, l.license_key, l.is_active, l.is_fake,
c.name as customer_name
FROM licenses l
LEFT JOIN customers c ON l.customer_id = c.id
@@ -407,7 +407,7 @@ def bulk_delete_licenses():
if not result:
continue
license_id, license_key, is_active, is_test, customer_name = result
license_id, license_key, is_active, is_fake, customer_name = result
# Safety check: Don't delete active licenses unless forced
if is_active and not force_delete:
@@ -601,7 +601,7 @@ def get_license_resources(license_id):
rp.id,
rp.resource_type,
rp.resource_value,
rp.is_test,
rp.is_fake,
rp.status_changed_at,
lr.assigned_at,
lr.assigned_by
@@ -617,7 +617,7 @@ def get_license_resources(license_id):
'id': row[0],
'type': row[1],
'value': row[2],
'is_test': row[3],
'is_fake': row[3],
'status_changed_at': row[4].isoformat() if row[4] else None,
'assigned_at': row[5].isoformat() if row[5] else None,
'assigned_by': row[6]
@@ -675,7 +675,7 @@ def allocate_resources():
try:
# Prüfe ob Ressource verfügbar ist
cur.execute("""
SELECT resource_value, status, is_test
SELECT resource_value, status, is_fake
FROM resource_pools
WHERE id = %s
""", (resource_id,))
@@ -690,8 +690,8 @@ def allocate_resources():
continue
# Prüfe Test/Produktion Kompatibilität
if resource[2] != license_data['is_test']:
errors.append(f"Ressource {resource[0]} ist {'Test' if resource[2] else 'Produktion'}, Lizenz ist {'Test' if license_data['is_test'] else 'Produktion'}")
if resource[2] != license_data['is_fake']:
errors.append(f"Ressource {resource[0]} ist {'Test' if resource[2] else 'Produktion'}, Lizenz ist {'Test' if license_data['is_fake'] else 'Produktion'}")
continue
# Weise Ressource zu
@@ -751,32 +751,32 @@ def check_resource_availability():
resource_type = request.args.get('type')
if resource_type:
count = int(request.args.get('count', 1))
is_test = request.args.get('is_test', 'false') == 'true'
show_test = request.args.get('show_test', 'false') == 'true'
is_fake = request.args.get('is_fake', 'false') == 'true'
show_fake = request.args.get('show_fake', 'false') == 'true'
conn = get_connection()
cur = conn.cursor()
try:
# Hole verfügbare Ressourcen mit Details
if show_test:
if show_fake:
# Zeige alle verfügbaren Ressourcen (Test und Produktion)
cur.execute("""
SELECT id, resource_value, is_test
SELECT id, resource_value, is_fake
FROM resource_pools
WHERE resource_type = %s
AND status = 'available'
ORDER BY is_test, resource_value
ORDER BY is_fake, resource_value
LIMIT %s
""", (resource_type, count))
else:
# Zeige nur Produktions-Ressourcen
cur.execute("""
SELECT id, resource_value, is_test
SELECT id, resource_value, is_fake
FROM resource_pools
WHERE resource_type = %s
AND status = 'available'
AND is_test = false
AND is_fake = false
ORDER BY resource_value
LIMIT %s
""", (resource_type, count))
@@ -786,7 +786,7 @@ def check_resource_availability():
available_resources.append({
'id': row[0],
'value': row[1],
'is_test': row[2]
'is_fake': row[2]
})
return jsonify({
@@ -794,7 +794,7 @@ def check_resource_availability():
'requested': count,
'available': available_resources,
'sufficient': len(available_resources) >= count,
'show_test': show_test
'show_fake': show_fake
})
except Exception as e:
@@ -808,7 +808,7 @@ def check_resource_availability():
domain_count = int(request.args.get('domain', 0))
ipv4_count = int(request.args.get('ipv4', 0))
phone_count = int(request.args.get('phone', 0))
is_test = request.args.get('is_test', 'false') == 'true'
is_fake = request.args.get('is_fake', 'false') == 'true'
conn = get_connection()
cur = conn.cursor()
@@ -823,8 +823,8 @@ def check_resource_availability():
FROM resource_pools
WHERE resource_type = 'domain'
AND status = 'available'
AND is_test = %s
""", (is_test,))
AND is_fake = %s
""", (is_fake,))
domain_available = cur.fetchone()[0]
# IPv4
@@ -833,8 +833,8 @@ def check_resource_availability():
FROM resource_pools
WHERE resource_type = 'ipv4'
AND status = 'available'
AND is_test = %s
""", (is_test,))
AND is_fake = %s
""", (is_fake,))
ipv4_available = cur.fetchone()[0]
# Phones
@@ -843,8 +843,8 @@ def check_resource_availability():
FROM resource_pools
WHERE resource_type = 'phone'
AND status = 'available'
AND is_test = %s
""", (is_test,))
AND is_fake = %s
""", (is_fake,))
phone_available = cur.fetchone()[0]
return jsonify({
@@ -862,7 +862,7 @@ def check_resource_availability():
ipv4_available >= ipv4_count and
phone_available >= phone_count
),
'is_test': is_test
'is_fake': is_fake
})
except Exception as e:

Datei anzeigen

@@ -42,7 +42,7 @@ def batch_create():
valid_from = request.form['valid_from']
valid_until = request.form['valid_until']
device_limit = int(request.form['device_limit'])
is_test = 'is_test' in request.form
is_fake = 'is_fake' in request.form
# Validierung
if count < 1 or count > 100:
@@ -74,13 +74,13 @@ def batch_create():
INSERT INTO licenses (
license_key, customer_id,
license_type, valid_from, valid_until, device_limit,
is_test, created_at
is_fake, created_at
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
RETURNING id
""", (
license_key, customer_id,
license_type, valid_from, valid_until, device_limit,
is_test, datetime.now()
is_fake, datetime.now()
))
license_id = cur.fetchone()[0]
@@ -141,7 +141,7 @@ def batch_export():
SELECT
l.license_key, c.name, c.email,
l.license_type, l.valid_from, l.valid_until,
l.device_limit, l.is_test, l.created_at
l.device_limit, l.is_fake, l.created_at
FROM licenses l
JOIN customers c ON l.customer_id = c.id
WHERE l.id = ANY(%s)
@@ -158,7 +158,7 @@ def batch_export():
'valid_from': row[4],
'valid_until': row[5],
'device_limit': row[6],
'is_test': row[7],
'is_fake': row[7],
'created_at': row[8]
})
@@ -334,13 +334,13 @@ def batch_import():
INSERT INTO licenses (
license_key, customer_id,
license_type, valid_from, valid_until, device_limit,
is_test, created_at
is_fake, created_at
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)
RETURNING id
""", (
license_key, customer_id,
row['license_type'], row['valid_from'], row['valid_until'],
int(row['device_limit']), row.get('is_test', False),
int(row['device_limit']), row.get('is_fake', False),
datetime.now()
))

Datei anzeigen

@@ -22,14 +22,14 @@ def test_customers():
@customer_bp.route("/customers")
@login_required
def customers():
show_test = request.args.get('show_test', 'false').lower() == 'true'
show_fake = request.args.get('show_fake', 'false').lower() == 'true'
search = request.args.get('search', '').strip()
page = request.args.get('page', 1, type=int)
per_page = 20
sort = request.args.get('sort', 'name')
order = request.args.get('order', 'asc')
customers_list = get_customers(show_test=show_test, search=search)
customers_list = get_customers(show_fake=show_fake, search=search)
# Sortierung
if sort == 'name':
@@ -48,7 +48,7 @@ def customers():
return render_template("customers.html",
customers=paginated_customers,
show_test=show_test,
show_fake=show_fake,
search=search,
page=page,
per_page=per_page,
@@ -77,17 +77,17 @@ def edit_customer(customer_id):
new_values = {
'name': request.form['name'],
'email': request.form['email'],
'is_test': 'is_test' in request.form
'is_fake': 'is_fake' in request.form
}
cur.execute("""
UPDATE customers
SET name = %s, email = %s, is_test = %s
SET name = %s, email = %s, is_fake = %s
WHERE id = %s
""", (
new_values['name'],
new_values['email'],
new_values['is_test'],
new_values['is_fake'],
customer_id
))
@@ -98,16 +98,16 @@ def edit_customer(customer_id):
old_values={
'name': current_customer['name'],
'email': current_customer['email'],
'is_test': current_customer.get('is_test', False)
'is_fake': current_customer.get('is_fake', False)
},
new_values=new_values)
flash('Kunde erfolgreich aktualisiert!', 'success')
# Redirect mit show_test Parameter wenn nötig
# Redirect mit show_fake Parameter wenn nötig
redirect_url = url_for('customers.customers_licenses')
if request.form.get('show_test') == 'true':
redirect_url += '?show_test=true'
if request.form.get('show_fake') == 'true':
redirect_url += '?show_fake=true'
return redirect(redirect_url)
finally:
cur.close()
@@ -137,13 +137,13 @@ def create_customer():
# Insert new customer
name = request.form['name']
email = request.form['email']
is_test = 'is_test' in request.form # Checkbox ist nur vorhanden wenn angekreuzt
is_fake = 'is_fake' in request.form # Checkbox ist nur vorhanden wenn angekreuzt
cur.execute("""
INSERT INTO customers (name, email, is_test, created_at)
INSERT INTO customers (name, email, is_fake, created_at)
VALUES (%s, %s, %s, %s)
RETURNING id
""", (name, email, is_test, datetime.now()))
""", (name, email, is_fake, datetime.now()))
customer_id = cur.fetchone()[0]
conn.commit()
@@ -153,17 +153,17 @@ def create_customer():
new_values={
'name': name,
'email': email,
'is_test': is_test
'is_fake': is_fake
})
if is_test:
flash(f'Testkunde {name} erfolgreich erstellt!', 'success')
if is_fake:
flash(f'Fake-Kunde {name} erfolgreich erstellt!', 'success')
else:
flash(f'Kunde {name} erfolgreich erstellt!', 'success')
# Redirect mit show_test=true wenn Testkunde erstellt wurde
if is_test:
return redirect(url_for('customers.customers_licenses', show_test='true'))
# Redirect mit show_fake=true wenn Fake-Kunde erstellt wurde
if is_fake:
return redirect(url_for('customers.customers_licenses', show_fake='true'))
else:
return redirect(url_for('customers.customers_licenses'))
@@ -232,9 +232,9 @@ def customers_licenses():
import psycopg2
logging.info("=== CUSTOMERS-LICENSES ROUTE CALLED ===")
# Get show_test parameter from URL
show_test = request.args.get('show_test', 'false').lower() == 'true'
logging.info(f"show_test parameter: {show_test}")
# Get show_fake parameter from URL
show_fake = request.args.get('show_fake', 'false').lower() == 'true'
logging.info(f"show_fake parameter: {show_fake}")
try:
# Direkte Verbindung ohne Helper-Funktionen
@@ -250,7 +250,7 @@ def customers_licenses():
try:
# Hole alle Kunden mit ihren Lizenzen
# Wenn show_test=false, zeige nur Nicht-Test-Kunden
# Wenn show_fake=false, zeige nur Nicht-Test-Kunden
query = """
SELECT
c.id,
@@ -259,17 +259,17 @@ def customers_licenses():
c.created_at,
COUNT(l.id),
COUNT(CASE WHEN l.is_active = true THEN 1 END),
COUNT(CASE WHEN l.is_test = true THEN 1 END),
COUNT(CASE WHEN l.is_fake = true THEN 1 END),
MAX(l.created_at),
c.is_test
c.is_fake
FROM customers c
LEFT JOIN licenses l ON c.id = l.customer_id
WHERE (%s OR c.is_test = false)
GROUP BY c.id, c.name, c.email, c.created_at, c.is_test
WHERE (%s OR c.is_fake = false)
GROUP BY c.id, c.name, c.email, c.created_at, c.is_fake
ORDER BY c.name
"""
cur.execute(query, (show_test,))
cur.execute(query, (show_fake,))
customers = []
results = cur.fetchall()
@@ -286,12 +286,12 @@ def customers_licenses():
'active_licenses': row[5],
'test_licenses': row[6],
'last_license_created': row[7],
'is_test': row[8]
'is_fake': row[8]
})
return render_template("customers_licenses.html",
customers=customers,
show_test=show_test)
show_fake=show_fake)
finally:
cur.close()
@@ -325,7 +325,7 @@ def api_customer_licenses(customer_id):
l.license_key,
l.license_type,
l.is_active,
l.is_test,
l.is_fake,
l.valid_from,
l.valid_until,
l.device_limit,
@@ -387,7 +387,7 @@ def api_customer_licenses(customer_id):
'license_key': row[1],
'license_type': row[2],
'is_active': row[3],
'is_test': row[4],
'is_fake': row[4],
'valid_from': row[5].strftime('%Y-%m-%d') if row[5] else None,
'valid_until': row[6].strftime('%Y-%m-%d') if row[6] else None,
'device_limit': row[7],
@@ -442,7 +442,7 @@ def api_customer_quick_stats(customer_id):
SELECT
COUNT(l.id) as total_licenses,
COUNT(CASE WHEN l.is_active = true THEN 1 END) as active_licenses,
COUNT(CASE WHEN l.is_test = true THEN 1 END) as test_licenses,
COUNT(CASE WHEN l.is_fake = true THEN 1 END) as test_licenses,
SUM(l.device_limit) as total_device_limit
FROM licenses l
WHERE l.customer_id = %s

Datei anzeigen

@@ -21,10 +21,10 @@ def export_licenses():
try:
# Filter aus Request
show_test = request.args.get('show_test', 'false') == 'true'
show_fake = request.args.get('show_fake', 'false') == 'true'
# SQL Query mit optionalem Test-Filter
if show_test:
if show_fake:
query = """
SELECT
l.id,
@@ -37,7 +37,7 @@ def export_licenses():
l.is_active,
l.device_limit,
l.created_at,
l.is_test,
l.is_fake,
CASE
WHEN l.valid_until < CURRENT_DATE THEN 'Abgelaufen'
WHEN l.is_active = false THEN 'Deaktiviert'
@@ -62,7 +62,7 @@ def export_licenses():
l.is_active,
l.device_limit,
l.created_at,
l.is_test,
l.is_fake,
CASE
WHEN l.valid_until < CURRENT_DATE THEN 'Abgelaufen'
WHEN l.is_active = false THEN 'Deaktiviert'
@@ -72,7 +72,7 @@ def export_licenses():
(SELECT COUNT(DISTINCT hardware_id) FROM sessions s WHERE s.license_key = l.license_key) as registered_devices
FROM licenses l
LEFT JOIN customers c ON l.customer_id = c.id
WHERE l.is_test = false
WHERE l.is_fake = false
ORDER BY l.created_at DESC
"""
@@ -81,7 +81,7 @@ def export_licenses():
# Daten für Export vorbereiten
data = []
columns = ['ID', 'Lizenzschlüssel', 'Kunde', 'E-Mail', 'Typ', 'Gültig von',
'Gültig bis', 'Aktiv', 'Gerätelimit', 'Erstellt am', 'Test-Lizenz',
'Gültig bis', 'Aktiv', 'Gerätelimit', 'Erstellt am', 'Fake-Lizenz',
'Status', 'Aktive Sessions', 'Registrierte Geräte']
for row in cur.fetchall():
@@ -163,13 +163,13 @@ def export_customers():
c.phone,
c.address,
c.created_at,
c.is_test,
c.is_fake,
COUNT(l.id) as license_count,
COUNT(CASE WHEN l.is_active = true THEN 1 END) as active_licenses,
COUNT(CASE WHEN l.valid_until < CURRENT_DATE THEN 1 END) as expired_licenses
FROM customers c
LEFT JOIN licenses l ON c.id = l.customer_id
GROUP BY c.id, c.name, c.email, c.phone, c.address, c.created_at, c.is_test
GROUP BY c.id, c.name, c.email, c.phone, c.address, c.created_at, c.is_fake
ORDER BY c.name
""")
@@ -227,7 +227,7 @@ def export_sessions():
s.last_heartbeat,
s.is_active,
l.license_type,
l.is_test
l.is_fake
FROM sessions s
LEFT JOIN licenses l ON s.license_key = l.license_key
WHERE s.is_active = true
@@ -247,7 +247,7 @@ def export_sessions():
s.last_heartbeat,
s.is_active,
l.license_type,
l.is_test
l.is_fake
FROM sessions s
LEFT JOIN licenses l ON s.license_key = l.license_key
WHERE s.started_at >= CURRENT_TIMESTAMP - INTERVAL '%s days'
@@ -259,7 +259,7 @@ def export_sessions():
data = []
columns = ['ID', 'Lizenzschlüssel', 'Kunde', 'Benutzer', 'Geräte-ID',
'Login-Zeit', 'Logout-Zeit', 'Letzte Aktivität', 'Aktiv',
'Lizenztyp', 'Test-Lizenz']
'Lizenztyp', 'Fake-Lizenz']
for row in cur.fetchall():
data.append(list(row))
@@ -295,7 +295,7 @@ def export_resources():
# Filter aus Request
resource_type = request.args.get('type', 'all')
status_filter = request.args.get('status', 'all')
show_test = request.args.get('show_test', 'false') == 'true'
show_fake = request.args.get('show_fake', 'false') == 'true'
# SQL Query aufbauen
query = """
@@ -304,7 +304,7 @@ def export_resources():
rp.resource_type,
rp.resource_value,
rp.status,
rp.is_test,
rp.is_fake,
l.license_key,
c.name as customer_name,
rp.created_at,
@@ -328,8 +328,8 @@ def export_resources():
query += " AND rp.status = %s"
params.append(status_filter)
if not show_test:
query += " AND rp.is_test = false"
if not show_fake:
query += " AND rp.is_fake = false"
query += " ORDER BY rp.resource_type, rp.resource_value"

Datei anzeigen

@@ -24,43 +24,47 @@ def licenses():
# Get filter parameters
search = request.args.get('search', '').strip()
filter_type = request.args.get('type', '')
filter_status = request.args.get('status', '')
filter_types = request.args.getlist('types[]') # Multi-select for types
filter_statuses = request.args.getlist('statuses[]') # Multi-select for statuses
sort = request.args.get('sort', 'created_at')
order = request.args.get('order', 'desc')
page = request.args.get('page', 1, type=int)
per_page = 50
# Process type filter to determine show_test
# Default: show only real data unless test_data is explicitly selected
show_test = filter_type == 'test_data'
# Get all licenses (both fake and real data)
licenses_list = get_licenses(show_fake=True)
# Get licenses based on filters
licenses_list = get_licenses(show_test=show_test)
# Type filtering with OR logic
if filter_types:
filtered_by_type = []
for license in licenses_list:
# Check if license matches any selected type
if 'full' in filter_types and license.get('license_type') == 'full' and not license.get('is_fake'):
filtered_by_type.append(license)
elif 'test' in filter_types and license.get('license_type') == 'test' and not license.get('is_fake'):
filtered_by_type.append(license)
licenses_list = filtered_by_type
else:
# If no types selected, show only real data by default
licenses_list = [l for l in licenses_list if not l.get('is_fake')]
# Additional filtering based on type and status
if filter_type:
if filter_type == 'full':
licenses_list = [l for l in licenses_list if l.get('license_type') == 'full' and not l.get('is_test')]
elif filter_type == 'test':
licenses_list = [l for l in licenses_list if l.get('license_type') == 'test' and not l.get('is_test')]
elif filter_type == 'test_data':
licenses_list = [l for l in licenses_list if l.get('is_test')]
elif filter_type == 'live_data':
licenses_list = [l for l in licenses_list if not l.get('is_test')]
# Status filtering
if filter_status:
# Status filtering with OR logic
if filter_statuses:
now = datetime.now()
if filter_status == 'active':
licenses_list = [l for l in licenses_list if l.get('is_active') and l.get('valid_until') and l.get('valid_until') > now]
elif filter_status == 'expiring':
expiry_threshold = now + timedelta(days=30)
licenses_list = [l for l in licenses_list if l.get('valid_until') and now < l.get('valid_until') <= expiry_threshold]
elif filter_status == 'expired':
licenses_list = [l for l in licenses_list if l.get('valid_until') and l.get('valid_until') <= now]
elif filter_status == 'inactive':
licenses_list = [l for l in licenses_list if not l.get('is_active')]
filtered_by_status = []
for license in licenses_list:
# Check if license matches any selected status
if 'active' in filter_statuses and license.get('is_active') and license.get('valid_until') and license.get('valid_until') > now:
filtered_by_status.append(license)
elif 'expiring' in filter_statuses:
expiry_threshold = now + timedelta(days=30)
if license.get('valid_until') and now < license.get('valid_until') <= expiry_threshold:
filtered_by_status.append(license)
elif 'expired' in filter_statuses and license.get('valid_until') and license.get('valid_until') <= now:
filtered_by_status.append(license)
elif 'inactive' in filter_statuses and not license.get('is_active'):
filtered_by_status.append(license)
licenses_list = filtered_by_status
# Search filtering
if search:
@@ -79,10 +83,9 @@ def licenses():
return render_template("licenses.html",
licenses=licenses_list,
show_test=show_test,
search=search,
filter_type=filter_type,
filter_status=filter_status,
filter_types=filter_types,
filter_statuses=filter_statuses,
sort=sort,
order=order,
page=page,
@@ -114,14 +117,14 @@ def edit_license(license_id):
'valid_from': request.form['valid_from'],
'valid_until': request.form['valid_until'],
'is_active': 'is_active' in request.form,
'is_test': 'is_test' in request.form,
'is_fake': 'is_fake' in request.form,
'device_limit': int(request.form.get('device_limit', 3))
}
cur.execute("""
UPDATE licenses
SET license_key = %s, license_type = %s, valid_from = %s,
valid_until = %s, is_active = %s, is_test = %s, device_limit = %s
valid_until = %s, is_active = %s, is_fake = %s, device_limit = %s
WHERE id = %s
""", (
new_values['license_key'],
@@ -129,7 +132,7 @@ def edit_license(license_id):
new_values['valid_from'],
new_values['valid_until'],
new_values['is_active'],
new_values['is_test'],
new_values['is_fake'],
new_values['device_limit'],
license_id
))
@@ -144,7 +147,7 @@ def edit_license(license_id):
'valid_from': str(current_license.get('valid_from', '')),
'valid_until': str(current_license.get('valid_until', '')),
'is_active': current_license.get('is_active'),
'is_test': current_license.get('is_test'),
'is_fake': current_license.get('is_fake'),
'device_limit': current_license.get('device_limit', 3)
},
new_values=new_values)
@@ -283,7 +286,7 @@ def create_license():
license_key = request.form["license_key"].upper() # Immer Großbuchstaben
license_type = request.form["license_type"]
valid_from = request.form["valid_from"]
is_test = request.form.get("is_test") == "on" # Checkbox value
is_fake = request.form.get("is_fake") == "on" # Checkbox value
# Berechne valid_until basierend auf Laufzeit
duration = int(request.form.get("duration", 1))
@@ -337,19 +340,19 @@ def create_license():
# Kunde einfügen (erbt Test-Status von Lizenz)
cur.execute("""
INSERT INTO customers (name, email, is_test, created_at)
INSERT INTO customers (name, email, is_fake, created_at)
VALUES (%s, %s, %s, NOW())
RETURNING id
""", (name, email, is_test))
""", (name, email, is_fake))
customer_id = cur.fetchone()[0]
customer_info = {'name': name, 'email': email, 'is_test': is_test}
customer_info = {'name': name, 'email': email, 'is_fake': is_fake}
# Audit-Log für neuen Kunden
log_audit('CREATE', 'customer', customer_id,
new_values={'name': name, 'email': email, 'is_test': is_test})
new_values={'name': name, 'email': email, 'is_fake': is_fake})
else:
# Bestehender Kunde - hole Infos für Audit-Log
cur.execute("SELECT name, email, is_test FROM customers WHERE id = %s", (customer_id,))
cur.execute("SELECT name, email, is_fake FROM customers WHERE id = %s", (customer_id,))
customer_data = cur.fetchone()
if not customer_data:
flash('Kunde nicht gefunden!', 'error')
@@ -357,17 +360,17 @@ def create_license():
customer_info = {'name': customer_data[0], 'email': customer_data[1]}
# Wenn Kunde Test-Kunde ist, Lizenz auch als Test markieren
if customer_data[2]: # is_test des Kunden
is_test = True
if customer_data[2]: # is_fake des Kunden
is_fake = True
# Lizenz hinzufügen
cur.execute("""
INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active,
domain_count, ipv4_count, phone_count, device_limit, is_test)
domain_count, ipv4_count, phone_count, device_limit, is_fake)
VALUES (%s, %s, %s, %s, %s, TRUE, %s, %s, %s, %s, %s)
RETURNING id
""", (license_key, customer_id, license_type, valid_from, valid_until,
domain_count, ipv4_count, phone_count, device_limit, is_test))
domain_count, ipv4_count, phone_count, device_limit, is_fake))
license_id = cur.fetchone()[0]
# Ressourcen zuweisen
@@ -375,10 +378,10 @@ def create_license():
# Prüfe Verfügbarkeit
cur.execute("""
SELECT
(SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'domain' AND status = 'available' AND is_test = %s) as domains,
(SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'ipv4' AND status = 'available' AND is_test = %s) as ipv4s,
(SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'phone' AND status = 'available' AND is_test = %s) as phones
""", (is_test, is_test, is_test))
(SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'domain' AND status = 'available' AND is_fake = %s) as domains,
(SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'ipv4' AND status = 'available' AND is_fake = %s) as ipv4s,
(SELECT COUNT(*) FROM resource_pools WHERE resource_type = 'phone' AND status = 'available' AND is_fake = %s) as phones
""", (is_fake, is_fake, is_fake))
available = cur.fetchone()
if available[0] < domain_count:
@@ -392,9 +395,9 @@ def create_license():
if domain_count > 0:
cur.execute("""
SELECT id FROM resource_pools
WHERE resource_type = 'domain' AND status = 'available' AND is_test = %s
WHERE resource_type = 'domain' AND status = 'available' AND is_fake = %s
LIMIT %s FOR UPDATE
""", (is_test, domain_count))
""", (is_fake, domain_count))
for (resource_id,) in cur.fetchall():
cur.execute("""
UPDATE resource_pools
@@ -417,9 +420,9 @@ def create_license():
if ipv4_count > 0:
cur.execute("""
SELECT id FROM resource_pools
WHERE resource_type = 'ipv4' AND status = 'available' AND is_test = %s
WHERE resource_type = 'ipv4' AND status = 'available' AND is_fake = %s
LIMIT %s FOR UPDATE
""", (is_test, ipv4_count))
""", (is_fake, ipv4_count))
for (resource_id,) in cur.fetchall():
cur.execute("""
UPDATE resource_pools
@@ -442,9 +445,9 @@ def create_license():
if phone_count > 0:
cur.execute("""
SELECT id FROM resource_pools
WHERE resource_type = 'phone' AND status = 'available' AND is_test = %s
WHERE resource_type = 'phone' AND status = 'available' AND is_fake = %s
LIMIT %s FOR UPDATE
""", (is_test, phone_count))
""", (is_fake, phone_count))
for (resource_id,) in cur.fetchall():
cur.execute("""
UPDATE resource_pools
@@ -480,7 +483,7 @@ def create_license():
'valid_from': valid_from,
'valid_until': valid_until,
'device_limit': device_limit,
'is_test': is_test
'is_fake': is_fake
})
flash(f'Lizenz {license_key} erfolgreich erstellt!', 'success')

Datei anzeigen

@@ -28,9 +28,9 @@ def resources():
resource_type = request.args.get('type', 'all')
status_filter = request.args.get('status', 'all')
search_query = request.args.get('search', '')
show_test = request.args.get('show_test', 'false') == 'true'
show_fake = request.args.get('show_fake', 'false') == 'true'
logging.info(f"Filters: type={resource_type}, status={status_filter}, search={search_query}, show_test={show_test}")
logging.info(f"Filters: type={resource_type}, status={status_filter}, search={search_query}, show_fake={show_fake}")
# Basis-Query
query = """
@@ -39,7 +39,7 @@ def resources():
rp.resource_type,
rp.resource_value,
rp.status,
rp.is_test,
rp.is_fake,
rp.allocated_to_license,
rp.created_at,
rp.status_changed_at,
@@ -67,8 +67,8 @@ def resources():
query += " AND (rp.resource_value ILIKE %s OR c.name ILIKE %s)"
params.extend([f'%{search_query}%', f'%{search_query}%'])
if not show_test:
query += " AND rp.is_test = false"
if not show_fake:
query += " AND rp.is_fake = false"
query += " ORDER BY rp.resource_type, rp.resource_value"
@@ -84,7 +84,7 @@ def resources():
'resource_type': row[1],
'resource_value': row[2],
'status': row[3],
'is_test': row[4],
'is_fake': row[4],
'allocated_to_license': row[5],
'created_at': row[6],
'status_changed_at': row[7],
@@ -98,16 +98,16 @@ def resources():
SELECT
resource_type,
status,
is_test,
is_fake,
COUNT(*) as count
FROM resource_pools
"""
# Apply test filter to statistics as well
if not show_test:
stats_query += " WHERE is_test = false"
if not show_fake:
stats_query += " WHERE is_fake = false"
stats_query += " GROUP BY resource_type, status, is_test"
stats_query += " GROUP BY resource_type, status, is_fake"
cur.execute(stats_query)
@@ -115,7 +115,7 @@ def resources():
for row in cur.fetchall():
res_type = row[0]
status = row[1]
is_test = row[2]
is_fake = row[2]
count = row[3]
if res_type not in stats:
@@ -131,7 +131,7 @@ def resources():
stats[res_type]['total'] += count
stats[res_type][status] = stats[res_type].get(status, 0) + count
if is_test:
if is_fake:
stats[res_type]['test'] += count
else:
stats[res_type]['prod'] += count
@@ -160,7 +160,7 @@ def resources():
resource_type=resource_type,
status_filter=status_filter,
search=search_query, # Changed from search_query to search
show_test=show_test,
show_fake=show_fake,
total=total,
page=page,
total_pages=total_pages,
@@ -325,7 +325,7 @@ def resource_history(resource_id):
try:
# Hole Ressourcen-Info
cur.execute("""
SELECT resource_type, resource_value, status, is_test
SELECT resource_type, resource_value, status, is_fake
FROM resource_pools WHERE id = %s
""", (resource_id,))
resource = cur.fetchone()
@@ -369,7 +369,7 @@ def resource_history(resource_id):
'type': resource[0],
'value': resource[1],
'status': resource[2],
'is_test': resource[3]
'is_fake': resource[3]
},
history=history)
@@ -395,10 +395,10 @@ def resource_metrics():
SELECT
resource_type,
status,
is_test,
is_fake,
COUNT(*) as count
FROM resource_pools
GROUP BY resource_type, status, is_test
GROUP BY resource_type, status, is_fake
ORDER BY resource_type, status
""")
@@ -539,8 +539,8 @@ def resources_report():
COUNT(CASE WHEN status = 'available' THEN 1 END) as available,
COUNT(CASE WHEN status = 'allocated' THEN 1 END) as allocated,
COUNT(CASE WHEN status = 'quarantined' THEN 1 END) as quarantined,
COUNT(CASE WHEN is_test = true THEN 1 END) as test,
COUNT(CASE WHEN is_test = false THEN 1 END) as production
COUNT(CASE WHEN is_fake = true THEN 1 END) as test,
COUNT(CASE WHEN is_fake = false THEN 1 END) as production
FROM resource_pools
GROUP BY resource_type
ORDER BY resource_type
@@ -566,7 +566,7 @@ def resources_report():
rp.resource_type,
rp.resource_value,
rp.status,
rp.is_test,
rp.is_fake,
c.name as customer_name,
l.license_key,
rp.status_changed_at,
@@ -628,7 +628,7 @@ def add_resources():
try:
resource_type = request.form.get('resource_type')
resources_text = request.form.get('resources_text', '')
is_test = request.form.get('is_test', 'false') == 'true'
is_fake = request.form.get('is_fake', 'false') == 'true'
if not resource_type or not resources_text.strip():
flash('Bitte Ressourcentyp und Ressourcen angeben!', 'error')
@@ -686,9 +686,9 @@ def add_resources():
for resource in new_resources:
cur.execute("""
INSERT INTO resource_pools
(resource_type, resource_value, status, is_test, created_by)
(resource_type, resource_value, status, is_fake, created_by)
VALUES (%s, %s, 'available', %s, %s)
""", (resource_type, resource, is_test, session['username']))
""", (resource_type, resource, is_fake, session['username']))
added_count += 1
conn.commit()
@@ -706,7 +706,7 @@ def add_resources():
if invalid_resources:
flash(f'{len(invalid_resources)} ungültige Ressourcen wurden ignoriert.', 'error')
return redirect(url_for('resources.resources', show_test=request.form.get('show_test', 'false')))
return redirect(url_for('resources.resources', show_fake=request.form.get('show_fake', 'false')))
except Exception as e:
conn.rollback()
@@ -717,5 +717,5 @@ def add_resources():
conn.close()
# GET request - show form
show_test = request.args.get('show_test', 'false') == 'true'
return render_template('add_resources.html', show_test=show_test)
show_fake = request.args.get('show_fake', 'false') == 'true'
return render_template('add_resources.html', show_fake=show_fake)

Datei anzeigen

@@ -175,9 +175,9 @@
<!-- Test Data Checkbox -->
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="isTest" name="is_test">
<label class="form-check-label" for="isTest">
<i class="fas fa-flask"></i> Als Testdaten markieren
<input class="form-check-input" type="checkbox" id="isFake" name="is_fake">
<label class="form-check-label" for="isFake">
<i class="fas fa-flask"></i> Als Fake-Daten markieren
<small class="text-muted">(wird von der Software ignoriert)</small>
</label>
</div>

Datei anzeigen

@@ -30,9 +30,9 @@
</div>
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="isTest" name="is_test">
<label class="form-check-label" for="isTest">
<i class="fas fa-flask"></i> Als Testdaten markieren
<input class="form-check-input" type="checkbox" id="isFake" name="is_fake">
<label class="form-check-label" for="isFake">
<i class="fas fa-flask"></i> Als Fake-Daten markieren
<small class="text-muted">(Kunde wird von der Software ignoriert)</small>
</label>
</div>

Datei anzeigen

@@ -5,10 +5,10 @@
{% macro sortable_header(label, field, current_sort, current_order) %}
<th>
{% if current_sort == field %}
<a href="{{ url_for('customers.customers', sort=field, order='desc' if current_order == 'asc' else 'asc', search=search, show_test=show_test, page=1) }}"
<a href="{{ url_for('customers.customers', sort=field, order='desc' if current_order == 'asc' else 'asc', search=search, show_fake=show_fake, page=1) }}"
class="server-sortable">
{% else %}
<a href="{{ url_for('customers.customers', sort=field, order='asc', search=search, show_test=show_test, page=1) }}"
<a href="{{ url_for('customers.customers', sort=field, order='asc', search=search, show_fake=show_fake, page=1) }}"
class="server-sortable">
{% endif %}
{{ label }}
@@ -41,10 +41,10 @@
</div>
<div class="col-md-2">
<div class="form-check mt-4">
<input class="form-check-input" type="checkbox" id="show_test" name="show_test" value="true"
{% if show_test %}checked{% endif %} onchange="this.form.submit()">
<label class="form-check-label" for="show_test">
🧪 Testdaten anzeigen
<input class="form-check-input" type="checkbox" id="show_fake" name="show_fake" value="true"
{% if show_fake %}checked{% endif %} onchange="this.form.submit()">
<label class="form-check-label" for="show_fake">
🧪 Fake-Daten anzeigen
</label>
</div>
</div>
@@ -126,31 +126,31 @@
<ul class="pagination justify-content-center">
<!-- Erste Seite -->
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('customers.customers', page=1, search=search, sort=sort, order=order, show_test=show_test) }}">Erste</a>
<a class="page-link" href="{{ url_for('customers.customers', page=1, search=search, sort=sort, order=order, show_fake=show_fake) }}">Erste</a>
</li>
<!-- Vorherige Seite -->
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('customers.customers', page=page-1, search=search, sort=sort, order=order, show_test=show_test) }}"></a>
<a class="page-link" href="{{ url_for('customers.customers', page=page-1, search=search, sort=sort, order=order, show_fake=show_fake) }}"></a>
</li>
<!-- Seitenzahlen -->
{% for p in range(1, total_pages + 1) %}
{% if p >= page - 2 and p <= page + 2 %}
<li class="page-item {% if p == page %}active{% endif %}">
<a class="page-link" href="{{ url_for('customers.customers', page=p, search=search, sort=sort, order=order, show_test=show_test) }}">{{ p }}</a>
<a class="page-link" href="{{ url_for('customers.customers', page=p, search=search, sort=sort, order=order, show_fake=show_fake) }}">{{ p }}</a>
</li>
{% endif %}
{% endfor %}
<!-- Nächste Seite -->
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('customers.customers', page=page+1, search=search, sort=sort, order=order, show_test=show_test) }}"></a>
<a class="page-link" href="{{ url_for('customers.customers', page=page+1, search=search, sort=sort, order=order, show_fake=show_fake) }}"></a>
</li>
<!-- Letzte Seite -->
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('customers.customers', page=total_pages, search=search, sort=sort, order=order, show_test=show_test) }}">Letzte</a>
<a class="page-link" href="{{ url_for('customers.customers', page=total_pages, search=search, sort=sort, order=order, show_fake=show_fake) }}">Letzte</a>
</li>
</ul>
<p class="text-center text-muted">

Datei anzeigen

@@ -17,14 +17,14 @@
<i class="bi bi-download"></i> Export
</button>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="{{ url_for('export.export_customers', format='excel', include_test=request.args.get('show_test')) }}">
<li><a class="dropdown-item" href="{{ url_for('export.export_customers', format='excel', include_test=request.args.get('show_fake')) }}">
<i class="bi bi-file-earmark-excel text-success"></i> Kunden (Excel)</a></li>
<li><a class="dropdown-item" href="{{ url_for('export.export_customers', format='csv', include_test=request.args.get('show_test')) }}">
<li><a class="dropdown-item" href="{{ url_for('export.export_customers', format='csv', include_test=request.args.get('show_fake')) }}">
<i class="bi bi-file-earmark-text"></i> Kunden (CSV)</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="{{ url_for('export.export_licenses', format='excel', include_test=request.args.get('show_test')) }}">
<li><a class="dropdown-item" href="{{ url_for('export.export_licenses', format='excel', include_test=request.args.get('show_fake')) }}">
<i class="bi bi-file-earmark-excel text-success"></i> Lizenzen (Excel)</a></li>
<li><a class="dropdown-item" href="{{ url_for('export.export_licenses', format='csv', include_test=request.args.get('show_test')) }}">
<li><a class="dropdown-item" href="{{ url_for('export.export_licenses', format='csv', include_test=request.args.get('show_fake')) }}">
<i class="bi bi-file-earmark-text"></i> Lizenzen (CSV)</a></li>
</ul>
</div>
@@ -47,11 +47,11 @@
<input type="text" class="form-control mb-2" id="customerSearch"
placeholder="Kunde suchen..." autocomplete="off">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="showTestCustomers"
{% if request.args.get('show_test', 'false').lower() == 'true' %}checked{% endif %}
onchange="toggleTestCustomers()">
<label class="form-check-label" for="showTestCustomers">
<small class="text-muted">Testkunden anzeigen</small>
<input class="form-check-input" type="checkbox" id="showFakeCustomers"
{% if request.args.get('show_fake', 'false').lower() == 'true' %}checked{% endif %}
onchange="toggleFakeCustomers()">
<label class="form-check-label" for="showFakeCustomers">
<small class="text-muted">Fake-Kunden anzeigen</small>
</label>
</div>
</div>
@@ -289,7 +289,7 @@ function loadCustomerLicenses(customerId) {
});
document.querySelector(`[data-customer-id="${customerId}"]`).classList.add('active');
// URL aktualisieren ohne Reload (behalte show_test Parameter)
// URL aktualisieren ohne Reload (behalte show_fake Parameter)
const currentUrl = new URL(window.location);
currentUrl.searchParams.set('customer_id', customerId);
window.history.pushState({}, '', currentUrl.toString());
@@ -522,10 +522,10 @@ function copyToClipboard(text) {
}
// Toggle Testkunden
function toggleTestCustomers() {
const showTest = document.getElementById('showTestCustomers').checked;
function toggleFakeCustomers() {
const showTest = document.getElementById('showFakeCustomers').checked;
const currentUrl = new URL(window.location);
currentUrl.searchParams.set('show_test', showTest);
currentUrl.searchParams.set('show_fake', showTest);
window.location.href = currentUrl.toString();
}
@@ -854,12 +854,12 @@ function quarantineResource(resourceId, resourceValue) {
// Lade verfügbare Ressourcen
function loadAvailableResources(licenseId) {
// Hole show_test Parameter aus der URL
// Hole show_fake Parameter aus der URL
const urlParams = new URLSearchParams(window.location.search);
const showTest = urlParams.get('show_test') === 'true';
const showTest = urlParams.get('show_fake') === 'true';
// Lade verfügbare Domains
fetch(`/api/resources/check-availability?type=domain&count=200&show_test=${showTest}`)
fetch(`/api/resources/check-availability?type=domain&count=200&show_fake=${showTest}`)
.then(response => response.json())
.then(data => {
const select = document.getElementById('availableDomains');
@@ -880,7 +880,7 @@ function loadAvailableResources(licenseId) {
});
// Lade verfügbare IPv4s
fetch(`/api/resources/check-availability?type=ipv4&count=200&show_test=${showTest}`)
fetch(`/api/resources/check-availability?type=ipv4&count=200&show_fake=${showTest}`)
.then(response => response.json())
.then(data => {
const select = document.getElementById('availableIpv4s');
@@ -901,7 +901,7 @@ function loadAvailableResources(licenseId) {
});
// Lade verfügbare Telefonnummern
fetch(`/api/resources/check-availability?type=phone&count=200&show_test=${showTest}`)
fetch(`/api/resources/check-availability?type=phone&count=200&show_fake=${showTest}`)
.then(response => response.json())
.then(data => {
const select = document.getElementById('availablePhones');

Datei anzeigen

@@ -116,17 +116,17 @@
<p class="text-muted">Vollversionen</p>
</div>
<div class="col-6 text-center">
<h3 class="text-warning">{{ stats.test_licenses }}</h3>
<h3 class="text-warning">{{ stats.fake_licenses }}</h3>
<p class="text-muted">Testversionen</p>
</div>
</div>
{% if stats.test_data_count > 0 or stats.test_customers_count > 0 or stats.test_resources_count > 0 %}
{% if stats.fake_data_count > 0 or stats.fake_customers_count > 0 or stats.fake_resources_count > 0 %}
<div class="alert alert-info mt-3 mb-0">
<small>
<i class="fas fa-flask"></i> Testdaten:
{{ stats.test_data_count }} Lizenzen,
{{ stats.test_customers_count }} Kunden,
{{ stats.test_resources_count }} Ressourcen
<i class="fas fa-flask"></i> Fake-Daten:
{{ stats.fake_data_count }} Lizenzen,
{{ stats.fake_customers_count }} Kunden,
{{ stats.fake_resources_count }} Ressourcen
</small>
</div>
{% endif %}

Datei anzeigen

@@ -7,15 +7,15 @@
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Kunde bearbeiten</h2>
<div>
<a href="{{ url_for('customers.customers_licenses', show_test=request.args.get('show_test')) }}" class="btn btn-secondary">👥 Zurück zur Übersicht</a>
<a href="{{ url_for('customers.customers_licenses', show_fake=request.args.get('show_fake')) }}" class="btn btn-secondary">👥 Zurück zur Übersicht</a>
</div>
</div>
<div class="card mb-4">
<div class="card-body">
<form method="post" action="{{ url_for('customers.edit_customer', customer_id=customer.id) }}" accept-charset="UTF-8">
{% if request.args.get('show_test') == 'true' %}
<input type="hidden" name="show_test" value="true">
{% if request.args.get('show_fake') == 'true' %}
<input type="hidden" name="show_fake" value="true">
{% endif %}
<div class="row g-3">
<div class="col-md-6">
@@ -33,16 +33,16 @@
</div>
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="isTest" name="is_test" {% if customer.is_test %}checked{% endif %}>
<input class="form-check-input" type="checkbox" id="isTest" name="is_fake" {% if customer.is_fake %}checked{% endif %}>
<label class="form-check-label" for="isTest">
<i class="fas fa-flask"></i> Als Testdaten markieren
<i class="fas fa-flask"></i> Als Fake-Daten markieren
<small class="text-muted">(Kunde und seine Lizenzen werden von der Software ignoriert)</small>
</label>
</div>
<div class="mt-4">
<button type="submit" class="btn btn-primary">💾 Änderungen speichern</button>
<a href="{{ url_for('customers.customers_licenses', show_test=request.args.get('show_test')) }}" class="btn btn-secondary">Abbrechen</a>
<a href="{{ url_for('customers.customers_licenses', show_fake=request.args.get('show_fake')) }}" class="btn btn-secondary">Abbrechen</a>
</div>
</form>
</div>

Datei anzeigen

@@ -7,15 +7,15 @@
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Lizenz bearbeiten</h2>
<div>
<a href="{{ url_for('customers.customers_licenses', show_test=request.args.get('show_test')) }}" class="btn btn-secondary">📋 Zurück zur Übersicht</a>
<a href="{{ url_for('customers.customers_licenses', show_fake=request.args.get('show_fake')) }}" class="btn btn-secondary">📋 Zurück zur Übersicht</a>
</div>
</div>
<div class="card">
<div class="card-body">
<form method="post" action="{{ url_for('licenses.edit_license', license_id=license.id) }}" accept-charset="UTF-8">
{% if request.args.get('show_test') == 'true' %}
<input type="hidden" name="show_test" value="true">
{% if request.args.get('show_fake') == 'true' %}
<input type="hidden" name="show_fake" value="true">
{% endif %}
<div class="row g-3">
<div class="col-md-6">
@@ -66,16 +66,16 @@
</div>
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="isTest" name="is_test" {% if license.is_test %}checked{% endif %}>
<input class="form-check-input" type="checkbox" id="isTest" name="is_fake" {% if license.is_fake %}checked{% endif %}>
<label class="form-check-label" for="isTest">
<i class="fas fa-flask"></i> Als Testdaten markieren
<i class="fas fa-flask"></i> Als Fake-Daten markieren
<small class="text-muted">(wird von der Software ignoriert)</small>
</label>
</div>
<div class="mt-4">
<button type="submit" class="btn btn-primary">💾 Änderungen speichern</button>
<a href="{{ url_for('customers.customers_licenses', show_test=request.args.get('show_test')) }}" class="btn btn-secondary">Abbrechen</a>
<a href="{{ url_for('customers.customers_licenses', show_fake=request.args.get('show_fake')) }}" class="btn btn-secondary">Abbrechen</a>
</div>
</form>
</div>

Datei anzeigen

@@ -154,9 +154,9 @@
<!-- Test Data Checkbox -->
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="isTest" name="is_test">
<input class="form-check-input" type="checkbox" id="isTest" name="is_fake">
<label class="form-check-label" for="isTest">
<i class="fas fa-flask"></i> Als Testdaten markieren
<i class="fas fa-flask"></i> Als Fake-Daten markieren
<small class="text-muted">(wird von der Software ignoriert)</small>
</label>
</div>

Datei anzeigen

@@ -4,13 +4,20 @@
{% macro sortable_header(label, field, current_sort, current_order) %}
<th>
{% set base_url = url_for('licenses.licenses') %}
{% set params = [] %}
{% if search %}{% set _ = params.append('search=' + search|urlencode) %}{% endif %}
{% for type in filter_types %}{% set _ = params.append('types[]=' + type|urlencode) %}{% endfor %}
{% for status in filter_statuses %}{% set _ = params.append('statuses[]=' + status|urlencode) %}{% endfor %}
{% set _ = params.append('sort=' + field) %}
{% if current_sort == field %}
<a href="{{ url_for('licenses.licenses', sort=field, order='desc' if current_order == 'asc' else 'asc', search=search, type=filter_type, status=filter_status, page=1) }}"
class="server-sortable">
{% set _ = params.append('order=' + ('desc' if current_order == 'asc' else 'asc')) %}
{% else %}
<a href="{{ url_for('licenses.licenses', sort=field, order='asc', search=search, type=filter_type, status=filter_status, page=1) }}"
class="server-sortable">
{% set _ = params.append('order=asc') %}
{% endif %}
{% set _ = params.append('page=1') %}
<a href="{{ base_url }}?{{ params|join('&') }}" class="server-sortable">
{{ label }}
<span class="sort-indicator{% if current_sort == field %} active{% endif %}">
{% if current_sort == field %}
@@ -24,6 +31,46 @@
{% endmacro %}
{% block extra_css %}
<style>
.filter-group {
background-color: #f8f9fa;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
}
.filter-group h6 {
color: #495057;
font-weight: 600;
}
.badge.bg-info {
background-color: #0dcaf0 !important;
font-size: 0.75rem;
}
.active-filters .badge {
font-size: 0.875rem;
padding: 0.35em 0.65em;
}
.active-filters a {
text-decoration: none;
opacity: 0.8;
}
.active-filters a:hover {
opacity: 1;
}
#advancedFilters {
transition: all 0.3s ease;
}
.btn-sm {
padding: 0.375rem 0.75rem;
}
</style>
{% endblock %}
{% block content %}
@@ -36,46 +83,162 @@
<div class="card mb-3">
<div class="card-body">
<form method="get" action="{{ url_for('licenses.licenses') }}" id="filterForm">
<div class="row g-3 align-items-end">
<div class="col-md-4">
<!-- Suchfeld -->
<div class="row g-3 mb-3">
<div class="col-md-8">
<label for="search" class="form-label">🔍 Suchen</label>
<input type="text" class="form-control" id="search" name="search"
placeholder="Lizenzschlüssel, Kunde, E-Mail..."
value="{{ search }}">
</div>
<div class="col-md-2">
<label for="type" class="form-label">Typ</label>
<select class="form-select" id="type" name="type">
<option value="">Alle Typen</option>
<option value="full" {% if filter_type == 'full' %}selected{% endif %}>Vollversion</option>
<option value="test" {% if filter_type == 'test' %}selected{% endif %}>Testversion</option>
<option value="test_data" {% if filter_type == 'test_data' %}selected{% endif %}>🧪 Testdaten</option>
<option value="live_data" {% if filter_type == 'live_data' %}selected{% endif %}>🚀 Live-Daten</option>
</select>
</div>
<div class="col-md-3">
<label for="status" class="form-label">Status</label>
<select class="form-select" id="status" name="status">
<option value="">Alle Status</option>
<option value="active" {% if filter_status == 'active' %}selected{% endif %}>✅ Aktiv</option>
<option value="expiring" {% if filter_status == 'expiring' %}selected{% endif %}>⏰ Läuft bald ab</option>
<option value="expired" {% if filter_status == 'expired' %}selected{% endif %}>⚠️ Abgelaufen</option>
<option value="inactive" {% if filter_status == 'inactive' %}selected{% endif %}>❌ Deaktiviert</option>
</select>
</div>
<div class="col-md-3">
<a href="{{ url_for('licenses.licenses') }}" class="btn btn-outline-secondary">Zurücksetzen</a>
<div class="col-md-4 text-end">
<label class="form-label d-block">&nbsp;</label>
<button type="button" class="btn btn-outline-primary me-2" onclick="toggleFilters()">
<i class="bi bi-funnel"></i> Filter
</button>
<a href="{{ url_for('licenses.licenses') }}" class="btn btn-outline-secondary">
<i class="bi bi-arrow-clockwise"></i> Zurücksetzen
</a>
</div>
</div>
<!-- Erweiterter Filterbereich -->
<div id="advancedFilters" class="collapse {{ 'show' if filter_types or filter_status else '' }}">
<hr class="my-3">
<!-- Lizenztyp Filter (OR Logic) -->
<div class="filter-group mb-3">
<div class="d-flex align-items-center mb-2">
<h6 class="mb-0 me-2">Lizenztyp</h6>
<span class="badge bg-info">ODER</span>
</div>
<div class="row g-2">
<div class="col-auto">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="types[]" value="full" id="typeFullversion"
{% if 'full' in (filter_types or []) %}checked{% endif %}>
<label class="form-check-label" for="typeFullversion">
<span class="badge bg-success">Vollversion</span>
</label>
</div>
</div>
<div class="col-auto">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="types[]" value="test" id="typeTestversion"
{% if 'test' in (filter_types or []) %}checked{% endif %}>
<label class="form-check-label" for="typeTestversion">
<span class="badge bg-warning">Testversion</span>
</label>
</div>
</div>
</div>
</div>
<!-- Status Filter (OR Logic) -->
<div class="filter-group mb-3">
<div class="d-flex align-items-center mb-2">
<h6 class="mb-0 me-2">Status</h6>
<span class="badge bg-info">ODER</span>
</div>
<div class="row g-2">
<div class="col-auto">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="statuses[]" value="active" id="statusActive"
{% if 'active' in (filter_statuses or []) %}checked{% endif %}>
<label class="form-check-label" for="statusActive">
✅ Aktiv
</label>
</div>
</div>
<div class="col-auto">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="statuses[]" value="expiring" id="statusExpiring"
{% if 'expiring' in (filter_statuses or []) %}checked{% endif %}>
<label class="form-check-label" for="statusExpiring">
⏰ Läuft bald ab
</label>
</div>
</div>
<div class="col-auto">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="statuses[]" value="expired" id="statusExpired"
{% if 'expired' in (filter_statuses or []) %}checked{% endif %}>
<label class="form-check-label" for="statusExpired">
⚠️ Abgelaufen
</label>
</div>
</div>
<div class="col-auto">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="statuses[]" value="inactive" id="statusInactive"
{% if 'inactive' in (filter_statuses or []) %}checked{% endif %}>
<label class="form-check-label" for="statusInactive">
❌ Deaktiviert
</label>
</div>
</div>
</div>
</div>
<!-- Quick Filters / Presets -->
<div class="filter-group">
<h6 class="mb-2">Schnellfilter</h6>
<div class="row g-2">
<div class="col-auto">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="applyPreset('expiring_soon')">
<i class="bi bi-clock-history"></i> Läuft in 7 Tagen ab
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="applyPreset('all_test')">
<i class="bi bi-bug"></i> Alle Testversionen
</button>
</div>
<div class="col-auto">
<button type="button" class="btn btn-sm btn-outline-primary" onclick="applyPreset('active_full')">
<i class="bi bi-shield-check"></i> Aktive Vollversionen
</button>
</div>
</div>
</div>
</div>
<!-- Hidden fields for sorting -->
<input type="hidden" name="sort" value="{{ sort }}">
<input type="hidden" name="order" value="{{ order }}">
</form>
{% if search or filter_type or filter_status %}
{% if search or filter_types or filter_statuses %}
<div class="mt-2">
<small class="text-muted">
Gefiltert: {{ total }} Ergebnisse
{% if search %} | Suche: <strong>{{ search }}</strong>{% endif %}
{% if filter_type %} | Typ: <strong>{{ 'Vollversion' if filter_type == 'full' else 'Testversion' }}</strong>{% endif %}
{% if filter_status %} | Status: <strong>{{ filter_status }}</strong>{% endif %}
</small>
<div class="d-flex align-items-center justify-content-between">
<small class="text-muted">
Gefiltert: {{ total }} Ergebnisse
</small>
<div class="active-filters">
{% if search %}
<span class="badge bg-secondary me-1">
<i class="bi bi-search"></i> {{ search }}
{% set clear_search_params = [] %}
{% for type in filter_types %}{% set _ = clear_search_params.append('types[]=' + type|urlencode) %}{% endfor %}
{% for status in filter_statuses %}{% set _ = clear_search_params.append('statuses[]=' + status|urlencode) %}{% endfor %}
{% set _ = clear_search_params.append('sort=' + sort) %}
{% set _ = clear_search_params.append('order=' + order) %}
<a href="{{ url_for('licenses.licenses') }}?{{ clear_search_params|join('&') }}" class="text-white ms-1">&times;</a>
</span>
{% endif %}
{% for type in (filter_types or []) %}
<span class="badge bg-primary me-1">
{{ 'Vollversion' if type == 'full' else 'Testversion' }}
<a href="#" onclick="removeFilter('type', '{{ type }}')" class="text-white ms-1">&times;</a>
</span>
{% endfor %}
{% for status in (filter_statuses or []) %}
<span class="badge bg-info me-1">
{% if status == 'active' %}✅ Aktiv{% elif status == 'expiring' %}⏰ Läuft bald ab{% elif status == 'expired' %}⚠️ Abgelaufen{% else %}❌ Deaktiviert{% endif %}
<a href="#" onclick="removeFilter('status', '{{ status }}')" class="text-white ms-1">&times;</a>
</span>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
@@ -119,8 +282,8 @@
</td>
<td>
{{ license.customer_name }}
{% if license.is_test %}
<span class="badge bg-secondary ms-1" title="Testdaten">🧪</span>
{% if license.is_fake %}
<span class="badge bg-secondary ms-1" title="Fake-Daten">🧪</span>
{% endif %}
</td>
<td>-</td>
@@ -182,33 +345,41 @@
{% if total_pages > 1 %}
<nav aria-label="Seitennavigation" class="mt-3">
<ul class="pagination justify-content-center">
{% set base_url = url_for('licenses.licenses') %}
{% set base_params = [] %}
{% if search %}{% set _ = base_params.append('search=' + search|urlencode) %}{% endif %}
{% for type in filter_types %}{% set _ = base_params.append('types[]=' + type|urlencode) %}{% endfor %}
{% for status in filter_statuses %}{% set _ = base_params.append('statuses[]=' + status|urlencode) %}{% endfor %}
{% set _ = base_params.append('sort=' + sort) %}
{% set _ = base_params.append('order=' + order) %}
<!-- Erste Seite -->
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('licenses.licenses', page=1, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}">Erste</a>
<a class="page-link" href="{{ base_url }}?{{ base_params|join('&') }}&page=1">Erste</a>
</li>
<!-- Vorherige Seite -->
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('licenses.licenses', page=page-1, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}"></a>
<a class="page-link" href="{{ base_url }}?{{ base_params|join('&') }}&page={{ page-1 }}"></a>
</li>
<!-- Seitenzahlen -->
{% for p in range(1, total_pages + 1) %}
{% if p >= page - 2 and p <= page + 2 %}
<li class="page-item {% if p == page %}active{% endif %}">
<a class="page-link" href="{{ url_for('licenses.licenses', page=p, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}">{{ p }}</a>
<a class="page-link" href="{{ base_url }}?{{ base_params|join('&') }}&page={{ p }}">{{ p }}</a>
</li>
{% endif %}
{% endfor %}
<!-- Nächste Seite -->
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('licenses.licenses', page=page+1, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}"></a>
<a class="page-link" href="{{ base_url }}?{{ base_params|join('&') }}&page={{ page+1 }}"></a>
</li>
<!-- Letzte Seite -->
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link" href="{{ url_for('licenses.licenses', page=total_pages, search=search, type=filter_type, status=filter_status, sort=sort, order=order) }}">Letzte</a>
<a class="page-link" href="{{ base_url }}?{{ base_params|join('&') }}&page={{ total_pages }}">Letzte</a>
</li>
</ul>
<p class="text-center text-muted">
@@ -235,12 +406,58 @@
{% block extra_js %}
<script>
// Toggle Filter Panel
function toggleFilters() {
const filtersDiv = document.getElementById('advancedFilters');
const bsCollapse = new bootstrap.Collapse(filtersDiv, {
toggle: true
});
}
// Apply Preset Filters
function applyPreset(preset) {
const form = document.getElementById('filterForm');
// Clear existing filters
document.querySelectorAll('input[name="types[]"]').forEach(cb => cb.checked = false);
document.querySelectorAll('input[name="statuses[]"]').forEach(cb => cb.checked = false);
switch(preset) {
case 'expiring_soon':
document.getElementById('statusExpiring').checked = true;
break;
case 'all_test':
document.getElementById('typeTestversion').checked = true;
break;
case 'active_full':
document.getElementById('typeFullversion').checked = true;
document.getElementById('statusActive').checked = true;
break;
}
form.submit();
}
// Remove Individual Filter
function removeFilter(filterType, value) {
const form = document.getElementById('filterForm');
if (filterType === 'type') {
const checkbox = document.querySelector(`input[name="types[]"][value="${value}"]`);
if (checkbox) checkbox.checked = false;
} else if (filterType === 'status') {
const checkbox = document.querySelector(`input[name="statuses[]"][value="${value}"]`);
if (checkbox) checkbox.checked = false;
}
form.submit();
return false;
}
// Live Filtering
document.addEventListener('DOMContentLoaded', function() {
const filterForm = document.getElementById('filterForm');
const searchInput = document.getElementById('search');
const typeSelect = document.getElementById('type');
const statusSelect = document.getElementById('status');
// Debounce timer für Suchfeld
let searchTimeout;
@@ -253,13 +470,11 @@ document.addEventListener('DOMContentLoaded', function() {
}, 300);
});
// Live-Filter für Dropdowns (sofort)
typeSelect.addEventListener('change', function() {
filterForm.submit();
});
statusSelect.addEventListener('change', function() {
filterForm.submit();
// Live-Filter für Checkboxen
document.querySelectorAll('input[name="types[]"], input[name="statuses[]"]').forEach(checkbox => {
checkbox.addEventListener('change', function() {
filterForm.submit();
});
});
});

Datei anzeigen

@@ -225,7 +225,7 @@
<div class="card-body py-2">
<div class="form-check mb-0">
<input class="form-check-input" type="checkbox" id="showTestResources"
{% if show_test %}checked{% endif %}
{% if show_fake %}checked{% endif %}
onchange="toggleTestResources()">
<label class="form-check-label" for="showTestResources">
Testressourcen anzeigen
@@ -293,7 +293,7 @@
<div class="card filter-card mb-4">
<div class="card-body">
<form method="get" action="{{ url_for('resources.resources') }}" id="filterForm">
<input type="hidden" name="show_test" value="{{ 'true' if show_test else 'false' }}">
<input type="hidden" name="show_fake" value="{{ 'true' if show_fake else 'false' }}">
<div class="row g-3">
<div class="col-md-3">
<label for="type" class="form-label">🏷️ Typ</label>
@@ -320,7 +320,7 @@
</div>
<div class="col-md-2">
<label class="form-label">&nbsp;</label>
<a href="{{ url_for('resources.resources', show_test=show_test) }}" class="btn btn-secondary w-100">
<a href="{{ url_for('resources.resources', show_fake=show_fake) }}" class="btn btn-secondary w-100">
🔄 Zurücksetzen
</a>
</div>
@@ -344,7 +344,7 @@
<i class="bi bi-download"></i> Export
</button>
<ul class="dropdown-menu" aria-labelledby="exportDropdown">
<li><a class="dropdown-item" href="{{ url_for('resources.resources_report', format='excel', type=resource_type, status=status_filter, search=search, show_test=show_test) }}">
<li><a class="dropdown-item" href="{{ url_for('resources.resources_report', format='excel', type=resource_type, status=status_filter, search=search, show_fake=show_fake) }}">
<i class="bi bi-file-earmark-excel text-success"></i> Excel Export</a></li>
</ul>
</div>
@@ -359,7 +359,7 @@
<thead>
<tr>
<th width="80">
<a href="{{ url_for('resources.resources', sort='id', order='desc' if sort_by == 'id' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
<a href="{{ url_for('resources.resources', sort='id', order='desc' if sort_by == 'id' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_fake=show_fake) }}"
class="text-decoration-none text-dark sort-link">
ID
{% if sort_by == 'id' %}
@@ -370,7 +370,7 @@
</a>
</th>
<th width="120">
<a href="{{ url_for('resources.resources', sort='type', order='desc' if sort_by == 'type' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
<a href="{{ url_for('resources.resources', sort='type', order='desc' if sort_by == 'type' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_fake=show_fake) }}"
class="text-decoration-none text-dark sort-link">
Typ
{% if sort_by == 'type' %}
@@ -381,7 +381,7 @@
</a>
</th>
<th>
<a href="{{ url_for('resources.resources', sort='resource', order='desc' if sort_by == 'resource' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
<a href="{{ url_for('resources.resources', sort='resource', order='desc' if sort_by == 'resource' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_fake=show_fake) }}"
class="text-decoration-none text-dark sort-link">
Ressource
{% if sort_by == 'resource' %}
@@ -392,7 +392,7 @@
</a>
</th>
<th width="140">
<a href="{{ url_for('resources.resources', sort='status', order='desc' if sort_by == 'status' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
<a href="{{ url_for('resources.resources', sort='status', order='desc' if sort_by == 'status' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_fake=show_fake) }}"
class="text-decoration-none text-dark sort-link">
Status
{% if sort_by == 'status' %}
@@ -403,7 +403,7 @@
</a>
</th>
<th>
<a href="{{ url_for('resources.resources', sort='assigned', order='desc' if sort_by == 'assigned' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
<a href="{{ url_for('resources.resources', sort='assigned', order='desc' if sort_by == 'assigned' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_fake=show_fake) }}"
class="text-decoration-none text-dark sort-link">
Zugewiesen an
{% if sort_by == 'assigned' %}
@@ -414,7 +414,7 @@
</a>
</th>
<th width="180">
<a href="{{ url_for('resources.resources', sort='changed', order='desc' if sort_by == 'changed' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_test=show_test) }}"
<a href="{{ url_for('resources.resources', sort='changed', order='desc' if sort_by == 'changed' and sort_order == 'asc' else 'asc', type=resource_type, status=status_filter, search=search, show_fake=show_fake) }}"
class="text-decoration-none text-dark sort-link">
Letzte Änderung
{% if sort_by == 'changed' %}
@@ -474,13 +474,13 @@
<td>
{% if resource.customer_name %}
<div>
<a href="{{ url_for('customers.customers_licenses', show_test=show_test) }}"
<a href="{{ url_for('customers.customers_licenses', show_fake=show_fake) }}"
class="text-decoration-none">
<strong>{{ resource.customer_name }}</strong>
</a>
</div>
<div class="small text-muted">
<a href="{{ url_for('licenses.edit_license', license_id=resource.allocated_to_license) }}?ref=resources{{ '&show_test=true' if show_test else '' }}"
<a href="{{ url_for('licenses.edit_license', license_id=resource.allocated_to_license) }}?ref=resources{{ '&show_fake=true' if show_fake else '' }}"
class="text-decoration-none text-muted">
{{ resource.allocated_to_license }}
</a>
@@ -502,10 +502,10 @@
<td class="text-center">
{% if resource.status == 'quarantine' %}
<!-- Quick Action für Quarantäne -->
<form method="post" action="{{ url_for('resources.release', show_test=show_test, type=resource_type, status=status_filter, search=search) }}"
<form method="post" action="{{ url_for('resources.release', show_fake=show_fake, type=resource_type, status=status_filter, search=search) }}"
style="display: inline-block; margin-right: 5px;">
<input type="hidden" name="resource_ids" value="{{ resource.id }}">
<input type="hidden" name="show_test" value="{{ show_test }}">
<input type="hidden" name="show_fake" value="{{ show_fake }}">
<button type="submit"
class="btn btn-sm btn-success">
<i class="bi bi-check-circle"></i> Freigeben
@@ -546,7 +546,7 @@
{% if resource.allocated_to_license %}
<li>
<a class="dropdown-item"
href="{{ url_for('licenses.edit_license', license_id=resource.allocated_to_license) }}?ref=resources{{ '&show_test=true' if show_test else '' }}">
href="{{ url_for('licenses.edit_license', license_id=resource.allocated_to_license) }}?ref=resources{{ '&show_fake=true' if show_fake else '' }}">
<i class="bi bi-file-text text-primary"></i> Lizenz bearbeiten
</a>
</li>
@@ -554,7 +554,7 @@
{% if resource.id %}
<li>
<a class="dropdown-item"
href="{{ url_for('customers.customers_licenses', customer_id=resource.id, show_test=show_test) }}">
href="{{ url_for('customers.customers_licenses', customer_id=resource.id, show_fake=show_fake) }}">
<i class="bi bi-person text-primary"></i> Kunde anzeigen
</a>
</li>
@@ -562,10 +562,10 @@
{% elif resource.status == 'quarantine' %}
<!-- Aktionen für Quarantäne-Ressourcen -->
<li>
<form method="post" action="{{ url_for('resources.release', show_test=show_test, type=resource_type, status=status_filter, search=search) }}"
<form method="post" action="{{ url_for('resources.release', show_fake=show_fake, type=resource_type, status=status_filter, search=search) }}"
style="display: contents;">
<input type="hidden" name="resource_ids" value="{{ resource.id }}">
<input type="hidden" name="show_test" value="{{ show_test }}">
<input type="hidden" name="show_fake" value="{{ show_fake }}">
<button type="submit" class="dropdown-item">
<i class="bi bi-check-circle text-success"></i> Ressource freigeben
</button>
@@ -614,13 +614,13 @@
<ul class="pagination justify-content-center">
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link"
href="{{ url_for('resources.resources', page=1, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
href="{{ url_for('resources.resources', page=1, type=resource_type, status=status_filter, search=search, show_fake=show_fake, sort=sort_by, order=sort_order) }}">
<i class="bi bi-chevron-double-left"></i> Erste
</a>
</li>
<li class="page-item {% if page == 1 %}disabled{% endif %}">
<a class="page-link"
href="{{ url_for('resources.resources', page=page-1, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
href="{{ url_for('resources.resources', page=page-1, type=resource_type, status=status_filter, search=search, show_fake=show_fake, sort=sort_by, order=sort_order) }}">
<i class="bi bi-chevron-left"></i> Zurück
</a>
</li>
@@ -629,7 +629,7 @@
{% if p == page or (p >= page - 2 and p <= page + 2) %}
<li class="page-item {% if p == page %}active{% endif %}">
<a class="page-link"
href="{{ url_for('resources.resources', page=p, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
href="{{ url_for('resources.resources', page=p, type=resource_type, status=status_filter, search=search, show_fake=show_fake, sort=sort_by, order=sort_order) }}">
{{ p }}
</a>
</li>
@@ -638,13 +638,13 @@
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link"
href="{{ url_for('resources.resources', page=page+1, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
href="{{ url_for('resources.resources', page=page+1, type=resource_type, status=status_filter, search=search, show_fake=show_fake, sort=sort_by, order=sort_order) }}">
Weiter <i class="bi bi-chevron-right"></i>
</a>
</li>
<li class="page-item {% if page == total_pages %}disabled{% endif %}">
<a class="page-link"
href="{{ url_for('resources.resources', page=total_pages, type=resource_type, status=status_filter, search=search, show_test=show_test, sort=sort_by, order=sort_order) }}">
href="{{ url_for('resources.resources', page=total_pages, type=resource_type, status=status_filter, search=search, show_fake=show_fake, sort=sort_by, order=sort_order) }}">
Letzte <i class="bi bi-chevron-double-right"></i>
</a>
</li>
@@ -702,7 +702,7 @@
<div class="modal-content">
<form method="post" id="quarantineForm">
<!-- Filter-Parameter als Hidden Fields -->
<input type="hidden" name="show_test" value="{{ show_test }}">
<input type="hidden" name="show_fake" value="{{ show_fake }}">
<input type="hidden" name="type" value="{{ resource_type }}">
<input type="hidden" name="status" value="{{ status_filter }}">
<input type="hidden" name="search" value="{{ search }}">
@@ -852,7 +852,7 @@ function showQuarantineModal(resourceId) {
function toggleTestResources() {
const showTest = document.getElementById('showTestResources').checked;
const currentUrl = new URL(window.location);
currentUrl.searchParams.set('show_test', showTest);
currentUrl.searchParams.set('show_fake', showTest);
window.location.href = currentUrl.toString();
}

Datei anzeigen

@@ -63,7 +63,7 @@ def prepare_license_export_data(licenses):
format_datetime_for_export(license[8]), # Created At
license[9], # Device Limit
license[10] or 0, # Current Devices
'Test' if license[11] else 'Full' # Is Test License
'Fake' if license[11] else 'Full' # Is Test License
])
return export_data
@@ -141,7 +141,7 @@ def create_batch_export(licenses):
'Gültig von': format_datetime_for_export(license.get('valid_from')),
'Gültig bis': format_datetime_for_export(license.get('valid_until')),
'Status': 'Aktiv' if license.get('is_active', True) else 'Inaktiv',
'Test-Lizenz': 'Ja' if license.get('is_test', False) else 'Nein'
'Fake-Lizenz': 'Ja' if license.get('is_test', False) else 'Nein'
})
df = pd.DataFrame(export_data)