first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:33:39 +03:00
commit 4362c3b83f
1991 changed files with 285411 additions and 0 deletions

View File

@@ -0,0 +1,538 @@
<div class="container-fluid" hx-trigger="heroListChanged from:body" hx-get="/admin/content/settings" hx-target="this"
hx-swap="outerHTML">
<h2 class="mb-4">Ayarlar & Banner Yönetimi</h2>
<ul class="nav nav-tabs mb-4" id="settingsTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="general-tab" data-bs-toggle="tab" data-bs-target="#general"
type="button" role="tab">Genel Ayarlar</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="security-tab" data-bs-toggle="tab" data-bs-target="#security" type="button" role="tab">Güvenlik</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="hero-tab" data-bs-toggle="tab" data-bs-target="#hero" type="button"
role="tab">Banner (Hero) Yönetimi</button>
</li>
</ul>
<div class="tab-content" id="settingsTabContent">
<!-- General Settings Tab -->
<div class="tab-pane fade show active" id="general" role="tabpanel">
<div class="card border-0 shadow-sm">
<div class="card-body">
<form action="/admin/settings" method="POST" enctype="multipart/form-data">
<div class="row">
<div class="col-md-6 mb-3">
<label for="title" class="form-label">Site Başlığı</label>
<input type="text" class="form-control" id="title" name="title"
value="{{ .Setting.Title }}">
</div>
<div class="col-md-6 mb-3">
<label for="meta_title" class="form-label">Meta Başlık</label>
<input type="text" class="form-control" id="meta_title" name="meta_title"
value="{{ .Setting.MetaTitle }}">
</div>
</div>
<div class="mb-3">
<label for="meta_description" class="form-label">Meta Açıklama</label>
<textarea class="form-control" id="meta_description" name="meta_description"
rows="2">{{ .Setting.MetaDescription }}</textarea>
</div>
<div class="row">
<div class="col-md-6 mb-3">
<label for="email" class="form-label">E-posta</label>
<input type="email" class="form-control" id="email" name="email"
value="{{ .Setting.Email }}">
</div>
<div class="col-md-6 mb-3">
<label for="phone" class="form-label">Telefon</label>
<input type="text" class="form-control" id="phone" name="phone"
value="{{ .Setting.Phone }}">
</div>
</div>
<div class="mb-3">
<label for="address" class="form-label">Adres</label>
<textarea class="form-control" id="address" name="address"
rows="2">{{ .Setting.Address }}</textarea>
</div>
<div class="mb-3">
<label for="url" class="form-label">Site URL</label>
<input type="text" class="form-control" id="url" name="url" value="{{ .Setting.URL }}">
</div>
<h5 class="mt-4">Logo Ayarları</h5>
<div class="row">
<!-- White Logo (WLogo) -->
<div class="col-md-6">
<div class="card border p-3 mb-3">
<h6 class="mb-3">Beyaz Logo (Genellikle Koyu Zemin İçin)</h6>
<div class="mb-3">
<label class="form-label">Logo Yükle</label>
<input type="file" class="form-control" name="w_logo" accept="image/*">
{{ if .Setting.WLogo }}
<div class="mt-2 text-center p-2 logo-preview logo-preview-light border rounded">
<img src="{{ .Setting.WLogo }}" alt="WLogo" style="max-height: 50px;">
</div>
{{ end }}
</div>
<div class="row g-2">
<div class="col-6">
<label class="form-label small text-muted mb-0">Genişlik</label>
<input type="number" class="form-control form-control-sm" name="w_width"
placeholder="Otomatik" value="{{ .Setting.WWidth }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Yükseklik</label>
<input type="number" class="form-control form-control-sm" name="w_height"
placeholder="Otomatik" value="{{ .Setting.WHeight }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Kalite (1-100)</label>
<input type="number" class="form-control form-control-sm" name="w_quality"
placeholder="80" value="{{ .Setting.WQuality }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Format</label>
<select class="form-select form-select-sm" name="w_format">
<option value="avif" {{ if eq .Setting.WFormat "avif" }}selected{{ end
}}>AVIF (Önerilen)</option>
<option value="webp" {{ if eq .Setting.WFormat "webp" }}selected{{ end
}}>WebP</option>
<option value="png" {{ if eq .Setting.WFormat "png" }}selected{{ end }}>
PNG</option>
<option value="jpg" {{ if eq .Setting.WFormat "jpg" }}selected{{ end }}>
JPG</option>
</select>
</div>
</div>
</div>
</div>
<!-- Black Logo (BLogo) -->
<div class="col-md-6">
<div class="card border p-3 mb-3">
<h6 class="mb-3">Siyah Logo (Genellikle Beyaz Zemin İçin)</h6>
<div class="mb-3">
<label class="form-label">Logo Yükle</label>
<input type="file" class="form-control" name="b_logo" accept="image/*">
{{ if .Setting.BLogo }}
<div class="mt-2 text-center p-2 logo-preview logo-preview-light border rounded">
<img src="{{ .Setting.BLogo }}" alt="BLogo" style="max-height: 50px;">
</div>
{{ end }}
</div>
<div class="row g-2">
<div class="col-6">
<label class="form-label small text-muted mb-0">Genişlik</label>
<input type="number" class="form-control form-control-sm" name="b_width"
placeholder="Otomatik" value="{{ .Setting.BWidth }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Yükseklik</label>
<input type="number" class="form-control form-control-sm" name="b_height"
placeholder="Otomatik" value="{{ .Setting.BHeight }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Kalite (1-100)</label>
<input type="number" class="form-control form-control-sm" name="b_quality"
placeholder="80" value="{{ .Setting.BQuality }}">
</div>
<div class="col-6">
<label class="form-label small text-muted mb-0">Format</label>
<select class="form-select form-select-sm" name="b_format">
<option value="avif" {{ if eq .Setting.BFormat "avif" }}selected{{ end
}}>AVIF (Önerilen)</option>
<option value="webp" {{ if eq .Setting.BFormat "webp" }}selected{{ end
}}>WebP</option>
<option value="png" {{ if eq .Setting.BFormat "png" }}selected{{ end }}>
PNG</option>
<option value="jpg" {{ if eq .Setting.BFormat "jpg" }}selected{{ end }}>
JPG</option>
</select>
</div>
</div>
</div>
</div>
</div>
<!-- CORS and Rate Limit moved to Güvenlik tab -->
<h5 class="mt-4">Sosyal Medya</h5>
<div class="row">
<div class="col-md-4 mb-3">
<label class="form-label">Instagram</label>
<input type="text" class="form-control" name="instagram"
value="{{ .Setting.Instagram }}">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Twitter (X)</label>
<input type="text" class="form-control" name="x" value="{{ .Setting.X }}">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Facebook</label>
<input type="text" class="form-control" name="facebook" value="{{ .Setting.Facebook }}">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Whatsapp</label>
<input type="text" class="form-control" name="whatsapp" value="{{ .Setting.Whatsapp }}">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Linkedin</label>
<input type="text" class="form-control" name="linkedin" value="{{ .Setting.Linkedin }}">
</div>
<div class="col-md-4 mb-3">
<label class="form-label">Pinterest</label>
<input type="text" class="form-control" name="pinterest"
value="{{ .Setting.Pinterest }}">
</div>
</div>
<div class="mb-3 form-check form-switch">
<input class="form-check-input" type="checkbox" id="is_active" name="is_active" {{ if
.Setting.IsActive }}checked{{ end }}>
<label class="form-check-label" for="is_active">Site Aktif</label>
</div>
<button type="submit" class="btn btn-primary">Kaydet</button>
</form>
</div>
</div>
</div>
<!-- Security Tab (CORS / Rate Limits) -->
<div class="tab-pane fade" id="security" role="tabpanel">
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h5 class="mb-0">CORS Yönetimi</h5>
<div class="btn-group">
<a href="/admin/content/settings" class="btn btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/settings?deleted=true" class="btn btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
</div>
<div class="row mb-3">
<div class="col-md-6">
<div class="card border p-3 mb-3">
<h6 class="mb-3">Whitelist (İzinli Origin'ler)</h6>
{{ if .EditWhitelist.ID }}
<form action="/admin/settings/cors/whitelist/{{ .EditWhitelist.ID }}/update" method="POST" class="mb-3">
<div class="input-group mb-2">
<input type="text" name="origin" class="form-control" placeholder="https://example.com" required value="{{ .EditWhitelist.Origin }}">
</div>
<div class="mb-2">
<input type="text" name="description" class="form-control" placeholder="Açıklama (opsiyonel)" value="{{ .EditWhitelist.Description }}">
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="wl_active" name="is_active" {{ if .EditWhitelist.IsActive }}checked{{ end }}>
<label class="form-check-label" for="wl_active">Aktif</label>
</div>
<div class="d-flex gap-2 justify-content-end">
<a href="/admin/content/settings" class="btn btn-sm btn-secondary">İptal</a>
<button class="btn btn-sm btn-primary" type="submit">Güncelle</button>
</div>
</form>
{{ else }}
<form action="/admin/settings/cors/whitelist/create" method="POST" class="mb-3">
<div class="input-group mb-2">
<input type="text" name="origin" class="form-control" placeholder="https://example.com" required>
</div>
<div class="mb-2">
<input type="text" name="description" class="form-control" placeholder="Açıklama (opsiyonel)">
</div>
<div class="d-flex gap-2 justify-content-end">
<button class="btn btn-sm btn-primary" type="submit">Ekle</button>
</div>
</form>
{{ end }}
<div>
<ul class="list-group list-group-flush">
{{ if .CorsWhitelist }}
{{ range .CorsWhitelist }}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ .Origin }}</strong>
<div class="small text-muted">{{ .Description }}</div>
</div>
<div class="btn-group">
{{ if $.ShowDeleted }}
<form action="/admin/settings/cors/whitelist/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success">Geri Yükle</button>
</form>
{{ else }}
<a href="/admin/content/settings?edit_whitelist={{ .ID }}" class="btn btn-sm btn-outline-secondary">Düzenle</a>
<form action="/admin/settings/cors/whitelist/{{ .ID }}/delete" method="POST" onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger">Sil</button>
</form>
{{ end }}
</div>
</li>
{{ end }}
{{ else }}
<li class="list-group-item">Kayıt yok</li>
{{ end }}
</ul>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card border p-3 mb-3">
<h6 class="mb-3">Blacklist (Yasaklı Origin'ler)</h6>
{{ if .EditBlacklist.ID }}
<form action="/admin/settings/cors/blacklist/{{ .EditBlacklist.ID }}/update" method="POST" class="mb-3">
<div class="input-group mb-2">
<input type="text" name="origin" class="form-control" placeholder="https://bad.com" required value="{{ .EditBlacklist.Origin }}">
</div>
<div class="mb-2">
<input type="text" name="reason" class="form-control" placeholder="Sebep (opsiyonel)" value="{{ .EditBlacklist.Reason }}">
</div>
<div class="form-check form-switch mb-2">
<input class="form-check-input" type="checkbox" id="bl_active" name="is_active" {{ if .EditBlacklist.IsActive }}checked{{ end }}>
<label class="form-check-label" for="bl_active">Aktif</label>
</div>
<div class="d-flex gap-2 justify-content-end">
<a href="/admin/content/settings" class="btn btn-sm btn-secondary">İptal</a>
<button class="btn btn-sm btn-primary" type="submit">Güncelle</button>
</div>
</form>
{{ else }}
<form action="/admin/settings/cors/blacklist/create" method="POST" class="mb-3">
<div class="input-group mb-2">
<input type="text" name="origin" class="form-control" placeholder="https://bad.com" required>
</div>
<div class="mb-2">
<input type="text" name="reason" class="form-control" placeholder="Sebep (opsiyonel)">
</div>
<div class="d-flex gap-2 justify-content-end">
<button class="btn btn-sm btn-primary" type="submit">Ekle</button>
</div>
</form>
{{ end }}
<div>
<ul class="list-group list-group-flush">
{{ if .CorsBlacklist }}
{{ range .CorsBlacklist }}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ .Origin }}</strong>
<div class="small text-muted">{{ .Reason }}</div>
</div>
<div class="btn-group">
{{ if $.ShowDeleted }}
<form action="/admin/settings/cors/blacklist/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success">Geri Yükle</button>
</form>
{{ else }}
<a href="/admin/content/settings?edit_blacklist={{ .ID }}" class="btn btn-sm btn-outline-secondary">Düzenle</a>
<form action="/admin/settings/cors/blacklist/{{ .ID }}/delete" method="POST" onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger">Sil</button>
</form>
{{ end }}
</div>
</li>
{{ end }}
{{ else }}
<li class="list-group-item">Kayıt yok</li>
{{ end }}
</ul>
</div>
</div>
</div>
</div>
<h5 class="mb-3">Rate Limit Ayarları</h5>
<div class="card border p-3 mb-3">
{{ if .EditRateLimit.ID }}
<form action="/admin/settings/rate-limit/{{ .EditRateLimit.ID }}/update" method="POST" class="row g-2 mb-3">
<div class="col-md-3">
<input type="text" name="name" class="form-control" placeholder="İsim (ör. login)" required value="{{ .EditRateLimit.Name }}">
</div>
<div class="col-md-3">
<input type="number" name="max_requests" class="form-control" placeholder="Max istek" required value="{{ .EditRateLimit.MaxRequests }}">
</div>
<div class="col-md-3">
<input type="number" name="window_seconds" class="form-control" placeholder="Pencere (s)" required value="{{ .EditRateLimit.WindowSeconds }}">
</div>
<div class="col-md-3 d-flex gap-2">
<a href="/admin/content/settings" class="btn btn-secondary">İptal</a>
<button class="btn btn-primary" type="submit">Güncelle</button>
</div>
</form>
{{ else }}
<form action="/admin/settings/rate-limit/create" method="POST" class="row g-2 mb-3">
<div class="col-md-3">
<input type="text" name="name" class="form-control" placeholder="İsim (ör. login)" required>
</div>
<div class="col-md-3">
<input type="number" name="max_requests" class="form-control" placeholder="Max istek" required>
</div>
<div class="col-md-3">
<input type="number" name="window_seconds" class="form-control" placeholder="Pencere (s)" required>
</div>
<div class="col-md-3 d-flex gap-2">
<button class="btn btn-primary" type="submit">Ekle</button>
</div>
</form>
{{ end }}
<div>
<ul class="list-group list-group-flush">
{{ if .RateLimits }}
{{ range .RateLimits }}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
<strong>{{ .Name }}</strong>
<div class="small text-muted">{{ .MaxRequests }} req / {{ .WindowSeconds }}s</div>
</div>
<div class="btn-group">
{{ if $.ShowDeleted }}
<form action="/admin/settings/rate-limit/{{ .ID }}/restore" method="POST">
<button class="btn btn-sm btn-success">Geri Yükle</button>
</form>
{{ else }}
<a href="/admin/content/settings?edit_ratelimit={{ .ID }}" class="btn btn-sm btn-outline-secondary">Düzenle</a>
<form action="/admin/settings/rate-limit/{{ .ID }}/delete" method="POST" onsubmit="return confirmDelete(event, this);">
<button class="btn btn-sm btn-outline-danger">Sil</button>
</form>
{{ end }}
</div>
</li>
{{ end }}
{{ else }}
<li class="list-group-item">Kayıt yok</li>
{{ end }}
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Hero Management Tab -->
<div class="tab-pane fade" id="hero" role="tabpanel">
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="btn-group" role="group">
<a href="/admin/content/settings"
class="btn btn-outline-primary {{ if not .ShowDeleted }}active{{ end }}">Aktif</a>
<a href="/admin/content/settings?deleted=true"
class="btn btn-outline-danger {{ if .ShowDeleted }}active{{ end }}">Silinenler</a>
</div>
<a href="/admin/heroes/new" class="btn btn-primary">
<i class="bi bi-plus-lg"></i> Yeni Banner Ekle
</a>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>Görsel/Renk</th>
<th>Başlık</th>
<th>Alt Başlık</th>
<th>Durum</th>
<th>İşlemler</th>
</tr>
</thead>
<tbody>
{{ range .Heroes }}
<tr>
<td>
<div style="width: 50px; height: 30px; background-color: {{ .Color }}; border-radius: 4px; border: 1px solid #ddd;"
title="{{ .Color }}"></div>
</td>
<td>{{ .Title }}</td>
<td>{{ .Text1 }}</td>
<td>
{{ if .IsActive }}
<span class="badge bg-success">Aktif</span>
{{ else }}
<span class="badge bg-secondary">Pasif</span>
{{ end }}
</td>
<td>
<div class="d-flex gap-2">
{{ if $.ShowDeleted }}
<form action="/admin/heroes/{{ .ID }}/restore" method="POST"
onsubmit="return confirmRestore(event, this);">
<button type="submit" class="btn btn-sm btn-success" title="Geri Yükle">
<i class="bi bi-arrow-counterclockwise"></i>
</button>
</form>
{{ else }}
<a href="/admin/heroes/{{ .ID }}/edit"
class="btn btn-sm btn-outline-secondary" title="Düzenle">
<i class="bi bi-pencil"></i>
</a>
<form action="/admin/heroes/{{ .ID }}/delete" method="POST"
onsubmit="return confirmDelete(event, this);">
<button type="submit" class="btn btn-sm btn-outline-danger" title="Sil">
<i class="bi bi-trash"></i>
</button>
</form>
{{ end }}
</div>
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// Simple tab state preservation using URL hash or local storage is complex with HTMX
// For now, let's just make sure tabs work via Bootstrap JS
// The tabs might reset to first tab on HTMX swap/update if we are not careful.
// Since we reload the whole #settings-container (this file) on update, tabs will reset.
// We can add a small script to check URL param or sessionStorage to activate correct tab.
(function(){
const STORAGE_KEY = 'admin.settings.activeTab';
// Save active tab id when clicked
document.querySelectorAll('#settingsTab button[data-bs-toggle="tab"]').forEach(btn => {
btn.addEventListener('shown.bs.tab', (e) => {
try { sessionStorage.setItem(STORAGE_KEY, e.target.id); } catch(e){}
});
});
// Restore on load
function restoreTab(){
try{
const id = sessionStorage.getItem(STORAGE_KEY);
if(id){
const btn = document.getElementById(id);
if(btn){
const tab = new bootstrap.Tab(btn);
tab.show();
}
}
}catch(e){}
}
document.addEventListener('DOMContentLoaded', restoreTab);
// HTMX: after swapping settings container, restore the tab
document.addEventListener('htmx:afterSwap', function(evt){
if(evt.detail.target && evt.detail.target.id === undefined){ return; }
const tgt = evt.detail.target;
if(tgt && (tgt.id === 'users-container' || tgt.id === 'main-content' || tgt.id === 'modal-area')){
// not our container
return;
}
// if settings container was swapped, restore
if(tgt && tgt.closest && tgt.closest('.container-fluid') && tgt.closest('.container-fluid').querySelector('#settingsTab')){
restoreTab();
}
});
})();
</script>