Compare commits

...

33 Commits

Author SHA1 Message Date
Devdatt Bhatt 351a2358ab update netplan 2026-06-08 09:20:29 +00:00
Devdatt Bhatt b1a3eafcf6 network update 2026-06-08 09:04:46 +00:00
Devdatt Bhatt 9d5be80184 netplan update 2026-06-08 08:58:57 +00:00
Devdatt Bhatt 9b74e8c22f update 2026-06-08 07:36:54 +00:00
Devdatt Bhatt c4ca0328e6 netplan update 2026-06-08 07:05:24 +00:00
Devdatt Bhatt c094498ed8 interfave name ui upadte 2026-06-08 05:17:21 +00:00
Devdatt Bhatt a718f7eff0 update 2026-06-08 05:10:16 +00:00
Devdatt Bhatt bea4040028 network.json 2026-06-08 04:51:39 +00:00
Devdatt Bhatt 41ef9ae7dc ui update 2026-06-08 04:34:56 +00:00
Devdatt Bhatt 4471cc9dce ui update 2026-06-08 04:30:36 +00:00
Devdatt Bhatt 34fb393b55 update 2026-06-08 04:23:00 +00:00
Devdatt Bhatt 5733e855f7 ui update 2026-06-08 04:16:16 +00:00
Devdatt Bhatt 279e891773 update 2026-06-08 04:11:06 +00:00
Devdatt Bhatt 283d663306 ui upadte 2026-06-08 04:03:10 +00:00
Devdatt Bhatt 69c1c6f2bd ui update 2026-06-08 04:00:17 +00:00
Devdatt Bhatt e762897da9 ui update 2026-06-08 03:57:05 +00:00
Devdatt Bhatt 47204774c3 ui update 2026-06-08 03:51:29 +00:00
Devdatt Bhatt 6358ffeb01 ui update 2026-06-08 02:14:04 +00:00
Devdatt Bhatt e637dce2c0 up update 2026-06-08 01:13:14 +00:00
Devdatt Bhatt 7ed793d13f ui update 2026-06-08 01:12:04 +00:00
Devdatt Bhatt c1bd845f6e ui upadte 2026-06-08 01:02:53 +00:00
Devdatt Bhatt 5c43a0ca50 ui update 2026-06-08 00:51:26 +00:00
Devdatt Bhatt fb53354518 ui update 2026-06-08 00:45:55 +00:00
Devdatt Bhatt 6c12a42f44 ui update 2026-06-08 00:32:56 +00:00
Devdatt Bhatt 6fa14d2f6e ui updtae 2026-06-08 00:27:25 +00:00
Devdatt Bhatt ba0d2ab332 ui upadte 2026-06-08 00:26:26 +00:00
Devdatt Bhatt 082244f358 update 2026-06-08 00:20:25 +00:00
Devdatt Bhatt a60557679c update 2026-06-08 00:18:00 +00:00
Devdatt Bhatt 30a3525a08 ui upadte 2026-06-08 00:14:45 +00:00
Devdatt Bhatt 038550bb45 ui update 2026-06-07 23:56:55 +00:00
Devdatt Bhatt 8fb3741dbb ui update 2026-06-07 23:51:58 +00:00
Devdatt Bhatt a1cf7bc35a ui update 2026-06-07 23:43:38 +00:00
Devdatt Bhatt 13658ddd4c network managment 2026-06-07 23:35:38 +00:00
3 changed files with 780 additions and 92 deletions

View File

@ -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 = [];
// Load network configuration
$config_file = '/var/www/network.json';
$network_config = [];
// Get all interfaces using ip command
$output = [];
exec('ip addr show', $output);
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 = [];
continue;
}
foreach ($output as $line) {
// Match interface name
if (preg_match('/^\d+:\s+([a-zA-Z0-9]+):/', $line, $matches)) {
$current_interface = $matches[1];
if ($current_interface === 'lo') {
$current_interface = null;
continue;
}
// Skip interfaces we want to exclude
if (strpos($current_interface, 'enx') === 0) {
$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;
}
if ($current_interface === 'lo') {
$current_interface = null;
continue;
}
$interface_data[$current_interface] = [
'name' => $current_interface,
'ip' => '',
'mac' => '',
'status' => 'down',
'config' => $network_config[$current_interface] ?? null
];
}
// 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;
}
// Extract IP address
if ($current_interface && preg_match('/inet\s+(\d+\.\d+\.\d+\.\d+)/', $line, $matches)) {
$interface_data[$current_interface]['ip'] = $matches[1];
}
$interface_data[$current_interface] = [
'name' => $current_interface,
'ip' => '',
'mac' => '',
'status' => 'down'
];
}
// Extract MAC address
if ($current_interface && preg_match('/link\/ether\s+([a-f0-9:]+)/', $line, $matches)) {
$interface_data[$current_interface]['mac'] = $matches[1];
}
// Extract IP address
if ($current_interface && preg_match('/inet\s+(\d+\.\d+\.\d+\.\d+)/', $line, $matches)) {
$interface_data[$current_interface]['ip'] = $matches[1];
}
// Check if interface is up
if ($current_interface && strpos($line, 'state UP') !== false) {
$interface_data[$current_interface]['status'] = 'up';
}
}
// Extract MAC address
if ($current_interface && preg_match('/link\/ether\s+([a-f0-9:]+)/', $line, $matches)) {
$interface_data[$current_interface]['mac'] = $matches[1];
}
// Get selected interface from GET parameter or first interface
$selected_interface = $_GET['interface'] ?? array_keys($interface_data)[0] ?? null;
?>
// Check if interface is up
if ($current_interface && strpos($line, 'state UP') !== false) {
$interface_data[$current_interface]['status'] = 'up';
}
}
<div class="containerindex">
<div class="grid">
<div class="card wide">
<h3>Network Configuration</h3>
// 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>
<!-- 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' ?>

View File

@ -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);
}