first commit

This commit is contained in:
2026-02-18 11:38:06 +03:00
commit 1dbacf592c
5 changed files with 1086 additions and 0 deletions

31
README.md Normal file
View 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
View 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
View 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 &copy; 2024</p>
</footer>
</div>
<script src="app.js"></script>
</body>
</html>

34
style.css Normal file
View 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
View 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