Compare commits
33 Commits
main
...
developmen
| Author | SHA1 | Date |
|---|---|---|
|
|
351a2358ab | |
|
|
b1a3eafcf6 | |
|
|
9d5be80184 | |
|
|
9b74e8c22f | |
|
|
c4ca0328e6 | |
|
|
c094498ed8 | |
|
|
a718f7eff0 | |
|
|
bea4040028 | |
|
|
41ef9ae7dc | |
|
|
4471cc9dce | |
|
|
34fb393b55 | |
|
|
5733e855f7 | |
|
|
279e891773 | |
|
|
283d663306 | |
|
|
69c1c6f2bd | |
|
|
e762897da9 | |
|
|
47204774c3 | |
|
|
6358ffeb01 | |
|
|
e637dce2c0 | |
|
|
7ed793d13f | |
|
|
c1bd845f6e | |
|
|
5c43a0ca50 | |
|
|
fb53354518 | |
|
|
6c12a42f44 | |
|
|
6fa14d2f6e | |
|
|
ba0d2ab332 | |
|
|
082244f358 | |
|
|
a60557679c | |
|
|
30a3525a08 | |
|
|
038550bb45 | |
|
|
8fb3741dbb | |
|
|
a1cf7bc35a | |
|
|
13658ddd4c |
|
|
@ -97,7 +97,7 @@ include 'header.php'; ?>
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const ramChart = new Chart(document.getElementById('ramChart').getContext('2d'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
|
|
@ -145,7 +145,7 @@ include 'header.php'; ?>
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const netChart = new Chart(document.getElementById('netChart').getContext('2d'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
|
|
@ -203,7 +203,7 @@ include 'header.php'; ?>
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const diskChart = new Chart(document.getElementById('diskChart').getContext('2d'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
|
|
|
|||
458
html/network.php
458
html/network.php
|
|
@ -1,5 +1,4 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
Urmi you happy me happy licence
|
||||
|
||||
|
|
@ -8,93 +7,380 @@ Copyright (c) 2026 shreebhattji
|
|||
License text:
|
||||
https://github.com/shreebhattji/Urmi/blob/main/licence.md
|
||||
*/
|
||||
include 'header.php' ?>
|
||||
include 'header.php';
|
||||
|
||||
<div class="container">
|
||||
<h2>Network Interfaces</h2>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Interface Name</th>
|
||||
<th>IP Address</th>
|
||||
<th>MAC Address</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php
|
||||
// Get network interfaces excluding specific ones
|
||||
$interfaces = [];
|
||||
|
||||
// Get all interfaces using ip command
|
||||
$output = [];
|
||||
exec('ip addr show', $output);
|
||||
|
||||
// Load network configuration
|
||||
$config_file = '/var/www/network.json';
|
||||
$network_config = [];
|
||||
|
||||
if (file_exists($config_file)) {
|
||||
$config_data = file_get_contents($config_file);
|
||||
$network_config = json_decode($config_data, true);
|
||||
}
|
||||
|
||||
// Handle form submissions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['action'])) {
|
||||
$interface = $_POST['interface'] ?? '';
|
||||
$action = $_POST['action'];
|
||||
$multicast = isset($_POST['multicast']) ? 'on' : 'off';
|
||||
|
||||
if ($action === 'save') {
|
||||
// Save configuration
|
||||
$config = [
|
||||
'interface' => $interface,
|
||||
'method' => $_POST['method'] ?? '',
|
||||
'ip' => $_POST['ip'] ?? '',
|
||||
'gateway' => $_POST['gateway'] ?? '',
|
||||
'dns' => $_POST['dns'] ?? '',
|
||||
'multicast' => $multicast
|
||||
];
|
||||
|
||||
$network_config[$interface] = $config;
|
||||
file_put_contents($config_file, json_encode($network_config, JSON_PRETTY_PRINT));
|
||||
|
||||
// Generate netplan configuration
|
||||
generate_netplan_config($network_config);
|
||||
} elseif ($action === 'toggle') {
|
||||
// Toggle interface state
|
||||
$current_status = $interface_data[$interface]['status'] ?? 'down';
|
||||
if ($current_status === 'up') {
|
||||
exec("sudo ip link set $interface down", $output, $return_code);
|
||||
} else {
|
||||
exec("sudo ip link set $interface up", $output, $return_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate netplan configuration file
|
||||
function generate_netplan_config($config)
|
||||
{
|
||||
// Create backup of cloud-init configuration
|
||||
$cloud_init_file = '/etc/netplan/50-cloud-init.yaml';
|
||||
$backup_file = '/var/www/50-cloud-init.yaml_backup';
|
||||
$source_file = '/var/www/50-cloud-init.yaml';
|
||||
|
||||
exec('sudo cp /etc/netplan/50-cloud-init.yaml /var/www/50-cloud-init.yaml_backup');
|
||||
|
||||
|
||||
$netplan_content = "network:\n version: 2\n ethernets:\n";
|
||||
|
||||
foreach ($config as $interface => $settings) {
|
||||
// Skip virtual interfaces and loopback
|
||||
if (
|
||||
strpos($interface, 'enx') === 0 ||
|
||||
strpos($interface, 'docker') === 0 ||
|
||||
strpos($interface, 'br-') === 0 ||
|
||||
strpos($interface, 'veth') === 0 ||
|
||||
$interface === 'lo'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip disabled interfaces
|
||||
if (($settings['method'] ?? '') === 'disable') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$netplan_content .= " $interface:\n";
|
||||
|
||||
switch ($settings['method']) {
|
||||
case 'dhcp':
|
||||
$netplan_content .= " dhcp4: true\n";
|
||||
|
||||
// Add multicast route for DHCP interfaces
|
||||
if (($settings['multicast'] ?? 'off') === 'on') {
|
||||
$netplan_content .= " routes:\n";
|
||||
$netplan_content .= " - to: 224.0.0.0/4\n";
|
||||
$netplan_content .= " scope: link\n";
|
||||
}
|
||||
break;
|
||||
case 'static':
|
||||
$netplan_content .= " addresses:\n";
|
||||
$netplan_content .= " - " . $settings['ip'] . "/24\n";
|
||||
|
||||
$hasRoutes = false;
|
||||
|
||||
// Default gateway
|
||||
if (!empty($settings['gateway'])) {
|
||||
if (!$hasRoutes) {
|
||||
$netplan_content .= " routes:\n";
|
||||
$hasRoutes = true;
|
||||
}
|
||||
|
||||
$netplan_content .= " - to: default\n";
|
||||
$netplan_content .= " via: " . $settings['gateway'] . "\n";
|
||||
}
|
||||
|
||||
// Multicast route
|
||||
if (($settings['multicast'] ?? 'off') === 'on') {
|
||||
if (!$hasRoutes) {
|
||||
$netplan_content .= " routes:\n";
|
||||
$hasRoutes = true;
|
||||
}
|
||||
|
||||
$netplan_content .= " - to: 224.0.0.0/4\n";
|
||||
$netplan_content .= " scope: link\n";
|
||||
}
|
||||
|
||||
if (!empty($settings['dns'])) {
|
||||
$netplan_content .= " nameservers:\n";
|
||||
$netplan_content .= " addresses:\n";
|
||||
$netplan_content .= " - " . $settings['dns'] . "\n";
|
||||
}
|
||||
break;
|
||||
case 'disable':
|
||||
default:
|
||||
$netplan_content .= " dhcp4: false\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Write to netplan file
|
||||
file_put_contents('/var/www/50-cloud-init.yaml', $netplan_content);
|
||||
|
||||
// Apply netplan configuration with validation
|
||||
$output = [];
|
||||
$return_code = 0;
|
||||
|
||||
// Run netplan try to validate configuration
|
||||
exec('sudo cp /var/www/50-cloud-init.yaml /etc/netplan/50-cloud-init.yaml', $output, $return_code);
|
||||
exec("sudo netplan generate 2>&1", $out, $return_code);
|
||||
|
||||
if ($return_code !== 0) {
|
||||
if (file_exists($backup_file)) {
|
||||
exec('sudo cp /var/www/50-cloud-init.yaml_backup /etc/netplan/50-cloud-init.yaml', $output, $return_code);
|
||||
exec('sudo netplan apply', $output, $return_code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get network interfaces excluding specific ones
|
||||
$interfaces = [];
|
||||
$output = [];
|
||||
exec('ip addr show', $output);
|
||||
|
||||
$current_interface = null;
|
||||
$interface_data = [];
|
||||
|
||||
foreach ($output as $line) {
|
||||
// Match interface name
|
||||
if (preg_match('/^\d+:\s+([a-zA-Z0-9]+):/', $line, $matches)) {
|
||||
$current_interface = $matches[1];
|
||||
|
||||
// Skip interfaces we want to exclude
|
||||
if (strpos($current_interface, 'enx') === 0) {
|
||||
$current_interface = null;
|
||||
$interface_data = [];
|
||||
|
||||
foreach ($output as $line) {
|
||||
// Match interface name
|
||||
if (preg_match('/^\d+:\s+([a-zA-Z0-9]+):/', $line, $matches)) {
|
||||
$current_interface = $matches[1];
|
||||
|
||||
// Skip interfaces we want to exclude
|
||||
if (strpos($current_interface, 'enx') === 0) {
|
||||
$current_interface = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($current_interface === 'lo') {
|
||||
$current_interface = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if interface is a bridge or docker interface
|
||||
if (strpos($current_interface, 'docker') === 0 ||
|
||||
strpos($current_interface, 'br-') === 0 ||
|
||||
strpos($current_interface, 'veth') === 0) {
|
||||
$current_interface = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
$interface_data[$current_interface] = [
|
||||
'name' => $current_interface,
|
||||
'ip' => '',
|
||||
'mac' => '',
|
||||
'status' => 'down'
|
||||
];
|
||||
}
|
||||
|
||||
// Extract IP address
|
||||
if ($current_interface && preg_match('/inet\s+(\d+\.\d+\.\d+\.\d+)/', $line, $matches)) {
|
||||
$interface_data[$current_interface]['ip'] = $matches[1];
|
||||
}
|
||||
|
||||
// Extract MAC address
|
||||
if ($current_interface && preg_match('/link\/ether\s+([a-f0-9:]+)/', $line, $matches)) {
|
||||
$interface_data[$current_interface]['mac'] = $matches[1];
|
||||
}
|
||||
|
||||
// Check if interface is up
|
||||
if ($current_interface && strpos($line, 'state UP') !== false) {
|
||||
$interface_data[$current_interface]['status'] = 'up';
|
||||
}
|
||||
}
|
||||
|
||||
// Display the filtered interfaces
|
||||
foreach ($interface_data as $interface) {
|
||||
if (!empty($interface['ip']) || !empty($interface['mac'])) {
|
||||
echo "<tr>";
|
||||
echo "<td>" . htmlspecialchars($interface['name']) . "</td>";
|
||||
echo "<td>" . htmlspecialchars($interface['ip']) . "</td>";
|
||||
echo "<td>" . htmlspecialchars($interface['mac']) . "</td>";
|
||||
echo "<td>" . htmlspecialchars($interface['status']) . "</td>";
|
||||
echo "</tr>";
|
||||
}
|
||||
}
|
||||
?>
|
||||
</tbody>
|
||||
</table>
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($current_interface === 'lo') {
|
||||
$current_interface = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if interface is a bridge or docker interface
|
||||
if (
|
||||
strpos($current_interface, 'docker') === 0 ||
|
||||
strpos($current_interface, 'br-') === 0 ||
|
||||
strpos($current_interface, 'veth') === 0
|
||||
) {
|
||||
$current_interface = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
$interface_data[$current_interface] = [
|
||||
'name' => $current_interface,
|
||||
'ip' => '',
|
||||
'mac' => '',
|
||||
'status' => 'down',
|
||||
'config' => $network_config[$current_interface] ?? null
|
||||
];
|
||||
}
|
||||
|
||||
// Extract IP address
|
||||
if ($current_interface && preg_match('/inet\s+(\d+\.\d+\.\d+\.\d+)/', $line, $matches)) {
|
||||
$interface_data[$current_interface]['ip'] = $matches[1];
|
||||
}
|
||||
|
||||
// Extract MAC address
|
||||
if ($current_interface && preg_match('/link\/ether\s+([a-f0-9:]+)/', $line, $matches)) {
|
||||
$interface_data[$current_interface]['mac'] = $matches[1];
|
||||
}
|
||||
|
||||
// Check if interface is up
|
||||
if ($current_interface && strpos($line, 'state UP') !== false) {
|
||||
$interface_data[$current_interface]['status'] = 'up';
|
||||
}
|
||||
}
|
||||
|
||||
// Get selected interface from GET parameter or first interface
|
||||
$selected_interface = $_GET['interface'] ?? array_keys($interface_data)[0] ?? null;
|
||||
?>
|
||||
|
||||
<div class="containerindex">
|
||||
<div class="grid">
|
||||
<div class="card wide">
|
||||
<h3>Network Configuration</h3>
|
||||
|
||||
<!-- Interface selection tabs -->
|
||||
<div class="interface-tabs">
|
||||
<div class="interface-list">
|
||||
<?php foreach ($interface_data as $interface): ?>
|
||||
<button type="button"
|
||||
class="tab-button <?php echo $selected_interface === $interface['name'] ? 'active' : ''; ?>"
|
||||
data-interface="<?php echo htmlspecialchars($interface['name']); ?>">
|
||||
<?php echo htmlspecialchars($interface['name']); ?>
|
||||
</button>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main container for network settings -->
|
||||
<?php if ($selected_interface && isset($interface_data[$selected_interface])): ?>
|
||||
<div class="interface-card">
|
||||
<div class="interface-header">
|
||||
<h5>Interface Settings</h5>
|
||||
<span class="badge bg-<?php echo $interface_data[$selected_interface]['status'] === 'up' ? 'success' : 'secondary'; ?>">
|
||||
<?php echo htmlspecialchars($interface_data[$selected_interface]['status']); ?>
|
||||
</span>
|
||||
</div>
|
||||
<div class="interface-body">
|
||||
<p><strong>IP Address:</strong> <?php echo htmlspecialchars($interface_data[$selected_interface]['ip'] ?: 'N/A'); ?></p>
|
||||
<p><strong>MAC Address:</strong> <?php echo htmlspecialchars($interface_data[$selected_interface]['mac'] ?: 'N/A'); ?></p>
|
||||
</div>
|
||||
<div class="interface-footer">
|
||||
<form method="post" action="" class="interface-form">
|
||||
<input type="hidden" name="interface" value="<?php echo htmlspecialchars($selected_interface); ?>">
|
||||
<input type="hidden" name="action" value="save">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Interface Name</label>
|
||||
<input type="text" class="form-control" name="interface"
|
||||
value="<?php echo htmlspecialchars($interface_data[$selected_interface]['config']['interface'] ?? $selected_interface); ?>"
|
||||
placeholder="Enter interface name">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Multicast</label>
|
||||
<div class="switch-container">
|
||||
<label class="switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="multicast-toggle"
|
||||
name="multicast"
|
||||
value="on"
|
||||
<?php echo (($interface_data[$selected_interface]['config']['multicast'] ?? 'off') === 'on') ? 'checked' : ''; ?>>
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<span class="switch-label">
|
||||
<?php echo (($interface_data[$selected_interface]['config']['multicast'] ?? 'off') === 'on') ? 'Enabled' : 'Disabled'; ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Configuration Method</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="method" id="disable-<?php echo $selected_interface; ?>"
|
||||
value="disable" <?php echo ($interface_data[$selected_interface]['config']['method'] ?? '') === 'disable' ? 'checked' : ''; ?>>
|
||||
<label class="form-check-label" for="disable-<?php echo $selected_interface; ?>">
|
||||
Disable
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="method" id="dhcp-<?php echo $selected_interface; ?>"
|
||||
value="dhcp" <?php echo ($interface_data[$selected_interface]['config']['method'] ?? '') === 'dhcp' ? 'checked' : ''; ?>>
|
||||
<label class="form-check-label" for="dhcp-<?php echo $selected_interface; ?>">
|
||||
DHCP
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="method" id="static-<?php echo $selected_interface; ?>"
|
||||
value="static" <?php echo ($interface_data[$selected_interface]['config']['method'] ?? '') === 'static' ? 'checked' : ''; ?>>
|
||||
<label class="form-check-label" for="static-<?php echo $selected_interface; ?>">
|
||||
Static IP
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3" id="static-ip-fields-<?php echo $selected_interface; ?>"
|
||||
style="<?php echo ($interface_data[$selected_interface]['config']['method'] ?? '') === 'static' ? 'display: block;' : 'display: none;'; ?>">
|
||||
<div class="input-group">
|
||||
<label class="form-label">IP Address</label>
|
||||
<input type="text" class="form-control" name="ip"
|
||||
value="<?php echo htmlspecialchars($interface_data[$selected_interface]['config']['ip'] ?? ''); ?>"
|
||||
placeholder="192.168.1.100">
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label class="form-label mt-2">Gateway</label>
|
||||
<input type="text" class="form-control" name="gateway"
|
||||
value="<?php echo htmlspecialchars($interface_data[$selected_interface]['config']['gateway'] ?? ''); ?>"
|
||||
placeholder="192.168.1.1">
|
||||
</div>
|
||||
|
||||
<div class="input-group">
|
||||
<label class="form-label mt-2">DNS Server</label>
|
||||
<input type="text" class="form-control" name="dns"
|
||||
value="<?php echo htmlspecialchars($interface_data[$selected_interface]['config']['dns'] ?? ''); ?>"
|
||||
placeholder="8.8.8.8">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<button type="submit" class="btn btn-primary">Save Configuration</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="alert alert-info">
|
||||
No network interfaces found or selected.
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Toggle static IP fields based on method selection
|
||||
document.querySelectorAll('input[name="method"]').forEach(radio => {
|
||||
radio.addEventListener('change', function() {
|
||||
// Get the interface name from the radio button's ID
|
||||
const interfaceName = this.id.split('-')[1]; // Get interface name from ID like "static-eth0"
|
||||
const staticFields = document.getElementById(`static-ip-fields-${interfaceName}`);
|
||||
|
||||
if (this.value === 'static') {
|
||||
staticFields.style.display = 'block';
|
||||
} else {
|
||||
staticFields.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Tab switching functionality
|
||||
document.querySelectorAll('.tab-button').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const interfaceName = this.getAttribute('data-interface');
|
||||
window.location.href = '?interface=' + encodeURIComponent(interfaceName);
|
||||
});
|
||||
});
|
||||
|
||||
// Multicast toggle switch functionality
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('.multicast-toggle').forEach(function(checkbox) {
|
||||
checkbox.addEventListener('change', function() {
|
||||
const switchContainer = this.closest('.switch-container');
|
||||
const label = switchContainer.querySelector('.switch-label');
|
||||
|
||||
if (label) {
|
||||
label.textContent = this.checked ? 'Enabled' : 'Disabled';
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php include 'footer.php' ?>
|
||||
408
html/style.css
408
html/style.css
|
|
@ -526,11 +526,413 @@ main {
|
|||
|
||||
/* Animation for futuristic effect */
|
||||
@keyframes pulse {
|
||||
0% { box-shadow: 0 0 0 0 rgba(0, 168, 255, 0.4); }
|
||||
70% { box-shadow: 0 0 0 10px rgba(0, 168, 255, 0); }
|
||||
100% { box-shadow: 0 0 0 0 rgba(0, 168, 255, 0); }
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(0, 168, 255, 0.4);
|
||||
}
|
||||
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(0, 168, 255, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(0, 168, 255, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
/* Network configuration specific styles */
|
||||
.network-settings-container {
|
||||
padding: 20px;
|
||||
background: rgba(13, 27, 45, 0.7);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid rgba(92, 158, 255, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.interface-tabs {
|
||||
margin-bottom: 20px;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid rgba(92, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.interface-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
padding: 10px 20px;
|
||||
background: rgba(13, 27, 45, 0.7);
|
||||
border: 1px solid rgba(92, 158, 255, 0.3);
|
||||
border-radius: 8px;
|
||||
color: var(--text-primary);
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 500;
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.tab-button:hover {
|
||||
background: rgba(0, 168, 255, 0.2);
|
||||
border-color: var(--accent-blue-light);
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
background: linear-gradient(90deg, var(--accent-blue), var(--accent-blue-light));
|
||||
color: white;
|
||||
border-color: var(--accent-blue-light);
|
||||
box-shadow: 0 4px 12px rgba(0, 168, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Activation buttons */
|
||||
.activation-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
/* Network interface card styling */
|
||||
.interface-card {
|
||||
flex: 1 1 300px;
|
||||
min-width: 300px;
|
||||
background: rgba(10, 25, 41, 0.7);
|
||||
border-radius: 14px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(92, 158, 255, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.interface-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 12px 30px rgba(0, 168, 255, 0.25);
|
||||
}
|
||||
|
||||
.interface-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid rgba(92, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.interface-header h5 {
|
||||
margin: 0;
|
||||
color: var(--accent-blue-light);
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.interface-body {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.interface-body p {
|
||||
margin: 8px 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.interface-footer {
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid rgba(92, 158, 255, 0.2);
|
||||
}
|
||||
|
||||
.interface-form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.interface-form .mb-3 {
|
||||
margin-bottom: 15px !important;
|
||||
}
|
||||
|
||||
.interface-form .form-check {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.interface-form .form-check-label {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.interface-form .form-control {
|
||||
background: rgba(13, 27, 45, 0.7);
|
||||
border: 1px solid rgba(92, 158, 255, 0.3);
|
||||
color: var(--text-primary);
|
||||
backdrop-filter: blur(5px);
|
||||
border-radius: 8px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.interface-form .form-control:focus {
|
||||
border-color: var(--accent-blue-light);
|
||||
box-shadow: 0 0 0 3px rgba(0, 168, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Button styling */
|
||||
.btn-primary {
|
||||
background: linear-gradient(90deg, var(--accent-blue), var(--accent-blue-light));
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
padding: 12px 25px;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 15px rgba(0, 168, 255, 0.3);
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: linear-gradient(90deg, var(--accent-blue-light), var(--accent-blue));
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 168, 255, 0.4);
|
||||
}
|
||||
|
||||
.btn-primary:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Toggle switch styling */
|
||||
.form-switch .form-check-input {
|
||||
height: 24px;
|
||||
width: 48px;
|
||||
border-radius: 12px;
|
||||
background-color: rgba(92, 158, 255, 0.3);
|
||||
border: none;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-switch .form-check-input:checked {
|
||||
background-color: var(--accent-blue-light);
|
||||
}
|
||||
|
||||
.form-switch .form-check-input::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.form-switch .form-check-input:checked::before {
|
||||
transform: translateX(24px);
|
||||
}
|
||||
|
||||
/* Input group styling for network config */
|
||||
.input-group.network-input {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.input-group.network-input label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.input-group.network-input input {
|
||||
width: 100%;
|
||||
padding: 12px 15px;
|
||||
font-size: 1rem;
|
||||
border: 1px solid rgba(92, 158, 255, 0.3);
|
||||
border-radius: 8px;
|
||||
outline: none;
|
||||
background: rgba(13, 27, 45, 0.7);
|
||||
transition: all 0.3s ease;
|
||||
color: var(--text-primary);
|
||||
backdrop-filter: blur(5px);
|
||||
}
|
||||
|
||||
.input-group.network-input input:focus {
|
||||
border-color: var(--accent-blue-light);
|
||||
box-shadow: 0 0 0 3px rgba(0, 168, 255, 0.3);
|
||||
}
|
||||
|
||||
/* Multicast toggle styling */
|
||||
.multicast-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.multicast-toggle .form-check-input {
|
||||
width: 50px;
|
||||
height: 25px;
|
||||
border-radius: 12px;
|
||||
background-color: rgba(92, 158, 255, 0.3);
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.multicast-toggle .form-check-input:checked {
|
||||
background-color: var(--accent-blue-light);
|
||||
}
|
||||
|
||||
.multicast-toggle .form-check-input::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 19px;
|
||||
width: 19px;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.multicast-toggle .form-check-input:checked::before {
|
||||
transform: translateX(25px);
|
||||
}
|
||||
|
||||
.multicast-toggle .form-check-label {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.network-settings-container {
|
||||
margin-top: 120px;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.network-interfaces-horizontal {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.interface-card {
|
||||
width: 100%;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.interface-list {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.activation-buttons {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.activation-buttons button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
min-width: 250px;
|
||||
}
|
||||
|
||||
.green-btn,
|
||||
.red-btn {
|
||||
width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.input-group.network-input {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.input-group.network-input input {
|
||||
padding: 10px 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.multicast-toggle .form-check-input {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.multicast-toggle .form-check-input::before {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
}
|
||||
|
||||
.multicast-toggle .form-check-input:checked::before {
|
||||
transform: translateX(20px);
|
||||
}
|
||||
}
|
||||
|
||||
.switch-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: .4s;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked+.slider {
|
||||
background-color: var(--accent-blue-light);
|
||||
}
|
||||
|
||||
input:checked+.slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.switch-label {
|
||||
margin: 0;
|
||||
vertical-align: middle;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
Loading…
Reference in New Issue