first commit
This commit is contained in:
31
README.md
Normal file
31
README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Netplan Config Generator
|
||||
|
||||
Bu proje, Ubuntu ve diğer Linux dağıtımlarında kullanılan Netplan ağ yapılandırma aracı için web tabanlı bir YAML oluşturucusudur.
|
||||
|
||||
## Özellikler
|
||||
|
||||
- **Görsel Arayüz:** IP adresleri, Gateway, DNS gibi ayarları kolayca girin.
|
||||
- **Gelişmiş Destek:** Ethernet, Wi-Fi, Bridge, Bond ve VLAN yapılandırmalarını destekler.
|
||||
- **Canlı Önizleme:** Yapılandırmanız değiştikçe YAML çıktısını anlık olarak görün.
|
||||
- **İndirme ve Kopyalama:** Oluşturulan dosyayı tek tıkla indirin veya panoya kopyalayın.
|
||||
- **Çoklu Dil:** Türkçe ve İngilizce dil desteği.
|
||||
- **Tema Desteği:** Aydınlık ve Karanlık mod seçenekleri.
|
||||
- **Kurulum Gerektirmez:** Sadece `index.html` dosyasını tarayıcınızda açarak kullanabilirsiniz.
|
||||
|
||||
## Kullanım
|
||||
|
||||
1. Proje klasöründeki `index.html` dosyasını bir web tarayıcısında (Chrome, Firefox, Edge vb.) açın.
|
||||
2. Sol menüden ağ arayüzlerinizi (Ethernet, Wi-Fi vb.) ekleyin.
|
||||
3. Gerekli IP ve ağ ayarlarını doldurun.
|
||||
4. Sağ taraftaki önizleme panelinden YAML çıktısını kontrol edin.
|
||||
5. "YAML İndir" butonu ile yapılandırma dosyanızı indirin.
|
||||
|
||||
## Dosya Yapısı
|
||||
|
||||
- `index.html`: Uygulamanın ana giriş noktası ve arayüzü.
|
||||
- `app.js`: Vue.js tabanlı uygulama mantığı ve YAML oluşturma kodları.
|
||||
- `style.css`: Özel stil tanımlamaları.
|
||||
|
||||
## Gereksinimler
|
||||
|
||||
- İnternet bağlantısı (Vue.js, TailwindCSS ve js-yaml kütüphanelerini CDN üzerinden yüklemek için gereklidir).
|
||||
344
app.js
Normal file
344
app.js
Normal file
@@ -0,0 +1,344 @@
|
||||
const { createApp } = Vue;
|
||||
|
||||
createApp({
|
||||
data() {
|
||||
return {
|
||||
currentLang: 'tr',
|
||||
isDark: false,
|
||||
// Translation dictionary
|
||||
translations: {
|
||||
tr: {
|
||||
globalSettings: 'Genel Ayarlar',
|
||||
renderer: 'Oluşturucu (Renderer)',
|
||||
version: 'Netplan Sürümü',
|
||||
ethernets: 'Ethernet Arayüzleri',
|
||||
addEthernet: 'Ethernet Ekle',
|
||||
wifis: 'Wi-Fi Arayüzleri',
|
||||
addWifi: 'Wi-Fi Ekle',
|
||||
bridges: 'Köprüler (Bridges)',
|
||||
addBridge: 'Köprü Ekle',
|
||||
bonds: 'Bağlar (Bonds)',
|
||||
addBond: 'Bağ Ekle',
|
||||
vlans: 'VLANlar',
|
||||
addVlan: 'VLAN Ekle',
|
||||
noInterfaces: 'Henüz arayüz eklenmedi.',
|
||||
interfaceName: 'Arayüz Adı',
|
||||
interfaces: 'Arayüzler (Interfaces)',
|
||||
ipAddresses: 'IP Adresleri',
|
||||
gateway4: 'Gateway (IPv4)',
|
||||
nameservers: 'DNS Sunucuları',
|
||||
commaSeparated: 'Virgülle ayrılmış (örn: 192.168.1.1, 8.8.8.8)',
|
||||
advancedSettings: 'Gelişmiş Ayarlar',
|
||||
routes: 'Statik Rotalar (Routes)',
|
||||
downloadYaml: 'YAML İndir',
|
||||
copyClipboard: 'Kopyala',
|
||||
parameters: 'Parametreler',
|
||||
mode: 'Mod',
|
||||
id: 'ID',
|
||||
link: 'Bağlantı (Link)',
|
||||
instructions: 'Kurulum Talimatları',
|
||||
step1: '1. Oluşturulan dosyayı kopyalayın veya indirin.',
|
||||
step2: '2. Linux sunucunuzda aşağıdaki dizine dosyayı kaydedin (varsayılan dosya adı genellikle 00-installer-config.yaml veya 01-netcfg.yaml olabilir):',
|
||||
step3: '3. Dosya izinlerini güvenli hale getirin:',
|
||||
step4: '4. Yapılandırmayı test edin (Hata varsa geri alır):',
|
||||
step5: '5. Eğer test başarılıysa yapılandırmayı uygulayın:',
|
||||
note: 'Not: Netplan dosya isimleri alfabetik sıraya göre işlenir. Eski yapılandırma dosyalarını yedeklemeyi veya silmeyi unutmayın.'
|
||||
},
|
||||
en: {
|
||||
globalSettings: 'Global Settings',
|
||||
renderer: 'Renderer',
|
||||
version: 'Netplan Version',
|
||||
ethernets: 'Ethernet Interfaces',
|
||||
addEthernet: 'Add Ethernet',
|
||||
wifis: 'Wi-Fi Interfaces',
|
||||
addWifi: 'Add Wi-Fi',
|
||||
bridges: 'Bridges',
|
||||
addBridge: 'Add Bridge',
|
||||
bonds: 'Bonds',
|
||||
addBond: 'Add Bond',
|
||||
vlans: 'VLANs',
|
||||
addVlan: 'Add VLAN',
|
||||
noInterfaces: 'No interfaces added yet.',
|
||||
interfaceName: 'Interface Name',
|
||||
interfaces: 'Interfaces',
|
||||
ipAddresses: 'IP Addresses',
|
||||
gateway4: 'Gateway (IPv4)',
|
||||
nameservers: 'Nameservers',
|
||||
commaSeparated: 'Comma separated (e.g. 192.168.1.1, 8.8.8.8)',
|
||||
advancedSettings: 'Advanced Settings',
|
||||
routes: 'Static Routes',
|
||||
wifis: 'Wi-Fi Interfaces',
|
||||
addWifi: 'Add Wi-Fi',
|
||||
noWifis: 'No Wi-Fi interfaces added yet.',
|
||||
downloadYaml: 'Download YAML',
|
||||
copyClipboard: 'Copy',
|
||||
parameters: 'Parameters',
|
||||
mode: 'Mode',
|
||||
id: 'ID',
|
||||
link: 'Link',
|
||||
instructions: 'Installation Instructions',
|
||||
step1: '1. Copy or download the generated file.',
|
||||
step2: '2. Save the file to the following directory on your Linux server (default filename is usually 00-installer-config.yaml or 01-netcfg.yaml):',
|
||||
step3: '3. Secure the file permissions:',
|
||||
step4: '4. Test the configuration (Reverts on error):',
|
||||
step5: '5. If the test is successful, apply the configuration:',
|
||||
note: 'Note: Netplan processes files in alphabetical order. Remember to backup or remove old configuration files.'
|
||||
}
|
||||
},
|
||||
// Configuration Model
|
||||
config: {
|
||||
version: 2,
|
||||
renderer: 'networkd',
|
||||
ethernets: [],
|
||||
wifis: [],
|
||||
bridges: [],
|
||||
bonds: [],
|
||||
vlans: []
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
generatedYaml() {
|
||||
const network = {
|
||||
version: this.config.version,
|
||||
renderer: this.config.renderer
|
||||
};
|
||||
|
||||
// Helper to clean common fields
|
||||
const processCommon = (item) => {
|
||||
const obj = {};
|
||||
// DHCP
|
||||
if (item.dhcp4) {
|
||||
obj.dhcp4 = true;
|
||||
}
|
||||
|
||||
// Static IP & Gateway & DNS - only if not DHCP (usually, though netplan allows mixing but let's keep simple)
|
||||
// Actually netplan allows addresses with dhcp4: true (e.g. static + dhcp). But let's follow the UI logic:
|
||||
// if DHCP is unchecked, these fields are shown.
|
||||
if (!item.dhcp4) {
|
||||
if (item.addressesInput) {
|
||||
obj.addresses = item.addressesInput.split(',').map(s => {
|
||||
let addr = s.trim();
|
||||
if (addr && !addr.includes('/')) {
|
||||
addr += '/24'; // Default to /24 if missing
|
||||
}
|
||||
return addr;
|
||||
}).filter(s => s);
|
||||
}
|
||||
if (item.nameserversInput) {
|
||||
obj.nameservers = {
|
||||
addresses: item.nameserversInput.split(',').map(s => s.trim()).filter(s => s)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Advanced
|
||||
if (item.mtu) obj.mtu = item.mtu;
|
||||
if (item.macaddress) obj.macaddress = item.macaddress;
|
||||
|
||||
// Routes
|
||||
let finalRoutes = [];
|
||||
|
||||
// Deprecated gateway4 -> converted to default route
|
||||
if (!item.dhcp4 && item.gateway4) {
|
||||
finalRoutes.push({ to: 'default', via: item.gateway4 });
|
||||
}
|
||||
|
||||
// Custom Routes
|
||||
if (item.routes && item.routes.length > 0) {
|
||||
const validRoutes = item.routes.filter(r => r.to && r.via).map(r => ({ to: r.to, via: r.via }));
|
||||
finalRoutes = finalRoutes.concat(validRoutes);
|
||||
}
|
||||
|
||||
if (finalRoutes.length > 0) {
|
||||
obj.routes = finalRoutes;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
// Ethernets
|
||||
if (this.config.ethernets.length > 0) {
|
||||
network.ethernets = {};
|
||||
this.config.ethernets.forEach(eth => {
|
||||
if (eth.name) {
|
||||
network.ethernets[eth.name] = processCommon(eth);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Wi-Fis
|
||||
if (this.config.wifis.length > 0) {
|
||||
network.wifis = {};
|
||||
this.config.wifis.forEach(wifi => {
|
||||
if (!wifi.name) return;
|
||||
const obj = processCommon(wifi);
|
||||
if (wifi.accessPoints && wifi.accessPoints.length > 0) {
|
||||
obj['access-points'] = {};
|
||||
wifi.accessPoints.forEach(ap => {
|
||||
if (ap.ssid) {
|
||||
obj['access-points'][ap.ssid] = {};
|
||||
if (ap.password) {
|
||||
obj['access-points'][ap.ssid].password = ap.password;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
network.wifis[wifi.name] = obj;
|
||||
});
|
||||
}
|
||||
|
||||
// Bridges
|
||||
if (this.config.bridges.length > 0) {
|
||||
network.bridges = {};
|
||||
this.config.bridges.forEach(bridge => {
|
||||
if (!bridge.name) return;
|
||||
const obj = processCommon(bridge);
|
||||
if (bridge.interfacesInput) {
|
||||
obj.interfaces = bridge.interfacesInput.split(',').map(s => s.trim()).filter(s => s);
|
||||
}
|
||||
if (bridge.parameters) {
|
||||
obj.parameters = {};
|
||||
if (bridge.parameters.stp !== null) obj.parameters.stp = bridge.parameters.stp;
|
||||
if (bridge.parameters.forwardDelay !== null && bridge.parameters.forwardDelay !== '') obj.parameters['forward-delay'] = bridge.parameters.forwardDelay;
|
||||
}
|
||||
network.bridges[bridge.name] = obj;
|
||||
});
|
||||
}
|
||||
|
||||
// Bonds
|
||||
if (this.config.bonds.length > 0) {
|
||||
network.bonds = {};
|
||||
this.config.bonds.forEach(bond => {
|
||||
if (!bond.name) return;
|
||||
const obj = processCommon(bond);
|
||||
if (bond.interfacesInput) {
|
||||
obj.interfaces = bond.interfacesInput.split(',').map(s => s.trim()).filter(s => s);
|
||||
}
|
||||
if (bond.parameters) {
|
||||
obj.parameters = {};
|
||||
if (bond.parameters.mode) obj.parameters.mode = bond.parameters.mode;
|
||||
}
|
||||
network.bonds[bond.name] = obj;
|
||||
});
|
||||
}
|
||||
|
||||
// VLANs
|
||||
if (this.config.vlans.length > 0) {
|
||||
network.vlans = {};
|
||||
this.config.vlans.forEach(vlan => {
|
||||
if (!vlan.name) return;
|
||||
const obj = processCommon(vlan);
|
||||
if (vlan.id) obj.id = vlan.id;
|
||||
if (vlan.link) obj.link = vlan.link;
|
||||
network.vlans[vlan.name] = obj;
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Using js-yaml from CDN
|
||||
if (typeof jsyaml !== 'undefined') {
|
||||
return jsyaml.dump({ network: network }, { indent: 2, noRefs: true });
|
||||
} else {
|
||||
return 'js-yaml library not loaded.';
|
||||
}
|
||||
} catch (e) {
|
||||
return 'Error generating YAML: ' + e.message;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
t(key) {
|
||||
return this.translations[this.currentLang][key] || key;
|
||||
},
|
||||
toggleLanguage() {
|
||||
this.currentLang = this.currentLang === 'tr' ? 'en' : 'tr';
|
||||
},
|
||||
toggleTheme() {
|
||||
this.isDark = !this.isDark;
|
||||
if (this.isDark) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
},
|
||||
createInterface(type) {
|
||||
const base = {
|
||||
dhcp4: true,
|
||||
addressesInput: '',
|
||||
gateway4: '',
|
||||
nameserversInput: '',
|
||||
mtu: null,
|
||||
macaddress: '',
|
||||
routes: [],
|
||||
showAdvanced: false
|
||||
};
|
||||
if (type === 'ethernet') {
|
||||
return { ...base, name: 'eth' + this.config.ethernets.length };
|
||||
}
|
||||
if (type === 'wifi') {
|
||||
return { ...base, name: 'wlan' + this.config.wifis.length, accessPoints: [{ ssid: '', password: '' }] };
|
||||
}
|
||||
if (type === 'bridge') {
|
||||
return { ...base, name: 'br' + this.config.bridges.length, interfacesInput: '', parameters: { stp: false, forwardDelay: 0 } };
|
||||
}
|
||||
if (type === 'bond') {
|
||||
return { ...base, name: 'bond' + this.config.bonds.length, interfacesInput: '', parameters: { mode: 'active-backup' } };
|
||||
}
|
||||
if (type === 'vlan') {
|
||||
return { ...base, name: 'vlan' + this.config.vlans.length, id: 10, link: '' };
|
||||
}
|
||||
},
|
||||
addEthernet() {
|
||||
this.config.ethernets.push(this.createInterface('ethernet'));
|
||||
},
|
||||
removeEthernet(index) {
|
||||
this.config.ethernets.splice(index, 1);
|
||||
},
|
||||
addWifi() {
|
||||
this.config.wifis.push(this.createInterface('wifi'));
|
||||
},
|
||||
removeWifi(index) {
|
||||
this.config.wifis.splice(index, 1);
|
||||
},
|
||||
addBridge() {
|
||||
this.config.bridges.push(this.createInterface('bridge'));
|
||||
},
|
||||
removeBridge(index) {
|
||||
this.config.bridges.splice(index, 1);
|
||||
},
|
||||
addBond() {
|
||||
this.config.bonds.push(this.createInterface('bond'));
|
||||
},
|
||||
removeBond(index) {
|
||||
this.config.bonds.splice(index, 1);
|
||||
},
|
||||
addVlan() {
|
||||
this.config.vlans.push(this.createInterface('vlan'));
|
||||
},
|
||||
removeVlan(index) {
|
||||
this.config.vlans.splice(index, 1);
|
||||
},
|
||||
async copyToClipboard() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(this.generatedYaml);
|
||||
// alert('Kopyalandı!');
|
||||
} catch (err) {
|
||||
console.error('Failed to copy: ', err);
|
||||
}
|
||||
},
|
||||
downloadYaml() {
|
||||
const blob = new Blob([this.generatedYaml], { type: 'text/yaml' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = '01-netcfg.yaml';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.addEthernet();
|
||||
}
|
||||
}).mount('#app');
|
||||
668
index.html
Normal file
668
index.html
Normal file
@@ -0,0 +1,668 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="tr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Netplan Yapılandırma Oluşturucu</title>
|
||||
<!-- Tailwind CSS -->
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<!-- Vue.js -->
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<!-- js-yaml -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"></script>
|
||||
<!-- Custom CSS -->
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
<script>
|
||||
tailwind.config = {
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'sans-serif'],
|
||||
},
|
||||
colors: {
|
||||
primary: {
|
||||
50: '#f0fdf4',
|
||||
100: '#dcfce7',
|
||||
200: '#bbf7d0',
|
||||
300: '#86efac',
|
||||
400: '#4ade80',
|
||||
500: '#22c55e',
|
||||
600: '#16a34a',
|
||||
700: '#15803d',
|
||||
800: '#166534',
|
||||
900: '#14532d',
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-50 dark:bg-gray-900 text-gray-800 dark:text-gray-200 transition-colors duration-300 font-sans">
|
||||
<div id="app" class="min-h-screen flex flex-col">
|
||||
<!-- Header -->
|
||||
<header class="bg-white dark:bg-gray-800 shadow-sm sticky top-0 z-50">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="bg-primary-600 text-white p-2 rounded-lg shadow-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24"
|
||||
stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
|
||||
</svg>
|
||||
</div>
|
||||
<h1
|
||||
class="text-2xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-primary-600 to-primary-400">
|
||||
Netplan Config Generator
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<button @click="toggleTheme"
|
||||
class="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
||||
<span v-if="isDark">🌞</span>
|
||||
<span v-else>🌙</span>
|
||||
</button>
|
||||
<button @click="toggleLanguage"
|
||||
class="flex items-center space-x-1 px-3 py-1.5 rounded-md text-sm font-medium bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors">
|
||||
<span>{{ currentLang === 'tr' ? '🇹🇷 TR' : '🇬🇧 EN' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="flex-grow container mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8">
|
||||
|
||||
<!-- Configuration Form -->
|
||||
<div class="lg:col-span-7 space-y-6">
|
||||
|
||||
<!-- Global Settings -->
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
|
||||
<div
|
||||
class="px-6 py-4 border-b border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 flex justify-between items-center">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<span class="mr-2">🌐</span> {{ t('globalSettings') }}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="p-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{
|
||||
t('renderer') }}</label>
|
||||
<select v-model="config.renderer"
|
||||
class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-primary-500 focus:ring-primary-500 shadow-sm transition-colors">
|
||||
<option value="networkd">networkd (Server Default)</option>
|
||||
<option value="NetworkManager">NetworkManager (Desktop Default)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">{{
|
||||
t('version') }}</label>
|
||||
<input type="number" v-model.number="config.version"
|
||||
class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 focus:border-primary-500 focus:ring-primary-500 shadow-sm"
|
||||
readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Ethernets -->
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
|
||||
<div
|
||||
class="px-6 py-4 border-b border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 flex justify-between items-center">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<span class="mr-2">🔌</span> {{ t('ethernets') }}
|
||||
</h2>
|
||||
<button @click="addEthernet"
|
||||
class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 transition-colors">
|
||||
<svg class="-ml-0.5 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{ t('addInterface') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="config.ethernets.length === 0"
|
||||
class="p-8 text-center text-gray-500 dark:text-gray-400">
|
||||
{{ t('noInterfaces') }}
|
||||
</div>
|
||||
|
||||
<div v-else class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<div v-for="(eth, index) in config.ethernets" :key="index"
|
||||
class="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/30 transition-colors relative group">
|
||||
<button @click="removeEthernet(index)"
|
||||
class="absolute top-4 right-4 text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all p-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('interfaceName') }}</label>
|
||||
<input type="text" v-model="eth.name" placeholder="e.g. eth0"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div class="flex items-center pt-6">
|
||||
<input type="checkbox" v-model="eth.dhcp4" :id="'dhcp4-'+index"
|
||||
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded">
|
||||
<label :for="'dhcp4-'+index"
|
||||
class="ml-2 block text-sm text-gray-900 dark:text-gray-300">DHCPv4</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!eth.dhcp4" class="space-y-4 animate-fade-in-down">
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('ipAddresses') }}</label>
|
||||
<div class="flex space-x-2">
|
||||
<input type="text" v-model="eth.addressesInput"
|
||||
placeholder="192.168.1.10/24, 10.0.0.5/24"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-gray-400">{{ t('commaSeparated') }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('gateway4') }}</label>
|
||||
<input type="text" v-model="eth.gateway4" placeholder="192.168.1.1"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('nameservers') }}</label>
|
||||
<input type="text" v-model="eth.nameserversInput" placeholder="8.8.8.8, 1.1.1.1"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-700">
|
||||
<button @click="eth.showAdvanced = !eth.showAdvanced"
|
||||
class="text-xs text-primary-600 hover:text-primary-700 dark:text-primary-400 dark:hover:text-primary-300 font-medium flex items-center">
|
||||
<span class="mr-1">{{ eth.showAdvanced ? '−' : '+' }}</span> {{
|
||||
t('advancedSettings') }}
|
||||
</button>
|
||||
|
||||
<div v-if="eth.showAdvanced"
|
||||
class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4 animate-fade-in-down">
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">MTU</label>
|
||||
<input type="number" v-model.number="eth.mtu" placeholder="1500"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">MAC
|
||||
Address (Optional)</label>
|
||||
<input type="text" v-model="eth.macaddress" placeholder="XX:XX:XX:XX:XX:XX"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div class="col-span-1 md:col-span-2">
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('routes') }}</label>
|
||||
<div v-for="(route, rIndex) in eth.routes" :key="rIndex"
|
||||
class="flex space-x-2 mb-2">
|
||||
<input type="text" v-model="route.to" placeholder="To: 10.0.0.0/24"
|
||||
class="flex-1 rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
<input type="text" v-model="route.via" placeholder="Via: 192.168.1.1"
|
||||
class="flex-1 rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
<button @click="eth.routes.splice(rIndex, 1)"
|
||||
class="text-red-500 hover:text-red-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5"
|
||||
viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<button @click="eth.routes.push({to: '', via: ''})"
|
||||
class="text-xs text-primary-600 hover:text-primary-700 font-medium flex items-center mt-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 mr-1"
|
||||
viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
Add Route
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Wi-Fi Settings -->
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
|
||||
<div
|
||||
class="px-6 py-4 border-b border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 flex justify-between items-center">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<span class="mr-2">📶</span> {{ t('wifis') }}
|
||||
</h2>
|
||||
<button @click="addWifi"
|
||||
class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none transition-colors">
|
||||
<svg class="-ml-0.5 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{ t('addWifi') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="config.wifis.length === 0" class="p-8 text-center text-gray-500 dark:text-gray-400">
|
||||
{{ t('noWifis') }}
|
||||
</div>
|
||||
<div v-else class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<div v-for="(wifi, index) in config.wifis" :key="index"
|
||||
class="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/30 transition-colors relative group">
|
||||
<button @click="removeWifi(index)"
|
||||
class="absolute top-4 right-4 text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all p-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('interfaceName') }}</label>
|
||||
<input type="text" v-model="wifi.name" placeholder="wlan0"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">SSID</label>
|
||||
<input type="text" v-model="wifi.accessPoints[0].ssid"
|
||||
placeholder="Network Name"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">Password</label>
|
||||
<input type="password" v-model="wifi.accessPoints[0].password" placeholder="Key"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div class="flex items-center pt-6">
|
||||
<input type="checkbox" v-model="wifi.dhcp4" :id="'wifi-dhcp4-'+index"
|
||||
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded">
|
||||
<label :for="'wifi-dhcp4-'+index"
|
||||
class="ml-2 block text-sm text-gray-900 dark:text-gray-300">DHCPv4</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bridges -->
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
|
||||
<div
|
||||
class="px-6 py-4 border-b border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 flex justify-between items-center">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<span class="mr-2">🌉</span> {{ t('bridges') }}
|
||||
</h2>
|
||||
<button @click="addBridge"
|
||||
class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none transition-colors">
|
||||
<svg class="-ml-0.5 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{ t('addBridge') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="config.bridges.length === 0"
|
||||
class="p-8 text-center text-gray-500 dark:text-gray-400">
|
||||
No Bridges added yet.
|
||||
</div>
|
||||
<div v-else class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<div v-for="(br, index) in config.bridges" :key="index"
|
||||
class="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/30 transition-colors relative group">
|
||||
<button @click="removeBridge(index)"
|
||||
class="absolute top-4 right-4 text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all p-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('interfaceName') }}</label>
|
||||
<input type="text" v-model="br.name" placeholder="br0"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('interfaces') }}</label>
|
||||
<input type="text" v-model="br.interfacesInput" placeholder="eth0, eth1"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div class="flex items-center pt-6">
|
||||
<input type="checkbox" v-model="br.dhcp4" :id="'br-dhcp4-'+index"
|
||||
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded">
|
||||
<label :for="'br-dhcp4-'+index"
|
||||
class="ml-2 block text-sm text-gray-900 dark:text-gray-300">DHCPv4</label>
|
||||
</div>
|
||||
<div v-if="!br.dhcp4">
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('ipAddresses') }}</label>
|
||||
<input type="text" v-model="br.addressesInput" placeholder="192.168.1.10/24"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bonds -->
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
|
||||
<div
|
||||
class="px-6 py-4 border-b border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 flex justify-between items-center">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<span class="mr-2">🔗</span> {{ t('bonds') }}
|
||||
</h2>
|
||||
<button @click="addBond"
|
||||
class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none transition-colors">
|
||||
<svg class="-ml-0.5 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{ t('addBond') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="config.bonds.length === 0" class="p-8 text-center text-gray-500 dark:text-gray-400">
|
||||
No Bonds added yet.
|
||||
</div>
|
||||
<div v-else class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<div v-for="(bond, index) in config.bonds" :key="index"
|
||||
class="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/30 transition-colors relative group">
|
||||
<button @click="removeBond(index)"
|
||||
class="absolute top-4 right-4 text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all p-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('interfaceName') }}</label>
|
||||
<input type="text" v-model="bond.name" placeholder="bond0"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('interfaces') }}</label>
|
||||
<input type="text" v-model="bond.interfacesInput" placeholder="eth0, eth1"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('mode') }}</label>
|
||||
<select v-model="bond.parameters.mode"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
<option value="balance-rr">balance-rr</option>
|
||||
<option value="active-backup">active-backup</option>
|
||||
<option value="balance-xor">balance-xor</option>
|
||||
<option value="broadcast">broadcast</option>
|
||||
<option value="802.3ad">802.3ad</option>
|
||||
<option value="balance-tlb">balance-tlb</option>
|
||||
<option value="balance-alb">balance-alb</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center pt-6">
|
||||
<input type="checkbox" v-model="bond.dhcp4" :id="'bond-dhcp4-'+index"
|
||||
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded">
|
||||
<label :for="'bond-dhcp4-'+index"
|
||||
class="ml-2 block text-sm text-gray-900 dark:text-gray-300">DHCPv4</label>
|
||||
</div>
|
||||
<div v-if="!bond.dhcp4">
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('ipAddresses') }}</label>
|
||||
<input type="text" v-model="bond.addressesInput" placeholder="192.168.1.10/24"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VLANs -->
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
|
||||
<div
|
||||
class="px-6 py-4 border-b border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 flex justify-between items-center">
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<span class="mr-2">🏷️</span> {{ t('vlans') }}
|
||||
</h2>
|
||||
<button @click="addVlan"
|
||||
class="inline-flex items-center px-3 py-1.5 border border-transparent text-xs font-medium rounded-md shadow-sm text-white bg-primary-600 hover:bg-primary-700 focus:outline-none transition-colors">
|
||||
<svg class="-ml-0.5 mr-2 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{ t('addVlan') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="config.vlans.length === 0" class="p-8 text-center text-gray-500 dark:text-gray-400">
|
||||
No VLANs added yet.
|
||||
</div>
|
||||
<div v-else class="divide-y divide-gray-100 dark:divide-gray-700">
|
||||
<div v-for="(vlan, index) in config.vlans" :key="index"
|
||||
class="p-6 hover:bg-gray-50 dark:hover:bg-gray-700/30 transition-colors relative group">
|
||||
<button @click="removeVlan(index)"
|
||||
class="absolute top-4 right-4 text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-all p-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('interfaceName') }}</label>
|
||||
<input type="text" v-model="vlan.name" placeholder="vlan10"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('id') }}</label>
|
||||
<input type="number" v-model.number="vlan.id" placeholder="10"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div>
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('link') }}</label>
|
||||
<input type="text" v-model="vlan.link" placeholder="eth0"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
<div class="flex items-center pt-6">
|
||||
<input type="checkbox" v-model="vlan.dhcp4" :id="'vlan-dhcp4-'+index"
|
||||
class="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded">
|
||||
<label :for="'vlan-dhcp4-'+index"
|
||||
class="ml-2 block text-sm text-gray-900 dark:text-gray-300">DHCPv4</label>
|
||||
</div>
|
||||
<div v-if="!vlan.dhcp4">
|
||||
<label
|
||||
class="block text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider mb-1">{{
|
||||
t('ipAddresses') }}</label>
|
||||
<input type="text" v-model="vlan.addressesInput" placeholder="192.168.10.1/24"
|
||||
class="block w-full rounded-md border-gray-300 dark:border-gray-600 dark:bg-gray-700 shadow-sm focus:border-primary-500 focus:ring-primary-500 sm:text-sm">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Preview Sidebar -->
|
||||
<div class="lg:col-span-5 space-y-6">
|
||||
<div class="sticky top-24">
|
||||
<div class="bg-gray-900 rounded-xl shadow-2xl overflow-hidden border border-gray-700">
|
||||
<div
|
||||
class="bg-gray-800 px-4 py-3 border-b border-gray-700 flex justify-between items-center">
|
||||
<div class="flex space-x-2">
|
||||
<div class="w-3 h-3 rounded-full bg-red-500"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-yellow-500"></div>
|
||||
<div class="w-3 h-3 rounded-full bg-green-500"></div>
|
||||
</div>
|
||||
<span class="text-xs text-gray-400 font-mono">01-netcfg.yaml</span>
|
||||
</div>
|
||||
<div class="relative group">
|
||||
<pre
|
||||
class="p-6 text-sm font-mono leading-relaxed text-gray-300 overflow-x-auto min-h-[400px] max-h-[calc(100vh-300px)] scrollbar-thin scrollbar-thumb-gray-600 scrollbar-track-transparent"><code>{{ generatedYaml }}</code></pre>
|
||||
|
||||
<div
|
||||
class="absolute top-4 right-4 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button @click="copyToClipboard"
|
||||
class="bg-gray-700 hover:bg-gray-600 text-white p-2 rounded-lg shadow-lg transition-colors"
|
||||
title="Kopyala">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex space-x-4">
|
||||
<button @click="downloadYaml"
|
||||
class="flex-1 bg-primary-600 hover:bg-primary-700 text-white font-medium py-3 px-4 rounded-xl shadow-lg shadow-primary-500/30 flex justify-center items-center transition-all transform hover:-translate-y-0.5">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" viewBox="0 0 20 20"
|
||||
fill="currentColor">
|
||||
<path fill-rule="evenodd"
|
||||
d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
{{ t('downloadYaml') }}
|
||||
</button>
|
||||
<button @click="copyToClipboard"
|
||||
class="flex-1 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-200 font-medium py-3 px-4 rounded-xl shadow-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 flex justify-center items-center transition-all transform hover:-translate-y-0.5">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
|
||||
</svg>
|
||||
{{ t('copyClipboard') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Instructions -->
|
||||
<div
|
||||
class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700 overflow-hidden">
|
||||
<div
|
||||
class="px-6 py-4 border-b border-gray-100 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50">
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white flex items-center">
|
||||
<span class="mr-2">📝</span> {{ t('instructions') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-6 text-sm text-gray-600 dark:text-gray-300 space-y-4">
|
||||
<p>{{ t('step1') }}</p>
|
||||
|
||||
<div>
|
||||
<p class="mb-2">{{ t('step2') }}</p>
|
||||
<div
|
||||
class="bg-gray-100 dark:bg-gray-900 p-2 rounded border border-gray-200 dark:border-gray-700 font-mono text-xs select-all break-all">
|
||||
/etc/netplan/01-netcfg.yaml
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="mb-2">{{ t('step3') }}</p>
|
||||
<div
|
||||
class="bg-gray-100 dark:bg-gray-900 p-2 rounded border border-gray-200 dark:border-gray-700 font-mono text-xs select-all">
|
||||
sudo chmod 600 /etc/netplan/01-netcfg.yaml
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="mb-2">{{ t('step4') }}</p>
|
||||
<div
|
||||
class="bg-gray-100 dark:bg-gray-900 p-2 rounded border border-gray-200 dark:border-gray-700 font-mono text-xs select-all">
|
||||
sudo netplan try
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p class="mb-2">{{ t('step5') }}</p>
|
||||
<div
|
||||
class="bg-gray-100 dark:bg-gray-900 p-2 rounded border border-gray-200 dark:border-gray-700 font-mono text-xs select-all">
|
||||
sudo netplan apply
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900/30 border-l-4 border-yellow-400 p-3">
|
||||
<p class="text-xs text-yellow-700 dark:text-yellow-200">
|
||||
{{ t('note') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="mt-auto py-8 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
<p>Netplan Config Generator © 2024</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
34
style.css
Normal file
34
style.css
Normal file
@@ -0,0 +1,34 @@
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.animate-fade-in-down {
|
||||
animation: fadeInDown 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeInDown {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom scrollbar for code block */
|
||||
.scrollbar-thin::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb {
|
||||
background-color: #4b5563;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #6b7280;
|
||||
}
|
||||
9
yapi.md
Normal file
9
yapi.md
Normal file
@@ -0,0 +1,9 @@
|
||||
ubuntu gibi linux dağıtımlarında kullanılan netplan için Web tabanlı bir yapılandırma yaml dosyası oluşturucu hazırlamak istiyorum.
|
||||
|
||||
- Proje IP adresleri gibi bilgileri kolay ve görsel bir şekilde girildikten sonra yaml formatında bir yapılandırma dosyası oluşturacak.
|
||||
- Oluşturulan yaml dosyasını kopyalama ve indirme imkanı sunacak.
|
||||
- netplan yapısının Gelişmiş özellikleri tanımlanabilecek ve ona gerekli komut ve benzeri yönlendirmeleri yapacak
|
||||
- Sadece IP tanımı değil, DNS, Vlan, Bridge, Bond, Proxy, Static route, DHCP, VPN, Firewall, NAT, Port Forwarding gibi gelişmiş ayarlarda istenirse eklenebilecek
|
||||
- Uygulama ana dili Türkçe olacak ve hazıarlama sürecindeki cevaplar ve çözümlerde tüekçe olarak verilecek.
|
||||
- Tema desteği olabilir, ek dil olarak ingilizce de eklenebilir
|
||||
|
||||
Reference in New Issue
Block a user