Proje dosyaları eklendi
This commit is contained in:
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user