Proje dosyaları eklendi

This commit is contained in:
2026-08-29 15:53:53 +03:00
commit d3313400ca
440 changed files with 24827 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
# TrafficPumper SERP Production Runbook
Aktivasyon seviyeleri: disabled dis istek yapmaz; validation_only yalniz config ve acik izinle ucretsiz bakiye health kontroludur; canary normal customer/scheduler kapaliyken gunde en fazla bir kontrollu sorgudur; enabled butce, kill switch ve circuit breaker altinda normal kullanimdir. Hicbir gecis otomatik yapilmaz.
## Deploy
1. Secret degerlerini yalniz production environment/secret store icinde saglayin.
2. php artisan migrate --force
3. npm ci ve npm run build
4. php artisan optimize:clear
5. Environment yuklendikten sonra config:cache, route:cache ve view:cache
6. php artisan horizon:terminate; process manager yeniden baslatmalidir.
7. php artisan schedule:list
8. Dis isteksiz php artisan serp:dataforseo:health --json
9. Ayri acik izinle health --remote --json
10. Once canary dry-run; ayri izinle yalniz bir kez --execute.
11. Maliyet, normalization, audit, queue ve log kontrolu tamamlanmadan enabled moda gecmeyin.
Migration additive oldugundan bakim modu zorunlu degildir; release eszamanliligini deployment sistemi guvenceye almiyorsa bakim modu kullanin.
## Scheduler ve Horizon
Tek cron: * * * * * cd /GERCEK/PROJE/YOLU ve php artisan schedule:run. Windows gelistirme yolu production yolu degildir. Horizon default ve serp kuyruklarini tuketir. Coklu sunucuda Redis ortak cache/lock/circuit icin zorunludur.
## Emergency stop ve rollback
Super_admin emergency stop ile yeni Live/submit islemlerini keser; mevcut ucretli Standard task polling sonucu kurtarir. Gerekirse worker/scheduler kontrollu durdurulur, onceki release donulur ve config cache yenilenir. Additive migration veri riski varsa korlemesine geri alinmaz. Stuck kayitlar once serp:reconcile, sonra onayla --execute ile islenir.
## Ilk 24 saat
Saatlik harcama, bakiye, basari orani, auth/429/5xx, circuit/kill state, stuck/poll sayilari, backlog, failed jobs ve scrub edilmis loglari kontrol edin. Secret, Authorization header, keyword veya ham response gorulurse emergency stop uygulayin.
## AŞAMA 8 — Güvenli production geçişi (Seviye 0)
Bu çalışma alanından production sunucusuna erişim veya deployment yetkisi verilmemiştir. Gerçek proje yolu, PHP binary, web/PHP-FPM/Horizon servis adları, cron/timer, release ve rollback hedefi **production'da salt okunur keşfedilecek**; Windows geliştirme yolları production değeri değildir.
### Deployment öncesi kapılar
- Release/commit ve doğrulanmış rollback hedefini kaydet; yedek politikasının varlığını doğrula.
- `APP_ENV=production`, `APP_DEBUG=false`; shared Redis cache/lock ve kalıcı queue kullan.
- `DATAFORSEO_LOGIN` ve `DATAFORSEO_PASSWORD` yalnız secret store/environment içinde bulunmalı; değerleri ekrana basma.
- Başlangıç durumu `SERP_CHECKS_ENABLED=false`, `SERP_OPERATION_MODE=disabled`, `SERP_PROVIDER=dataforseo` kalmalıdır.
- Önce `php artisan migrate --pretend`, yetkili deploy sırasında sonra `php artisan migrate --force` çalıştır.
- Lock dosyalarıyla production Composer/npm kurulumu ve `npm run build`; ardından cache, route, view ve worker yenileme adımlarını platformda keşfedilmiş servis adlarıyla uygula.
- `/up`, ana sayfa, login, admin login, migration status, route list, `schedule:list`, Horizon ve `default,serp` tüketimini doğrula.
`php artisan serp:production:preflight --json` dış HTTP yapmaz ve kritik kapıda non-zero döner. Disabled deploy sonrasında `serp:dataforseo:health --json`, `serp:dataforseo:canary --json`, `serp:reconcile` ve `serp:operations:snapshot --window=24h --json` çalıştırılır. Preflight production ortamında PASS olmadan aktivasyon ilerletilmez.
### Ayrı onay kapıları
1. Remote health: yalnız ayrı açık onayla `serp:dataforseo:health --remote --json`. Bakiye critical altındaysa veya health başarısızsa dur.
2. Tek canary: remote health PASS sonrasında ayrıca canary mode değişikliği ve ayrıca tek ücretli `canary --execute` onayı gerekir. Normal customer ve scheduler trafiği kapalı kalır.
3. Limited production: canary kabul kriterlerinin tamamı PASS olduktan sonra ikinci ayrı açık onay gerekir. İki anahtar birlikte açılmadan normal trafik başlamaz.
Canary kabulü; başarılı HTTP/envelope, yalnız organic sonuç, `rank_group` normalizasyonu, depth 10, tek reservation/reconciliation, izin verilen maliyet, scrubbed audit/log, kapalı circuit ve doğru bütçe sayaçlarının tamamını gerektirir. Eksikte emergency stop/disabled uygulanır.
### Limitler ve ilk 24 saat
Güvenli profil: günlük 1.00 USD, aylık 20.00 USD, uyarı %80, low balance 10.00 USD, critical balance 2.00 USD, Live rezervasyon 0.003 USD, Standard 0.001 USD, depth 10. Ramp-up yeni ücretli rezervasyonları atomik olarak ilk saat 5, ilk 6 saat 25, ilk 24 saat 100 toplam kontrolle sınırlar; daha önce ücretlenmiş Standard polling devam eder.
Filament `/admin/serp-operations` ve `serp:operations:snapshot` aynı metrik servisini kullanır. Bütçe, backlog, failed/stuck, health, kill/circuit ve ramp durumu izlenir. Support/admin salt okunur; emergency stop/clear yalnız `super_admin` yetkisidir. Audit kanıtları operasyon audit kayıtlarında, maliyetler bütçe ledgerında, hassas olmayan özet Filament/CLI'dadır.
### Rollback ve incident recovery
Önce emergency stop ile yeni ücretli trafik kesilir; ücretlenmiş Standard task polling kontrollü sürer. Release/commit kaydedilip önceki doğrulanmış release'e dönülür; config/route/view cache ve keşfedilmiş PHP-FPM/Horizon servisleri kontrollü yenilenir. Additive migration körlemesine rollback edilmez; ileri düzeltme kullanılır. `/up`, login, admin ve salt okunur SERP ekranları tekrar doğrulanır; budget/audit kayıtları silinmez.
+36
View File
@@ -0,0 +1,36 @@
:root {
--tp-brand-rgb: 91, 75, 255;
--bs-primary-rgb: var(--tp-brand-rgb);
--bs-link-color-rgb: var(--tp-brand-rgb);
}
[data-bs-theme="light"] {
--tp-page-accent: rgba(var(--tp-brand-rgb), .08);
}
[data-bs-theme="dark"] {
--tp-page-accent: rgba(var(--tp-brand-rgb), .16);
}
body {
min-height: 100vh;
background:
radial-gradient(circle at 85% 15%, var(--tp-page-accent), transparent 32rem),
var(--bs-body-bg);
}
.card,
.navbar,
.dropdown-menu,
.btn {
transition: background-color .2s ease, border-color .2s ease, color .2s ease;
}
@media (prefers-reduced-motion: reduce) {
.card,
.navbar,
.dropdown-menu,
.btn {
transition: none;
}
}
+41
View File
@@ -0,0 +1,41 @@
(function (window, document) {
'use strict';
var labels = { light: 'Açık', dark: 'Karanlık', system: 'Sistem' };
var icons = { light: '☀', dark: '☾', system: '◐' };
function updateThemeControls(event) {
var state = event && event.detail ? event.detail : {
preference: window.ThemeManager.getPreference(),
resolved: window.ThemeManager.getResolvedTheme()
};
document.querySelectorAll('[data-theme-value]').forEach(function (button) {
var active = button.dataset.themeValue === state.preference;
button.classList.toggle('active', active);
button.setAttribute('aria-pressed', String(active));
});
document.querySelectorAll('[data-theme-current-label]').forEach(function (node) {
node.textContent = labels[state.preference];
});
document.querySelectorAll('[data-theme-current-icon]').forEach(function (node) {
node.textContent = icons[state.preference];
});
document.querySelectorAll('[data-theme-preference]').forEach(function (node) {
node.textContent = labels[state.preference];
});
document.querySelectorAll('[data-theme-resolved]').forEach(function (node) {
node.textContent = labels[state.resolved];
});
}
document.addEventListener('click', function (event) {
var button = event.target.closest('[data-theme-value]');
if (button) {
window.ThemeManager.setPreference(button.dataset.themeValue);
}
});
window.addEventListener('trafficpumper:theme-change', updateThemeControls);
updateThemeControls();
}(window, document));
@@ -0,0 +1,76 @@
(function (window, document) {
'use strict';
var STORAGE_KEY = 'trafficpumper.theme';
var DEFAULT_PREFERENCE = 'system';
var VALID_PREFERENCES = ['light', 'dark', 'system'];
var mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
var preference = readPreference();
function isValid(value) {
return VALID_PREFERENCES.indexOf(value) !== -1;
}
function readPreference() {
try {
var stored = window.localStorage.getItem(STORAGE_KEY);
return isValid(stored) ? stored : DEFAULT_PREFERENCE;
} catch (error) {
return DEFAULT_PREFERENCE;
}
}
function resolve(value) {
return value === 'system' ? (mediaQuery.matches ? 'dark' : 'light') : value;
}
function apply() {
var resolved = resolve(preference);
document.documentElement.setAttribute('data-bs-theme', resolved);
document.documentElement.style.colorScheme = resolved;
window.dispatchEvent(new window.CustomEvent('trafficpumper:theme-change', {
detail: { preference: preference, resolved: resolved }
}));
return resolved;
}
function setPreference(value) {
if (!isValid(value)) {
return false;
}
preference = value;
try {
window.localStorage.setItem(STORAGE_KEY, value);
} catch (error) {
// Depolama kapalıysa tema oturum boyunca yine çalışır.
}
apply();
return true;
}
function onSystemThemeChange() {
if (preference === 'system') {
apply();
}
}
if (typeof mediaQuery.addEventListener === 'function') {
mediaQuery.addEventListener('change', onSystemThemeChange);
} else if (typeof mediaQuery.addListener === 'function') {
mediaQuery.addListener(onSystemThemeChange);
}
window.ThemeManager = Object.freeze({
storageKey: STORAGE_KEY,
getPreference: function () { return preference; },
getResolvedTheme: function () { return resolve(preference); },
setPreference: setPreference,
apply: apply
});
// Bu dosya <head> içinde senkron çalıştığı için ilk boyadan önce uygulanır.
apply();
}(window, document));
+83
View File
@@ -0,0 +1,83 @@
<!doctype html>
<html lang="tr" data-bs-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light dark">
<title>TrafficPumper Tema Altyapısı</title>
<!-- Senkron yüklenir: ilk boya yapılmadan önce kayıtlı/aktif temayı uygular. -->
<script src="assets/js/theme-manager.js"></script>
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB"
crossorigin="anonymous"
>
<link rel="stylesheet" href="assets/css/app.css">
</head>
<body>
<nav class="navbar border-bottom bg-body-tertiary" aria-label="Ana navigasyon">
<div class="container py-2 gap-3">
<a class="navbar-brand fw-semibold" href="#">TrafficPumper</a>
<div class="dropdown ms-auto">
<button
class="btn btn-outline-secondary dropdown-toggle d-flex align-items-center gap-2"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
aria-label="Tema seç"
>
<span data-theme-current-icon aria-hidden="true"></span>
<span data-theme-current-label>Sistem</span>
</button>
<ul class="dropdown-menu dropdown-menu-end shadow-sm" data-theme-picker>
<li><button class="dropdown-item d-flex gap-2" type="button" data-theme-value="light"><span aria-hidden="true"></span> Açık</button></li>
<li><button class="dropdown-item d-flex gap-2" type="button" data-theme-value="dark"><span aria-hidden="true"></span> Karanlık</button></li>
<li><button class="dropdown-item d-flex gap-2" type="button" data-theme-value="system"><span aria-hidden="true"></span> Sistem</button></li>
</ul>
</div>
</div>
</nav>
<main class="container py-5">
<section class="row align-items-center g-5 py-lg-5">
<div class="col-lg-7">
<span class="badge text-bg-primary mb-3">Ortak tema temeli</span>
<h1 class="display-5 fw-bold">Açık, karanlık ve sistem teması hazır.</h1>
<p class="lead text-body-secondary mt-3">
Seçiminiz bu tarayıcıda saklanır ve tüm TrafficPumper ekranlarında ortak kullanılabilir.
</p>
<div class="d-flex flex-wrap gap-2 mt-4" role="group" aria-label="Hızlı tema seçimi" data-theme-picker>
<button class="btn btn-outline-primary" type="button" data-theme-value="light">☀ Açık</button>
<button class="btn btn-outline-primary" type="button" data-theme-value="dark">☾ Karanlık</button>
<button class="btn btn-outline-primary" type="button" data-theme-value="system">◐ Sistem</button>
</div>
</div>
<div class="col-lg-5">
<div class="card border-0 shadow-sm">
<div class="card-body p-4 p-md-5">
<h2 class="h4">Tema durumu</h2>
<dl class="row mb-0 mt-4">
<dt class="col-6 text-body-secondary">Tercih</dt>
<dd class="col-6 text-end fw-semibold" data-theme-preference>Sistem</dd>
<dt class="col-6 text-body-secondary">Etkin tema</dt>
<dd class="col-6 text-end fw-semibold" data-theme-resolved></dd>
</dl>
<hr>
<p class="small text-body-secondary mb-0">
“Sistem” seçiliyken işletim sistemi teması değiştiğinde bu alan anında güncellenir.
</p>
</div>
</div>
</div>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<script src="assets/js/app.js"></script>
</body>
</html>