Cuma 02:47 — DC-01 "patlıyor" ama siz yama yüklüyorsunuz
Cuma gecesi 02:47'de telefonunuz titreşir. ShamashAi mobil app: "DC-01: CPU %98, 14 failed authentication, service restart loop". Hemen Discord'u açıyorsunuz — ekip zaten uyanık: "Abi bakım başladı 02:30'da, DC'yi yeniden başlatacağız, normal bu." Siz de hatırlıyorsunuz: Çarşamba toplantısında Windows Server 2019→2022 upgrade'i planlamıştınız, takvime de eklemiştiniz. Ama ShamashAi'a söylemeyi unuttunuz. Maintenance windows olmadan, SIEM planlı bakımı saldırıdan ayırt edemez. Sonuç: 40 dakikalık bakım → 160 critical alert → 6 kişi uykusuz → ertesi gün morale sıfır. Ya da alert engine'i elle kapatırsınız — o zaman gerçek saldırı olursa göremezsiniz. Ya da ingest'i durdurursunız — 40 dakikalık log kaybı, compliance problemi. ShamashAi'nin Maintenance Windows modülü bu kısır döngüyü kırar: scope (project / site / device), zaman penceresi ve sebep tanımlarsınız; window aktif olduğu sürece alert evaluation atlanır, ama log ingestion devam eder. Bakım bitince audit trail eksiksiz, false-alarm sıfır.ShamashAi maintenance_windows tablosu ve scope yapısı
ShamashAi'de planlı sessizlikdbo.maintenance_windows tablosunda saklanır. Her pencere şu bilgileri tutar:
- scope_type:
project,siteveyadevice
- scope_id: hangi proje / site / cihaz için (UUID)
- starts_at: ISO 8601 timestamp (Europe/Istanbul)
- ends_at: bitiş zamanı
- reason: "DC-01 Windows Server upgrade", "Fiber hat kesme çalışması" gibi serbest metin
- created_by: hangi kullanıcı tanımladı (dbo.users foreign key)
- status:
scheduled,active,completed,cancelled
project: tüm proje sessiz (tüm site'ler, tüm device'lar)
site: o site'deki tüm cihazlar sessiz
device: sadece o cihaz sessiz
dbo.maintenance_windows tablosunu kontrol eder:
csharp
// .NET 8 alert evaluator snippet (ShamashAi Agent codebase)
var activeWindow = await _dbContext.MaintenanceWindows
.Where(w => w.Status == "active"
&& w.StartsAt <= utcNow
&& w.EndsAt >= utcNow
&& (
(w.ScopeType == "project" && w.ScopeId == evt.ProjectId) ||
(w.ScopeType == "site" && w.ScopeId == evt.SiteId) ||
(w.ScopeType == "device" && w.ScopeId == evt.DeviceId)
))
.FirstOrDefaultAsync();
if (activeWindow != null)
{
_logger.LogDebug("Event {EventId} skip alert eval: maintenance window {WindowId}",
evt.Id, activeWindow.Id);
await _dbContext.AuditLog.AddAsync(new AuditEntry
{
Action = "ALERT_EVAL_SKIPPED",
Reason = $"Maintenance window: {activeWindow.Reason}",
EventId = evt.Id,
WindowId = activeWindow.Id
});
return; // alert rule evaluation atlanır
}
Eğer aktif pencere varsa alert evaluation atlanır, ama event dbo.events tablosuna yazılır. Log kaybı yok.
POST /maintenance-windows — pencere oluşturma
Yeni pencere oluşturmak için ShamashAi API: javascript // Node.js Fastify route (ShamashAi API) fastify.post('/maintenance-windows', { schema: { body: { type: 'object', required: ['scope_type', 'scope_id', 'starts_at', 'ends_at', 'reason'], properties: { scope_type: { type: 'string', enum: ['project', 'site', 'device'] }, scope_id: { type: 'string', format: 'uuid' }, starts_at: { type: 'string', format: 'date-time' }, ends_at: { type: 'string', format: 'date-time' }, reason: { type: 'string', maxLength: 500 } } } }, preHandler: fastify.authenticate // JWT token }, async (req, reply) => { const { scope_type, scope_id, starts_at, ends_at, reason } = req.body; const userId = req.user.id; // Validation: ends_at > starts_at if (new Date(ends_at) <= new Date(starts_at)) { return reply.code(400).send({ error: 'ends_at must be after starts_at' }); } // Scope existence check const scopeExists = await checkScopeExists(scope_type, scope_id); if (!scopeExists) { return reply.code(404).send({ error:${scope_type} ${scope_id} not found });
}
const windowId = uuidv4();
await fastify.db.query(
`INSERT INTO dbo.maintenance_windows
(id, scope_type, scope_id, starts_at, ends_at, reason, created_by, status)
VALUES (@id, @scope_type, @scope_id, @starts_at, @ends_at, @reason, @created_by, 'scheduled')`,
{ id: windowId, scope_type, scope_id, starts_at, ends_at, reason, created_by: userId }
);
await fastify.audit.log({
action: 'MAINTENANCE_WINDOW_CREATED',
user_id: userId,
resource_id: windowId,
details: { scope_type, scope_id, starts_at, ends_at, reason }
});
return reply.code(201).send({ id: windowId, status: 'scheduled' });
});
Status geçişleri:
scheduled→active(starts_at geldiğinde background worker otomatik değiştirir)
active→completed(ends_at geldiğinde)
scheduled|active→cancelled(manuel iptal)
dbo.maintenance_windows tarar:
javascript
// Node.js background worker (ShamashAi API)
setInterval(async () => {
const now = new Date().toISOString();
// scheduled → active
await fastify.db.query(
`UPDATE dbo.maintenance_windows
SET status = 'active'
WHERE status = 'scheduled' AND starts_at <= @now`,
{ now }
);
// active → completed
await fastify.db.query(
`UPDATE dbo.maintenance_windows
SET status = 'completed'
WHERE status = 'active' AND ends_at <= @now`,
{ now }
);
}, 30_000);
Ingest devam eder, alert evaluation atlanır
Önemli: Maintenance window alert'leri susturur, ama log ingestion'ı durdurmaz. ShamashAi Agent (Node.js veya .NET 8 syslog listener), cihazdan gelen event'leridbo.events tablosuna yazar:
csharp
// .NET 8 Agent syslog handler
public async Task HandleSyslogMessage(SyslogMessage msg)
{
var evt = new Event
{
Id = Guid.NewGuid(),
DeviceId = msg.DeviceId,
SiteId = await GetSiteIdForDevice(msg.DeviceId),
ProjectId = await GetProjectIdForDevice(msg.DeviceId),
Timestamp = msg.Timestamp,
RawLog = msg.RawMessage,
Severity = msg.Severity
};
// Event insert (maintenance window olsun olmasın)
await _dbContext.Events.AddAsync(evt);
await _dbContext.SaveChangesAsync();
// Alert rule evaluation (maintenance window varsa atlanır)
await EvaluateAlertRules(evt);
}
EvaluateAlertRules fonksiyonu yukarıda gördüğünüz gibi aktif pencereyi kontrol eder. Pencere varsa dbo.audit_log'a "ALERT_EVAL_SKIPPED" yazar, alert oluşturmaz.
Bu sayede:
- Bakım bittikten sonra log timeline eksiksiz
- Compliance evidence pack'e dahil
- Forensics analiz yapabilirsiniz
Web arayüzü: Next.js takvim görünümü
ShamashAi web paneli (Next.js)/maintenance sayfasında pencere listesi ve takvim görünümü sunar:
typescript
// Next.js page component (ShamashAi Web)
import { useQuery } from '@tanstack/react-query';
import { Calendar } from '@/components/Calendar';
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
interface MaintenanceWindow {
id: string;
scope_type: 'project' | 'site' | 'device';
scope_id: string;
starts_at: string;
ends_at: string;
reason: string;
status: 'scheduled' | 'active' | 'completed' | 'cancelled';
}
export default function MaintenancePage() {
const { data: windows } = useQuery<MaintenanceWindow[]>(
['maintenance-windows'],
async () => {
const res = await fetch(${apiUrl}/maintenance-windows, {
headers: { Authorization: Bearer ${getToken()} }
});
return res.json();
}
);
const calendarEvents = windows?.map(w => ({
title: w.reason,
start: new Date(w.starts_at),
end: new Date(w.ends_at),
color: w.status === 'active' ? 'orange' : 'gray'
}));
return (
<div>
<h1>Planlı Bakım Pencereleri</h1>
<Calendar events={calendarEvents} />
<button onClick={() => openCreateModal()}>Yeni Pencere</button>
</div>
);
}
Yeni pencere oluşturmak için modal:
typescript
const createWindow = async (data: CreateWindowInput) => {
const res = await fetch(${apiUrl}/maintenance-windows, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: Bearer ${getToken()}
},
body: JSON.stringify(data)
});
if (!res.ok) throw new Error('Failed to create window');
return res.json();
};
Audit trail: geçmiş pencereler retention-immune
ShamashAi'dedbo.events tablosu retention policy'ye tabidir (örneğin 90 gün). Ama dbo.maintenance_windows retention-immune: geçmiş pencereler silinmez. KVKK Madde 12 uyarınca "işleme amacı ortadan kalktığında" silme gerekir, ama maintenance window audit trail "hukuki yükümlülük" kapsamında saklanır (örneğin: "O tarihte neden alert çıkmadı?" sorusuna cevap).
Sorgu örneği:
sql
-- 2026 Eylül ayında DC-01 için hangi pencereler aktifti?
SELECT
mw.starts_at,
mw.ends_at,
mw.reason,
mw.status,
u.email AS created_by_email
FROM dbo.maintenance_windows mw
JOIN dbo.users u ON mw.created_by = u.id
WHERE mw.scope_type = 'device'
AND mw.scope_id = 'a3f7c...-device-uuid'
AND mw.starts_at >= '2026-09-01'
AND mw.starts_at < '2026-10-01'
ORDER BY mw.starts_at;
Audit log'da hangi event'lerin window nedeniyle atlandığını görebilirsiniz:
sql
SELECT
al.timestamp,
al.action, -- 'ALERT_EVAL_SKIPPED'
al.reason, -- 'Maintenance window: DC-01 Windows Server 2022 upgrade + reboot'
e.event_type,
e.source_ip
FROM dbo.audit_log al
JOIN dbo.events e ON al.event_id = e.id
WHERE al.action = 'ALERT_EVAL_SKIPPED'
AND al.timestamp >= '2026-09-12T02:30:00'
AND al.timestamp <= '2026-09-12T04:00:00';
Overlap ve manuel iptal senaryoları
ShamashAi aynı scope için overlap eden pencereler oluşturmanıza izin verir (ör. "DC-01 02:30-04:00" ve "DC-01 03:00-05:00"). Alert evaluator en uzun pencereyi dikkate alır — 02:30-05:00 arası sessiz. Manuel iptal: javascript // PATCH /maintenance-windows/:id/cancel fastify.patch('/maintenance-windows/:id/cancel', { preHandler: fastify.authenticate }, async (req, reply) => { const { id } = req.params; const userId = req.user.id; const result = await fastify.db.query( `UPDATE dbo.maintenance_windows SET status = 'cancelled' WHERE id = @id AND status IN ('scheduled', 'active')`, { id } ); if (result.rowsAffected === 0) { return reply.code(404).send({ error: 'Window not found or already completed' }); } await fastify.audit.log({ action: 'MAINTENANCE_WINDOW_CANCELLED', user_id: userId, resource_id: id }); return reply.send({ status: 'cancelled' }); }); İptal edilen pencere artık alert'leri susturmaz, ama audit trail'de kalır.Gerçek dünya senaryoları
Bir orta ölçekli Türk e-ticaret firması haftalık veritabanı bakımını Pazar 03:00-05:00 arası yapar. ShamashAi'a her Pazar için tekrarlayan pencere tanımlamak isterse: ShamashAi dokümantasyonu recurring window özelliği hakkında daha fazla bilgi vermiyor; şu an her pencereyi elle oluşturmanız gerekir. Pilot kapsamında detay paylaşılır. Başka senaryo: Fiber hat kesme çalışması, ISP bildirimi "09 Eylül 10:00-12:00 internet kesilecek". VPN gateway cihazı için pencere: { "scope_type": "device", "scope_id": "vpn-gw-uuid", "starts_at": "2026-09-09T10:00:00+03:00", "ends_at": "2026-09-09T12:00:00+03:00", "reason": "ISP fiber hat bakımı — internet kesilecek" } Bu pencere sırasındaCONN_DOWN, VPN_TUNNEL_LOST gibi alert'ler tetiklenmez.
Limitler ve dürüst notlar
- Recurring window yok: Her pencereyi manuel oluşturmalısınız. API veya web UI üzerinden toplu oluşturma mümkün, ama "her Pazar 03:00" gibi CRON mantığı yok.
- Granular alert scope yok: Maintenance window tüm alert rule'ları susturur. "Sadece CPU alert'leri sessiz olsun, AUTH_FAIL devam etsin" seçeneği yok.
- Bildirim iptali yok: Alert oluşturulmaz ama event
dbo.events'de; isterseniz manuel sorgu ile görebilirsiniz. Push bildirimi veya e-posta otomatik gitmez.
- Timezone: API ISO 8601 bekler; web UI Europe/Istanbul kabul eder. Farklı timezone senaryosu dokümantasyonda detaylandırılmamış.
- Max pencere süresi yok: 10 dakikalık veya 10 günlük pencere tanımlayabilirsiniz, ama uzun pencereler yanlışlıkla gerçek incident'i maskeleyebilir — manuel review tavsiye edilir.
Planlı bakım pencerelerini ShamashAi'ya bildirmek, false-alarm gürültüsünü keser, ekip moralini korur ve log timeline'ı eksiksiz tutar. Pilot programı 30 gün ücretsiz: shamashai.com.tr/iletisim
