diff --git a/src/dashboard.py b/src/dashboard.py
index ec57ef9d..76c21552 100644
--- a/src/dashboard.py
+++ b/src/dashboard.py
@@ -40,6 +40,7 @@ from modules.DashboardClients import DashboardClients
from modules.DashboardPlugins import DashboardPlugins
from modules.DashboardWebHooks import DashboardWebHooks
from modules.NewConfigurationTemplates import NewConfigurationTemplates
+from modules.DashboardAdmins import DashboardAdmins
class CustomJsonEncoder(DefaultJSONProvider):
def __init__(self, app):
@@ -201,6 +202,31 @@ with app.app_context():
DashboardPlugins: DashboardPlugins = DashboardPlugins(app, WireguardConfigurations)
DashboardWebHooks: DashboardWebHooks = DashboardWebHooks(DashboardConfig)
NewConfigurationTemplates: NewConfigurationTemplates = NewConfigurationTemplates()
+
+ # Initialize DashboardAdmins
+ DashboardAdmins: DashboardAdmins = DashboardAdmins(
+ DashboardConfig.engine,
+ DashboardConfig.GetConfig('Database', 'type')[1]
+ )
+
+ # Migrate admin from config file to database (first-time only)
+ if DashboardAdmins.getAdminCount() == 0:
+ _, configUsername = DashboardConfig.GetConfig("Account", "username")
+ _, configPassword = DashboardConfig.GetConfig("Account", "password")
+ _, enableTotp = DashboardConfig.GetConfig("Account", "enable_totp")
+ _, totpKey = DashboardConfig.GetConfig("Account", "totp_key")
+
+ success, msg = DashboardAdmins.migrateFromConfig(
+ username=configUsername,
+ passwordHash=configPassword,
+ enableTotp=enableTotp,
+ totpKey=totpKey
+ )
+ if success:
+ print(f"[WGDashboard] Admin migrated to database: {configUsername}")
+ else:
+ print(f"[WGDashboard] Admin migration warning: {msg}")
+
InitWireguardConfigurationsList(startup=True)
DashboardClients: DashboardClients = DashboardClients(WireguardConfigurations)
app.register_blueprint(createClientBlueprint(WireguardConfigurations, DashboardConfig, DashboardClients))
@@ -297,30 +323,31 @@ def API_AuthenticateLogin():
resp.set_cookie("authToken", authToken)
session.permanent = True
return resp
- valid = bcrypt.checkpw(data['password'].encode("utf-8"),
- DashboardConfig.GetConfig("Account", "password")[1].encode("utf-8"))
- totpEnabled = DashboardConfig.GetConfig("Account", "enable_totp")[1]
- totpValid = False
- if totpEnabled:
- totpValid = pyotp.TOTP(DashboardConfig.GetConfig("Account", "totp_key")[1]).now() == data['totp']
-
- if (valid
- and data['username'] == DashboardConfig.GetConfig("Account", "username")[1]
- and ((totpEnabled and totpValid) or not totpEnabled)
- ):
- authToken = hashlib.sha256(f"{data['username']}{datetime.now()}".encode()).hexdigest()
- session['role'] = 'admin'
- session['username'] = authToken
- resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
- resp.set_cookie("authToken", authToken)
- session.permanent = True
- DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login success: {data['username']}")
- return resp
- DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login failed: {data['username']}")
- if totpEnabled:
- return ResponseObject(False, "Sorry, your username, password or OTP is incorrect.")
- else:
+
+ # Authenticate using DashboardAdmins
+ success, admin, msg = DashboardAdmins.authenticate(data['username'], data['password'])
+
+ if not success:
+ DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login failed: {data['username']}")
return ResponseObject(False, "Sorry, your username or password is incorrect.")
+
+ # Check TOTP if enabled
+ if admin['enable_totp'] and admin['totp_verified']:
+ totpValid = pyotp.TOTP(admin['totp_key']).now() == data.get('totp', '')
+ if not totpValid:
+ DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login failed (TOTP): {data['username']}")
+ return ResponseObject(False, "Sorry, your username, password or OTP is incorrect.")
+
+ authToken = hashlib.sha256(f"{data['username']}{datetime.now()}".encode()).hexdigest()
+ session['role'] = 'admin'
+ session['username'] = authToken
+ session['admin_id'] = admin['id']
+ session['admin_username'] = admin['username']
+ resp = ResponseObject(True, DashboardConfig.GetConfig("Other", "welcome_session")[1])
+ resp.set_cookie("authToken", authToken)
+ session.permanent = True
+ DashboardLogger.log(str(request.url), str(request.remote_addr), Message=f"Login success: {data['username']}")
+ return resp
@app.get(f'{APP_PREFIX}/api/signout')
def API_SignOut():
@@ -329,6 +356,129 @@ def API_SignOut():
session.clear()
return resp
+# =====================================================
+# Admin Management API Endpoints
+# =====================================================
+
+@app.get(f'{APP_PREFIX}/api/admins')
+def API_GetAdmins():
+ """Get all admin users"""
+ admins = DashboardAdmins.getAllAdmins()
+ return ResponseObject(True, data=[admin.toDict() for admin in admins])
+
+@app.post(f'{APP_PREFIX}/api/admins/add')
+def API_AddAdmin():
+ """Add a new admin user"""
+ data = request.get_json()
+ username = data.get('username', '').strip()
+ password = data.get('password', '')
+ email = data.get('email', '').strip()
+
+ if not username or not password:
+ return ResponseObject(False, "Username and password are required")
+
+ success, msg = DashboardAdmins.addAdmin(username, password, email)
+ if success:
+ DashboardLogger.log(str(request.url), str(request.remote_addr),
+ Message=f"Admin created: {username} by {session.get('admin_username', 'unknown')}")
+ return ResponseObject(success, msg)
+
+@app.post(f'{APP_PREFIX}/api/admins/update')
+def API_UpdateAdmin():
+ """Update admin details (username, email)"""
+ data = request.get_json()
+ adminId = data.get('id')
+ username = data.get('username')
+ email = data.get('email')
+
+ if not adminId:
+ return ResponseObject(False, "Admin ID is required")
+
+ success, msg = DashboardAdmins.updateAdmin(adminId, username, email)
+ if success:
+ DashboardLogger.log(str(request.url), str(request.remote_addr),
+ Message=f"Admin updated: ID {adminId} by {session.get('admin_username', 'unknown')}")
+ return ResponseObject(success, msg)
+
+@app.post(f'{APP_PREFIX}/api/admins/changePassword')
+def API_ChangeAdminPassword():
+ """Change password for current logged-in admin"""
+ data = request.get_json()
+ currentPassword = data.get('currentPassword', '')
+ newPassword = data.get('newPassword', '')
+
+ adminId = session.get('admin_id')
+ if not adminId:
+ return ResponseObject(False, "Not authenticated")
+
+ if not currentPassword or not newPassword:
+ return ResponseObject(False, "Current password and new password are required")
+
+ success, msg = DashboardAdmins.changePassword(adminId, currentPassword, newPassword)
+ if success:
+ DashboardLogger.log(str(request.url), str(request.remote_addr),
+ Message=f"Password changed: {session.get('admin_username', 'unknown')}")
+ return ResponseObject(success, msg)
+
+@app.post(f'{APP_PREFIX}/api/admins/resetPassword')
+def API_ResetAdminPassword():
+ """Reset password for any admin (admin action)"""
+ data = request.get_json()
+ adminId = data.get('id')
+ newPassword = data.get('newPassword', '')
+
+ if not adminId or not newPassword:
+ return ResponseObject(False, "Admin ID and new password are required")
+
+ success, msg = DashboardAdmins.resetPassword(adminId, newPassword)
+ if success:
+ admin = DashboardAdmins.getAdminById(adminId)
+ DashboardLogger.log(str(request.url), str(request.remote_addr),
+ Message=f"Password reset for: {admin['username'] if admin else adminId} by {session.get('admin_username', 'unknown')}")
+ return ResponseObject(success, msg)
+
+@app.post(f'{APP_PREFIX}/api/admins/delete')
+def API_DeleteAdmin():
+ """Delete an admin user"""
+ data = request.get_json()
+ adminId = data.get('id')
+
+ if not adminId:
+ return ResponseObject(False, "Admin ID is required")
+
+ # Prevent self-deletion
+ if adminId == session.get('admin_id'):
+ return ResponseObject(False, "Cannot delete your own account")
+
+ admin = DashboardAdmins.getAdminById(adminId)
+ success, msg = DashboardAdmins.deleteAdmin(adminId)
+ if success:
+ DashboardLogger.log(str(request.url), str(request.remote_addr),
+ Message=f"Admin deleted: {admin['username'] if admin else adminId} by {session.get('admin_username', 'unknown')}")
+ return ResponseObject(success, msg)
+
+@app.get(f'{APP_PREFIX}/api/admins/current')
+def API_GetCurrentAdmin():
+ """Get current logged-in admin info"""
+ adminId = session.get('admin_id')
+ if not adminId:
+ return ResponseObject(False, "Not authenticated")
+
+ admin = DashboardAdmins.getAdminById(adminId)
+ if admin:
+ # Don't return sensitive data
+ return ResponseObject(True, data={
+ 'id': admin['id'],
+ 'username': admin['username'],
+ 'email': admin['email'],
+ 'enable_totp': admin['enable_totp']
+ })
+ return ResponseObject(False, "Admin not found")
+
+# =====================================================
+# End Admin Management API Endpoints
+# =====================================================
+
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurations')
def API_getWireguardConfigurations():
InitWireguardConfigurationsList()
diff --git a/src/modules/DashboardAdmins.py b/src/modules/DashboardAdmins.py
new file mode 100644
index 00000000..7c8265e5
--- /dev/null
+++ b/src/modules/DashboardAdmins.py
@@ -0,0 +1,351 @@
+"""
+Dashboard Admins - Multiple admin users management
+"""
+import bcrypt
+import sqlalchemy as db
+from datetime import datetime
+from typing import Optional, List
+from dataclasses import dataclass, asdict
+
+
+@dataclass
+class AdminUser:
+ """Represents an admin user"""
+ id: int
+ username: str
+ email: str
+ created_at: str
+ last_login: Optional[str]
+ enable_totp: bool = False
+ totp_verified: bool = False
+
+ def toDict(self) -> dict:
+ return asdict(self)
+
+
+class DashboardAdmins:
+ """Manages multiple admin users in the database"""
+
+ def __init__(self, engine, dbType: str = 'sqlite'):
+ self.engine = engine
+ self.dbType = dbType
+ self.dbMetadata = db.MetaData()
+ self._createTable()
+
+ def _createTable(self):
+ """Create the DashboardAdmins table if it doesn't exist"""
+ self.adminsTable = db.Table(
+ 'DashboardAdmins',
+ self.dbMetadata,
+ db.Column("id", db.Integer, primary_key=True, autoincrement=True),
+ db.Column("username", db.String(255), nullable=False, unique=True),
+ db.Column("password", db.String(255), nullable=False),
+ db.Column("email", db.String(255), nullable=True, default=""),
+ db.Column("enable_totp", db.Boolean, default=False),
+ db.Column("totp_verified", db.Boolean, default=False),
+ db.Column("totp_key", db.String(255), nullable=True),
+ db.Column("created_at",
+ (db.DATETIME if self.dbType == 'sqlite' else db.TIMESTAMP),
+ server_default=db.func.now()),
+ db.Column("last_login",
+ (db.DATETIME if self.dbType == 'sqlite' else db.TIMESTAMP),
+ nullable=True)
+ )
+ self.dbMetadata.create_all(self.engine)
+
+ def _hashPassword(self, plainPassword: str) -> str:
+ """Hash a password using bcrypt"""
+ return bcrypt.hashpw(plainPassword.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
+
+ def _checkPassword(self, plainPassword: str, hashedPassword: str) -> bool:
+ """Verify a password against its hash"""
+ try:
+ return bcrypt.checkpw(plainPassword.encode('utf-8'), hashedPassword.encode('utf-8'))
+ except Exception:
+ return False
+
+ def getAdminCount(self) -> int:
+ """Get total number of admins"""
+ with self.engine.connect() as conn:
+ result = conn.execute(
+ db.select(db.func.count()).select_from(self.adminsTable)
+ ).scalar()
+ return result or 0
+
+ def getAllAdmins(self) -> List[AdminUser]:
+ """Get all admin users (without passwords)"""
+ admins = []
+ with self.engine.connect() as conn:
+ result = conn.execute(
+ self.adminsTable.select().order_by(self.adminsTable.c.id)
+ ).fetchall()
+
+ for row in result:
+ admins.append(AdminUser(
+ id=row.id,
+ username=row.username,
+ email=row.email or "",
+ created_at=row.created_at.strftime("%Y-%m-%d %H:%M:%S") if row.created_at else "",
+ last_login=row.last_login.strftime("%Y-%m-%d %H:%M:%S") if row.last_login else None,
+ enable_totp=row.enable_totp or False,
+ totp_verified=row.totp_verified or False
+ ))
+ return admins
+
+ def getAdminByUsername(self, username: str) -> Optional[dict]:
+ """Get admin by username (includes password hash for auth)"""
+ with self.engine.connect() as conn:
+ result = conn.execute(
+ self.adminsTable.select().where(
+ self.adminsTable.c.username == username
+ )
+ ).fetchone()
+
+ if result:
+ return {
+ 'id': result.id,
+ 'username': result.username,
+ 'password': result.password,
+ 'email': result.email or "",
+ 'enable_totp': result.enable_totp or False,
+ 'totp_verified': result.totp_verified or False,
+ 'totp_key': result.totp_key,
+ 'created_at': result.created_at,
+ 'last_login': result.last_login
+ }
+ return None
+
+ def getAdminById(self, adminId: int) -> Optional[dict]:
+ """Get admin by ID"""
+ with self.engine.connect() as conn:
+ result = conn.execute(
+ self.adminsTable.select().where(
+ self.adminsTable.c.id == adminId
+ )
+ ).fetchone()
+
+ if result:
+ return {
+ 'id': result.id,
+ 'username': result.username,
+ 'email': result.email or "",
+ 'enable_totp': result.enable_totp or False,
+ 'totp_verified': result.totp_verified or False,
+ 'totp_key': result.totp_key,
+ 'created_at': result.created_at,
+ 'last_login': result.last_login
+ }
+ return None
+
+ def authenticate(self, username: str, password: str) -> tuple[bool, Optional[dict], str]:
+ """
+ Authenticate an admin user
+ Returns: (success, admin_data, message)
+ """
+ admin = self.getAdminByUsername(username)
+
+ if not admin:
+ return False, None, "Invalid username or password"
+
+ if not self._checkPassword(password, admin['password']):
+ return False, None, "Invalid username or password"
+
+ # Update last login
+ self.updateLastLogin(admin['id'])
+
+ return True, admin, "Login successful"
+
+ def addAdmin(self, username: str, password: str, email: str = "") -> tuple[bool, str]:
+ """Add a new admin user"""
+ # Check if username already exists
+ if self.getAdminByUsername(username):
+ return False, "Username already exists"
+
+ # Validate username
+ if len(username) < 3:
+ return False, "Username must be at least 3 characters"
+
+ # Validate password
+ if len(password) < 4:
+ return False, "Password must be at least 4 characters"
+
+ try:
+ with self.engine.begin() as conn:
+ conn.execute(
+ self.adminsTable.insert().values(
+ username=username,
+ password=self._hashPassword(password),
+ email=email,
+ enable_totp=False,
+ totp_verified=False,
+ created_at=datetime.now()
+ )
+ )
+ return True, "Admin created successfully"
+ except Exception as e:
+ return False, f"Error creating admin: {str(e)}"
+
+ def updateAdmin(self, adminId: int, username: str = None, email: str = None) -> tuple[bool, str]:
+ """Update admin details (not password)"""
+ admin = self.getAdminById(adminId)
+ if not admin:
+ return False, "Admin not found"
+
+ updates = {}
+ if username is not None and username != admin['username']:
+ # Check if new username is taken
+ existing = self.getAdminByUsername(username)
+ if existing and existing['id'] != adminId:
+ return False, "Username already taken"
+ updates['username'] = username
+
+ if email is not None:
+ updates['email'] = email
+
+ if not updates:
+ return True, "No changes to apply"
+
+ try:
+ with self.engine.begin() as conn:
+ conn.execute(
+ self.adminsTable.update()
+ .where(self.adminsTable.c.id == adminId)
+ .values(**updates)
+ )
+ return True, "Admin updated successfully"
+ except Exception as e:
+ return False, f"Error updating admin: {str(e)}"
+
+ def changePassword(self, adminId: int, currentPassword: str, newPassword: str) -> tuple[bool, str]:
+ """Change admin password (requires current password)"""
+ admin = self.getAdminById(adminId)
+ if not admin:
+ return False, "Admin not found"
+
+ # Get full admin record with password
+ with self.engine.connect() as conn:
+ result = conn.execute(
+ self.adminsTable.select().where(self.adminsTable.c.id == adminId)
+ ).fetchone()
+
+ if not result:
+ return False, "Admin not found"
+
+ if not self._checkPassword(currentPassword, result.password):
+ return False, "Current password is incorrect"
+
+ if len(newPassword) < 4:
+ return False, "New password must be at least 4 characters"
+
+ try:
+ with self.engine.begin() as conn:
+ conn.execute(
+ self.adminsTable.update()
+ .where(self.adminsTable.c.id == adminId)
+ .values(password=self._hashPassword(newPassword))
+ )
+ return True, "Password changed successfully"
+ except Exception as e:
+ return False, f"Error changing password: {str(e)}"
+
+ def resetPassword(self, adminId: int, newPassword: str) -> tuple[bool, str]:
+ """Reset admin password (admin action, no current password needed)"""
+ admin = self.getAdminById(adminId)
+ if not admin:
+ return False, "Admin not found"
+
+ if len(newPassword) < 4:
+ return False, "Password must be at least 4 characters"
+
+ try:
+ with self.engine.begin() as conn:
+ conn.execute(
+ self.adminsTable.update()
+ .where(self.adminsTable.c.id == adminId)
+ .values(password=self._hashPassword(newPassword))
+ )
+ return True, "Password reset successfully"
+ except Exception as e:
+ return False, f"Error resetting password: {str(e)}"
+
+ def deleteAdmin(self, adminId: int) -> tuple[bool, str]:
+ """Delete an admin user"""
+ # Cannot delete if only one admin remains
+ if self.getAdminCount() <= 1:
+ return False, "Cannot delete the last admin"
+
+ admin = self.getAdminById(adminId)
+ if not admin:
+ return False, "Admin not found"
+
+ try:
+ with self.engine.begin() as conn:
+ conn.execute(
+ self.adminsTable.delete().where(self.adminsTable.c.id == adminId)
+ )
+ return True, f"Admin '{admin['username']}' deleted successfully"
+ except Exception as e:
+ return False, f"Error deleting admin: {str(e)}"
+
+ def updateLastLogin(self, adminId: int):
+ """Update last login timestamp"""
+ try:
+ with self.engine.begin() as conn:
+ conn.execute(
+ self.adminsTable.update()
+ .where(self.adminsTable.c.id == adminId)
+ .values(last_login=datetime.now())
+ )
+ except Exception:
+ pass # Non-critical error
+
+ def updateTOTP(self, adminId: int, enable: bool, totpKey: str = None, verified: bool = False) -> tuple[bool, str]:
+ """Update TOTP settings for an admin"""
+ admin = self.getAdminById(adminId)
+ if not admin:
+ return False, "Admin not found"
+
+ try:
+ updates = {
+ 'enable_totp': enable,
+ 'totp_verified': verified
+ }
+ if totpKey:
+ updates['totp_key'] = totpKey
+
+ with self.engine.begin() as conn:
+ conn.execute(
+ self.adminsTable.update()
+ .where(self.adminsTable.c.id == adminId)
+ .values(**updates)
+ )
+ return True, "TOTP updated successfully"
+ except Exception as e:
+ return False, f"Error updating TOTP: {str(e)}"
+
+ def migrateFromConfig(self, username: str, passwordHash: str,
+ enableTotp: bool = False, totpKey: str = None) -> tuple[bool, str]:
+ """
+ Migrate admin from wg-dashboard.ini config to database.
+ Used for first-time migration.
+ """
+ # Check if any admins exist
+ if self.getAdminCount() > 0:
+ return False, "Admins already exist in database"
+
+ try:
+ with self.engine.begin() as conn:
+ conn.execute(
+ self.adminsTable.insert().values(
+ username=username,
+ password=passwordHash, # Already hashed
+ email="",
+ enable_totp=enableTotp,
+ totp_verified=enableTotp, # If TOTP was enabled, it was verified
+ totp_key=totpKey,
+ created_at=datetime.now()
+ )
+ )
+ return True, f"Admin '{username}' migrated successfully"
+ except Exception as e:
+ return False, f"Error migrating admin: {str(e)}"
diff --git a/src/static/app/src/components/configurationComponents/peerAddModal.vue b/src/static/app/src/components/configurationComponents/peerAddModal.vue
index 5212464c..72c198ee 100644
--- a/src/static/app/src/components/configurationComponents/peerAddModal.vue
+++ b/src/static/app/src/components/configurationComponents/peerAddModal.vue
@@ -18,6 +18,20 @@ import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStor
const dashboardStore = DashboardConfigurationStore()
const wireguardStore = WireguardConfigurationsStore()
+const route = useRoute()
+
+// Find current configuration to get Override Peer Settings
+const currentConfig = wireguardStore.Configurations.find(
+ x => x.Name === route.params.id
+);
+
+// Get Override Peer Settings (if they exist and have values)
+const override = currentConfig?.Info?.OverridePeerSettings || {};
+
+// Get global defaults
+const globalDefaults = dashboardStore.Configuration.Peers;
+
+// Initialize peerData with Override settings, falling back to global defaults
const peerData = ref({
bulkAdd: false,
bulkAddAmount: 0,
@@ -25,10 +39,18 @@ const peerData = ref({
allowed_ips: [],
private_key: "",
public_key: "",
- DNS: dashboardStore.Configuration.Peers.peer_global_dns,
- endpoint_allowed_ip: dashboardStore.Configuration.Peers.peer_endpoint_allowed_ip,
- keepalive: parseInt(dashboardStore.Configuration.Peers.peer_keep_alive),
- mtu: parseInt(dashboardStore.Configuration.Peers.peer_mtu),
+ // DNS: Override if set, otherwise global default
+ DNS: override.DNS || globalDefaults.peer_global_dns,
+ // Endpoint Allowed IPs: Override if set, otherwise global default
+ endpoint_allowed_ip: override.EndpointAllowedIPs || globalDefaults.peer_endpoint_allowed_ip,
+ // Persistent Keepalive: Override if set (and > 0), otherwise global default
+ keepalive: (override.PersistentKeepalive && parseInt(override.PersistentKeepalive) > 0)
+ ? parseInt(override.PersistentKeepalive)
+ : parseInt(globalDefaults.peer_keep_alive),
+ // MTU: Override if set (and > 0), otherwise global default
+ mtu: (override.MTU && parseInt(override.MTU) > 0)
+ ? parseInt(override.MTU)
+ : parseInt(globalDefaults.peer_mtu),
preshared_key: "",
preshared_key_bulkAdd: false,
advanced_security: "off",
@@ -37,7 +59,6 @@ const peerData = ref({
const availableIp = ref([])
const saving = ref(false)
-const route = useRoute()
await fetchGet("/api/getAvailableIPs/" + route.params.id, {}, (res) => {
if (res.status){
availableIp.value = res.data;
diff --git a/src/static/app/src/components/configurationComponents/peerCreate.vue b/src/static/app/src/components/configurationComponents/peerCreate.vue
index 6f6defdf..ad58d2c8 100644
--- a/src/static/app/src/components/configurationComponents/peerCreate.vue
+++ b/src/static/app/src/components/configurationComponents/peerCreate.vue
@@ -31,10 +31,11 @@ export default {
allowed_ips: [],
private_key: "",
public_key: "",
- DNS: this.dashboardStore.Configuration.Peers.peer_global_dns,
- endpoint_allowed_ip: this.dashboardStore.Configuration.Peers.peer_endpoint_allowed_ip,
- keepalive: parseInt(this.dashboardStore.Configuration.Peers.peer_keep_alive),
- mtu: parseInt(this.dashboardStore.Configuration.Peers.peer_mtu),
+ // Will be set in mounted() with Override Peer Settings priority
+ DNS: "",
+ endpoint_allowed_ip: "",
+ keepalive: 0,
+ mtu: 0,
preshared_key: "",
preshared_key_bulkAdd: false,
advanced_security: "off",
@@ -46,11 +47,40 @@ export default {
}
},
mounted() {
+ // Load available IPs
fetchGet("/api/getAvailableIPs/" + this.$route.params.id, {}, (res) => {
if (res.status){
this.availableIp = res.data;
}
})
+
+ // Find current configuration to get Override Peer Settings
+ const currentConfig = this.store.Configurations.find(
+ x => x.Name === this.$route.params.id
+ );
+
+ // Get Override Peer Settings (if they exist and have values)
+ const override = currentConfig?.Info?.OverridePeerSettings || {};
+
+ // Get global defaults
+ const globalDefaults = this.dashboardStore.Configuration.Peers;
+
+ // Apply Override settings with fallback to global defaults
+ // DNS: Override if set, otherwise global default
+ this.data.DNS = override.DNS || globalDefaults.peer_global_dns;
+
+ // Endpoint Allowed IPs: Override if set, otherwise global default
+ this.data.endpoint_allowed_ip = override.EndpointAllowedIPs || globalDefaults.peer_endpoint_allowed_ip;
+
+ // MTU: Override if set (and > 0), otherwise global default
+ this.data.mtu = (override.MTU && parseInt(override.MTU) > 0)
+ ? parseInt(override.MTU)
+ : parseInt(globalDefaults.peer_mtu);
+
+ // Persistent Keepalive: Override if set (and > 0), otherwise global default
+ this.data.keepalive = (override.PersistentKeepalive && parseInt(override.PersistentKeepalive) > 0)
+ ? parseInt(override.PersistentKeepalive)
+ : parseInt(globalDefaults.peer_keep_alive);
},
setup(){
const store = WireguardConfigurationsStore();
diff --git a/src/static/app/src/components/settingsComponent/dashboardAdminUsers.vue b/src/static/app/src/components/settingsComponent/dashboardAdminUsers.vue
new file mode 100644
index 00000000..47b1fd61
--- /dev/null
+++ b/src/static/app/src/components/settingsComponent/dashboardAdminUsers.vue
@@ -0,0 +1,484 @@
+
+
+
+
+
+
+ {{ admins.length }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ admin.username }}
+ You
+
+ {{ admin.email || '-' }}
+ {{ formatDate(admin.created_at) }}
+ {{ formatDate(admin.last_login) }}
+
+
+ Enabled
+
+ Disabled
+
+
+
+