From 65b7e0d96515008776c84f634454ac5bd79e7148 Mon Sep 17 00:00:00 2001 From: Alexander Borg Date: Sat, 13 Sep 2025 12:04:02 +0200 Subject: [PATCH] Fix jwt-token --- management/src/components/TenantModal.jsx | 614 ++++++++++++++++++++++ management/src/pages/Tenants.jsx | 77 +-- 2 files changed, 660 insertions(+), 31 deletions(-) create mode 100644 management/src/components/TenantModal.jsx diff --git a/management/src/components/TenantModal.jsx b/management/src/components/TenantModal.jsx new file mode 100644 index 0000000..686ad43 --- /dev/null +++ b/management/src/components/TenantModal.jsx @@ -0,0 +1,614 @@ +import React, { useState, useEffect } from 'react' +import { XMarkIcon, EyeIcon, EyeSlashIcon } from '@heroicons/react/24/outline' +import toast from 'react-hot-toast' + +const TenantModal = ({ isOpen, onClose, tenant = null, onSave }) => { + const [formData, setFormData] = useState({ + name: '', + slug: '', + domain: '', + subscription_type: 'basic', + auth_provider: 'local', + is_active: true, + admin_email: '', + admin_phone: '', + billing_email: '', + auth_config: { + // Local auth config + allow_registration: true, + require_email_verification: false, + password_policy: { + min_length: 8, + require_uppercase: false, + require_lowercase: false, + require_numbers: false, + require_symbols: false + }, + // SAML config + sso_url: '', + certificate: '', + issuer: '', + logout_url: '', + // OAuth config + client_id: '', + client_secret: '', + authorization_url: '', + token_url: '', + userinfo_url: '', + scopes: ['openid', 'profile', 'email'], + // LDAP config + url: '', + base_dn: '', + bind_dn: '', + bind_password: '', + user_search_filter: '(sAMAccountName={username})', + domain_name: '' + }, + user_mapping: { + username: ['preferred_username', 'username', 'user'], + email: ['email', 'mail'], + firstName: ['given_name', 'givenName', 'first_name'], + lastName: ['family_name', 'surname', 'last_name'], + phoneNumber: ['phone_number', 'phone'] + }, + role_mapping: { + admin: ['admin', 'administrator', 'Domain Admins'], + operator: ['operator', 'user', 'Users'], + viewer: ['viewer', 'guest', 'read-only'], + default: 'viewer' + }, + features: { + max_devices: 50, + max_users: 10, + api_rate_limit: 5000, + data_retention_days: 365, + features: ['basic_detection', 'alerts', 'dashboard'] + }, + branding: { + logo_url: '', + primary_color: '#3B82F6', + secondary_color: '#1F2937', + company_name: '' + } + }) + + const [showSecrets, setShowSecrets] = useState(false) + const [loading, setLoading] = useState(false) + + // Load tenant data when editing + useEffect(() => { + if (tenant) { + setFormData({ + ...formData, + ...tenant, + auth_config: { ...formData.auth_config, ...tenant.auth_config }, + user_mapping: { ...formData.user_mapping, ...tenant.user_mapping }, + role_mapping: { ...formData.role_mapping, ...tenant.role_mapping }, + features: { ...formData.features, ...tenant.features }, + branding: { ...formData.branding, ...tenant.branding } + }) + } + }, [tenant]) + + // Auto-generate slug from name + const handleNameChange = (e) => { + const name = e.target.value + const slug = name.toLowerCase() + .replace(/[^a-z0-9\s-]/g, '') + .replace(/\s+/g, '-') + .replace(/-+/g, '-') + .trim() + + setFormData(prev => ({ + ...prev, + name, + slug: tenant ? prev.slug : slug // Don't auto-update slug when editing + })) + } + + const handleSubmit = async (e) => { + e.preventDefault() + setLoading(true) + + try { + // Validate required fields + if (!formData.name || !formData.slug) { + toast.error('Name and slug are required') + return + } + + // Clean up empty auth config fields + const cleanAuthConfig = Object.fromEntries( + Object.entries(formData.auth_config).filter(([_, value]) => { + if (typeof value === 'string') return value.trim() !== '' + if (Array.isArray(value)) return value.length > 0 + if (typeof value === 'object') return Object.keys(value).length > 0 + return value !== null && value !== undefined + }) + ) + + const tenantData = { + ...formData, + auth_config: cleanAuthConfig + } + + await onSave(tenantData) + onClose() + toast.success(tenant ? 'Tenant updated successfully' : 'Tenant created successfully') + } catch (error) { + toast.error(error.message || 'Failed to save tenant') + } finally { + setLoading(false) + } + } + + if (!isOpen) return null + + return ( +
+
+
+

+ {tenant ? 'Edit Tenant' : 'Create New Tenant'} +

+ +
+ +
+ {/* Basic Information */} +
+

Basic Information

+
+
+ + +
+ +
+ + setFormData(prev => ({ ...prev, slug: e.target.value.toLowerCase() }))} + className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="acme" + pattern="^[a-z0-9-]+$" + required + /> +

+ Will be: https://{formData.slug || 'slug'}.dev.uggla.uamils.com +

+
+ +
+ + setFormData(prev => ({ ...prev, domain: e.target.value }))} + className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="drones.acme.com" + /> +
+ +
+ + +
+ +
+ + setFormData(prev => ({ ...prev, admin_email: e.target.value }))} + className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="admin@acme.com" + /> +
+ +
+ setFormData(prev => ({ ...prev, is_active: e.target.checked }))} + className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" + /> + +
+
+
+ + {/* Authentication Configuration */} +
+

Authentication

+
+
+ + +
+ + {/* Local Auth Config */} + {formData.auth_provider === 'local' && ( +
+
+ setFormData(prev => ({ + ...prev, + auth_config: { ...prev.auth_config, allow_registration: e.target.checked } + }))} + className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" + /> + +
+ +
+ setFormData(prev => ({ + ...prev, + auth_config: { ...prev.auth_config, require_email_verification: e.target.checked } + }))} + className="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded" + /> + +
+
+ )} + + {/* SAML Config */} + {formData.auth_provider === 'saml' && ( +
+
+ + setFormData(prev => ({ + ...prev, + auth_config: { ...prev.auth_config, sso_url: e.target.value } + }))} + className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="https://adfs.company.com/adfs/ls/" + /> +
+ +
+ + setFormData(prev => ({ + ...prev, + auth_config: { ...prev.auth_config, issuer: e.target.value } + }))} + className="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" + placeholder="urn:company:uav-detection" + /> +
+ +
+ +