first commit
This commit is contained in:
501
app/pages/admin/security/index.vue
Normal file
501
app/pages/admin/security/index.vue
Normal file
@@ -0,0 +1,501 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Güvenlik & Ağ Yönetimi</h2>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs mb-4" id="securityTab" role="tablist">
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link active" id="whitelist-tab" data-bs-toggle="tab" data-bs-target="#whitelist"
|
||||
type="button" role="tab" aria-controls="whitelist" aria-selected="true">CORS Whitelist</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="blacklist-tab" data-bs-toggle="tab" data-bs-target="#blacklist"
|
||||
type="button" role="tab" aria-controls="blacklist" aria-selected="false">CORS Blacklist</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="ratelimit-tab" data-bs-toggle="tab" data-bs-target="#ratelimit"
|
||||
type="button" role="tab" aria-controls="ratelimit" aria-selected="false">Rate Limit</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="securityTabContent">
|
||||
<!-- WHITELIST TAB -->
|
||||
<div class="tab-pane fade show active" id="whitelist" role="tabpanel" aria-labelledby="whitelist-tab">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center bg-white">
|
||||
<h5 class="mb-0">Whitelist Origins</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="form-check form-switch me-3 mb-0">
|
||||
<input class="form-check-input" type="checkbox" v-model="showDeleted"
|
||||
id="whitelistDeleted">
|
||||
<label class="form-check-label" for="whitelistDeleted">Silinenler</label>
|
||||
</div>
|
||||
<button class="btn btn-success btn-sm" @click="openModal('whitelist')"><i
|
||||
class="fas fa-plus me-2"></i> Yeni
|
||||
Ekle</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Origin</th>
|
||||
<th>Açıklama</th>
|
||||
<th>Durum</th>
|
||||
<th class="text-end" width="150">İşlemler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in whitelistItems" :key="item.ID">
|
||||
<td>{{ item.origin }}</td>
|
||||
<td>{{ item.description }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="item.is_active ? 'bg-success' : 'bg-secondary'">
|
||||
{{ item.is_active ? 'Aktif' : 'Pasif' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<template v-if="!item.DeletedAt">
|
||||
<button class="btn btn-outline-primary"
|
||||
@click="openModal('whitelist', item)" title="Düzenle">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-danger"
|
||||
@click="deleteItem('whitelist', item.ID)" title="Sil">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</template>
|
||||
<button v-else class="btn btn-outline-success"
|
||||
@click="restoreItem('whitelist', item.ID)" title="Geri Yükle">
|
||||
<i class="fas fa-trash-restore"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="whitelistItems.length === 0">
|
||||
<td colspan="4" class="text-center py-4">Kayıt bulunamadı.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BLACKLIST TAB -->
|
||||
<div class="tab-pane fade" id="blacklist" role="tabpanel" aria-labelledby="blacklist-tab">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center bg-white">
|
||||
<h5 class="mb-0">Blacklist Origins</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="form-check form-switch me-3 mb-0">
|
||||
<input class="form-check-input" type="checkbox" v-model="showDeleted"
|
||||
id="blacklistDeleted">
|
||||
<label class="form-check-label" for="blacklistDeleted">Silinenler</label>
|
||||
</div>
|
||||
<button class="btn btn-success btn-sm" @click="openModal('blacklist')"><i
|
||||
class="fas fa-plus me-2"></i>
|
||||
Yeni
|
||||
Ekle</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Origin</th>
|
||||
<th>Sebep</th>
|
||||
<th>Durum</th>
|
||||
<th class="text-end" width="150">İşlemler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in blacklistItems" :key="item.ID">
|
||||
<td>{{ item.origin }}</td>
|
||||
<td>{{ item.reason }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="item.is_active ? 'bg-danger' : 'bg-secondary'">
|
||||
{{ item.is_active ? 'Aktif' : 'Pasif' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<template v-if="!item.DeletedAt">
|
||||
<button class="btn btn-outline-primary"
|
||||
@click="openModal('blacklist', item)" title="Düzenle">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-danger"
|
||||
@click="deleteItem('blacklist', item.ID)" title="Sil">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</template>
|
||||
<button v-else class="btn btn-outline-success"
|
||||
@click="restoreItem('blacklist', item.ID)" title="Geri Yükle">
|
||||
<i class="fas fa-trash-restore"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="blacklistItems.length === 0">
|
||||
<td colspan="4" class="text-center py-4">Kayıt bulunamadı.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RATE LIMIT TAB -->
|
||||
<div class="tab-pane fade" id="ratelimit" role="tabpanel" aria-labelledby="ratelimit-tab">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center bg-white">
|
||||
<h5 class="mb-0">Rate Limits</h5>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="form-check form-switch me-3 mb-0">
|
||||
<input class="form-check-input" type="checkbox" v-model="showDeleted"
|
||||
id="ratelimitDeleted">
|
||||
<label class="form-check-label" for="ratelimitDeleted">Silinenler</label>
|
||||
</div>
|
||||
<button class="btn btn-success btn-sm" @click="openModal('ratelimit')"><i
|
||||
class="fas fa-plus me-2"></i>
|
||||
Yeni
|
||||
Ekle</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>İsim</th>
|
||||
<th>Max İstek / Süre (sn)</th>
|
||||
<th>Açıklama</th>
|
||||
<th>Durum</th>
|
||||
<th class="text-end" width="150">İşlemler</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in rateLimitItems" :key="item.ID">
|
||||
<td>{{ item.name }}</td>
|
||||
<td>{{ item.max_requests }} / {{ item.window_seconds }}sn</td>
|
||||
<td>{{ item.description }}</td>
|
||||
<td>
|
||||
<span class="badge" :class="item.is_active ? 'bg-success' : 'bg-secondary'">
|
||||
{{ item.is_active ? 'Aktif' : 'Pasif' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<template v-if="!item.DeletedAt">
|
||||
<button class="btn btn-outline-primary"
|
||||
@click="openModal('ratelimit', item)" title="Düzenle">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button class="btn btn-outline-danger"
|
||||
@click="deleteItem('ratelimit', item.ID)" title="Sil">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</template>
|
||||
<button v-else class="btn btn-outline-success"
|
||||
@click="restoreItem('ratelimit', item.ID)" title="Geri Yükle">
|
||||
<i class="fas fa-trash-restore"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="rateLimitItems.length === 0">
|
||||
<td colspan="5" class="text-center py-4">Kayıt bulunamadı.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- MODAL -->
|
||||
<div class="modal fade" id="securityModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{{ modalTitle }}</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form @submit.prevent="saveItem">
|
||||
<!-- Common Fields -->
|
||||
<div v-if="activeTab === 'whitelist' || activeTab === 'blacklist'" class="mb-3">
|
||||
<label class="form-label">Origin (URL)</label>
|
||||
<input v-model="formData.origin" type="text" class="form-control"
|
||||
placeholder="http://example.com" required>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'whitelist'" class="mb-3">
|
||||
<label class="form-label">Açıklama</label>
|
||||
<input v-model="formData.description" type="text" class="form-control">
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'blacklist'" class="mb-3">
|
||||
<label class="form-label">Sebep</label>
|
||||
<input v-model="formData.reason" type="text" class="form-control">
|
||||
</div>
|
||||
|
||||
<!-- Rate Limit Fields -->
|
||||
<div v-if="activeTab === 'ratelimit'" class="mb-3">
|
||||
<label class="form-label">İsim (Endpoint/Key)</label>
|
||||
<input v-model="formData.name" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div v-if="activeTab === 'ratelimit'" class="row">
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Max İstek</label>
|
||||
<input v-model="formData.max_requests" type="number" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-md-6 mb-3">
|
||||
<label class="form-label">Süre (Saniye)</label>
|
||||
<input v-model="formData.window_seconds" type="number" class="form-control"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activeTab === 'ratelimit'" class="mb-3">
|
||||
<label class="form-label">Açıklama</label>
|
||||
<input v-model="formData.description" type="text" class="form-control">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" v-model="formData.is_active"
|
||||
id="isActiveCheck">
|
||||
<label class="form-check-label" for="isActiveCheck">Aktif</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-end">
|
||||
<button type="button" class="btn btn-secondary me-2"
|
||||
data-bs-dismiss="modal">İptal</button>
|
||||
<button type="submit" class="btn btn-primary" :disabled="loading">
|
||||
<span v-if="loading" class="spinner-border spinner-border-sm me-2"></span>
|
||||
Kaydet
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CorsWhitelistItem, CorsBlacklistItem, RateLimitItem } from '~~/types/security';
|
||||
import Swal from 'sweetalert2';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'admin',
|
||||
middleware: 'admin'
|
||||
});
|
||||
|
||||
const config = useRuntimeConfig();
|
||||
const { data: authData } = useAuth();
|
||||
const loading = ref(false);
|
||||
|
||||
const activeTab = ref<'whitelist' | 'blacklist' | 'ratelimit'>('whitelist');
|
||||
const showDeleted = ref(false); // Global toggle for simplicity, or could be per tab
|
||||
const whitelistItems = ref<CorsWhitelistItem[]>([]);
|
||||
const blacklistItems = ref<CorsBlacklistItem[]>([]);
|
||||
const rateLimitItems = ref<RateLimitItem[]>([]);
|
||||
|
||||
const formData = ref<any>({});
|
||||
const isEditing = ref(false);
|
||||
let securityModal: any = null;
|
||||
|
||||
const modalTitle = computed(() => {
|
||||
const action = isEditing.value ? 'Düzenle' : 'Ekle';
|
||||
if (activeTab.value === 'whitelist') return `Whitelist ${action}`;
|
||||
if (activeTab.value === 'blacklist') return `Blacklist ${action}`;
|
||||
return `Rate Limit ${action}`;
|
||||
});
|
||||
|
||||
// Watch for tab or filter changes
|
||||
watch([activeTab, showDeleted], () => {
|
||||
fetchItems();
|
||||
});
|
||||
|
||||
// -- API HANDLERS --
|
||||
|
||||
const fetchItems = async () => {
|
||||
try {
|
||||
const query = showDeleted.value ? { deleted: 'true' } : {};
|
||||
const headers = { Authorization: `Bearer ${(authData.value as any)?.accessToken}` };
|
||||
|
||||
if (activeTab.value === 'whitelist') {
|
||||
const res = await $fetch<any>('/api/v1/admin/cors/whitelist', { baseURL: config.public.BASE_API_URL, headers, query });
|
||||
whitelistItems.value = res.items || [];
|
||||
} else if (activeTab.value === 'blacklist') {
|
||||
const res = await $fetch<any>('/api/v1/admin/cors/blacklist', { baseURL: config.public.BASE_API_URL, headers, query });
|
||||
blacklistItems.value = res.items || [];
|
||||
} else {
|
||||
const res = await $fetch<any>('/api/v1/admin/rate-limit', { baseURL: config.public.BASE_API_URL, headers, query });
|
||||
rateLimitItems.value = res.items || [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fetch error:', error);
|
||||
// Fallback or empty list on error
|
||||
if (activeTab.value === 'whitelist') whitelistItems.value = [];
|
||||
else if (activeTab.value === 'blacklist') blacklistItems.value = [];
|
||||
else rateLimitItems.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const openModal = (tab: 'whitelist' | 'blacklist' | 'ratelimit', item: any = null) => {
|
||||
activeTab.value = tab;
|
||||
if (item) {
|
||||
isEditing.value = true;
|
||||
formData.value = { ...item };
|
||||
} else {
|
||||
isEditing.value = false;
|
||||
formData.value = { is_active: true };
|
||||
}
|
||||
|
||||
const modalEl = document.getElementById('securityModal');
|
||||
if (modalEl) {
|
||||
// @ts-ignore
|
||||
securityModal = new bootstrap.Modal(modalEl);
|
||||
securityModal.show();
|
||||
}
|
||||
};
|
||||
|
||||
const saveItem = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
let endpoint = '';
|
||||
if (activeTab.value === 'whitelist') endpoint = '/api/v1/admin/cors/whitelist';
|
||||
else if (activeTab.value === 'blacklist') endpoint = '/api/v1/admin/cors/blacklist';
|
||||
else endpoint = '/api/v1/admin/rate-limit';
|
||||
|
||||
let method: 'POST' | 'PUT' = 'POST';
|
||||
if (isEditing.value && formData.value.ID) {
|
||||
endpoint += `/${formData.value.ID}`;
|
||||
method = 'PUT';
|
||||
}
|
||||
|
||||
// Convert numbers for RateLimit
|
||||
if (activeTab.value === 'ratelimit') {
|
||||
formData.value.max_requests = Number(formData.value.max_requests);
|
||||
formData.value.window_seconds = Number(formData.value.window_seconds);
|
||||
}
|
||||
|
||||
await $fetch(endpoint, {
|
||||
method,
|
||||
baseURL: config.public.BASE_API_URL,
|
||||
headers: { Authorization: `Bearer ${(authData.value as any)?.accessToken}` },
|
||||
body: formData.value
|
||||
});
|
||||
|
||||
if (securityModal) securityModal.hide();
|
||||
Swal.fire('Başarılı', 'İşlem başarıyla tamamlandı.', 'success');
|
||||
fetchItems();
|
||||
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Swal.fire('Hata', 'İşlem başarısız.', 'error');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteItem = async (tab: string, id: number) => {
|
||||
const result = await Swal.fire({
|
||||
title: 'Silme Tipi?',
|
||||
text: "Kayıt nasıl silinsin?",
|
||||
icon: 'warning',
|
||||
showCancelButton: true,
|
||||
showDenyButton: true,
|
||||
confirmButtonText: 'Soft Sil (Arşivle)',
|
||||
denyButtonText: 'Hard Sil (Kalıcı)',
|
||||
cancelButtonText: 'İptal'
|
||||
});
|
||||
|
||||
if (result.isDismissed) return;
|
||||
|
||||
const isHard = result.isDenied;
|
||||
|
||||
try {
|
||||
let endpoint = '';
|
||||
if (tab === 'whitelist') endpoint = `/api/v1/admin/cors/whitelist/${id}`;
|
||||
else if (tab === 'blacklist') endpoint = `/api/v1/admin/cors/blacklist/${id}`;
|
||||
else endpoint = `/api/v1/admin/rate-limit/${id}`;
|
||||
|
||||
if (isHard) endpoint += '/hard';
|
||||
|
||||
await $fetch(endpoint, {
|
||||
method: 'DELETE',
|
||||
baseURL: config.public.BASE_API_URL,
|
||||
headers: { Authorization: `Bearer ${(authData.value as any)?.accessToken}` }
|
||||
});
|
||||
|
||||
Swal.fire('Silindi!', isHard ? 'Kayıt kalıcı olarak silindi.' : 'Kayıt arşivlendi (soft delete).', 'success');
|
||||
fetchItems();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Swal.fire('Hata', 'Silme işlemi başarısız.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
const restoreItem = async (tab: string, id: number) => {
|
||||
const result = await Swal.fire({
|
||||
title: 'Emin misiniz?',
|
||||
text: "Kayıt geri yüklenecek.",
|
||||
icon: 'question',
|
||||
showCancelButton: true,
|
||||
confirmButtonText: 'Geri Yükle',
|
||||
cancelButtonText: 'İptal'
|
||||
});
|
||||
|
||||
if (!result.isConfirmed) return;
|
||||
|
||||
try {
|
||||
let endpoint = '';
|
||||
if (tab === 'whitelist') endpoint = `/api/v1/admin/cors/whitelist/${id}/restore`;
|
||||
else if (tab === 'blacklist') endpoint = `/api/v1/admin/cors/blacklist/${id}/restore`;
|
||||
else endpoint = `/api/v1/admin/rate-limit/${id}/restore`;
|
||||
|
||||
await $fetch(endpoint, {
|
||||
method: 'POST', // Assuming POST for restore action
|
||||
baseURL: config.public.BASE_API_URL,
|
||||
headers: { Authorization: `Bearer ${(authData.value as any)?.accessToken}` }
|
||||
});
|
||||
|
||||
Swal.fire('Başarılı', 'Kayıt geri yüklendi.', 'success');
|
||||
fetchItems();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
Swal.fire('Hata', 'Geri yükleme başarısız. Endpoint mevcut olmayabilir.', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
// Bootstrap tabs events
|
||||
const tabEls = document.querySelectorAll('button[data-bs-toggle="tab"]');
|
||||
tabEls.forEach(tabEl => {
|
||||
tabEl.addEventListener('shown.bs.tab', (event: any) => {
|
||||
const targetId = event.target.getAttribute('data-bs-target');
|
||||
if (targetId === '#whitelist') activeTab.value = 'whitelist';
|
||||
if (targetId === '#blacklist') activeTab.value = 'blacklist';
|
||||
if (targetId === '#ratelimit') activeTab.value = 'ratelimit';
|
||||
// fetchItems() is called by watcher
|
||||
});
|
||||
});
|
||||
|
||||
fetchItems(); // Initial fetch
|
||||
});
|
||||
</script>
|
||||
```
|
||||
Reference in New Issue
Block a user