Zafiyet Önceliklendirme ve Yama Takip Formu

Bu form; tespit edilen zafiyetin CVE bilgisini, Etkilenen varlığı, Ağ erişimini, İstismar durumunu, Kritiklik seviyesini ve Giderim planını tek formda kaydetmek için kullanılır. 

Bu bilgileri ve hedef giderim tarihini değerlendirerek P1–P4 öncelik önerisi sunar; Yama sürecinin, Sorumluların ve Doğrulama kanıtlarının takibini sağlar. Excel Dışarı Aktar form verilerini Excel’e kaydeder, Excel İçeri Aktar mevcut kaydı forma yükler, PDF / Yazdır ise rapor çıktısı oluşturur.

Bir Not defteri açarak, aşağıdaki (Mavi satırlar) html kodu içine kaydedin. Not defteri uzantısını .txt yerine ".html" olarak değiştirip kaydedin. Ör: "Zafiyet Önceliklendirme ve Yama Takip Formu.html" sayfayı kapatıp tekrar açtığınızda, web browser üzerinden sayfanız açılacaktır. 

Sol tarafta görünen logo için, html dosyanız nerede ise, masaüstü veya oluşturulan bir klasör. Logonuzda aynı yerde olursa forma yansır. Logo ismi "logo.jpg" olmalı. Çift tıklayarak açtığınızda, browser üzerinde form açılışı gerçekleşir. Veya alanına 5 kez peş peşe tıklayarak çalışma esnasında logo değişimi de yapılabilir.

Başlık, başlığın altındaki, başlığın sağındaki ve alttaki yazıları, not defteri içinde "Ctrl + F" tuşları ile kelime yazarak bulabilir, ilgili metinleri kendinize göre uyarlayarak kaydederek kullanabilirsiniz. Başlık alanındaki "OLGUN BAŞKANLIĞI" alanına 5 kez peş peşe tıklayarak; Departman, Müdürlük veya Başkanlığınıza ait bilgiyi manuel girebilirsiniz.

Formun boş hali;

Dışarı Aktarılan Excel Verisi:

Formun dolu hali:

PDF / Yazdır


HTML Kod:
<!doctype html>
<html lang="tr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Zafiyet Önceliklendirme ve Yama Takip Formu</title>
<style>
:root{--bg:#f8fafc;--card:#fff;--blue:#0284c7;--blue-dark:#075985;--blue-soft:#e0f2fe;--line:#d7dee8;--text:#0f172a;--muted:#64748b;--green:#166534;--green-soft:#dcfce7;--orange:#9a3412;--orange-soft:#ffedd5;--red:#b91c1c;--red-soft:#fee2e2;--gray-soft:#f1f5f9;--shadow:0 2px 8px rgba(15,23,42,.06)}
*{box-sizing:border-box;font-family:"Segoe UI",Arial,sans-serif}html,body{margin:0;min-height:100%;background:var(--bg);color:var(--text)}body{padding:12px}.container{width:100%;max-width:1600px;margin:0 auto}
.header{width:100%;display:grid;grid-template-columns:clamp(42px,6vw,60px) minmax(0,1fr) max-content;align-items:center;gap:clamp(6px,1vw,12px);background:#fff;border:1px solid var(--line);border-left:5px solid var(--blue);border-radius:6px;padding:10px 14px;box-shadow:var(--shadow);margin-bottom:10px}.logoBox{width:60px;height:60px;display:flex;align-items:center;justify-content:center;overflow:hidden;cursor:pointer}.logoBox:hover{outline:1px dashed #94a3b8;border-radius:4px}.logoBox:focus-visible{outline:2px solid var(--blue);outline-offset:2px}.logoBox img{width:100%;height:100%;object-fit:contain}.headerTitleBlock{align-self:center;min-width:0}.headerTitle{font-size:21px;font-weight:750;line-height:1.15}.headerSub{margin-top:4px;color:var(--muted);font-size:11.5px;line-height:1.35}.headerRight{justify-self:end;align-self:center;width:max-content;max-width:40vw;min-width:0;text-align:right;color:#000;font-size:clamp(9px,1.1vw,15px);font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.headerRight[contenteditable=true]{max-width:min(55vw,760px);min-width:180px;width:max-content;margin-left:auto;overflow:visible;text-overflow:clip;outline:1px dashed var(--blue);background:#f0f9ff;border-radius:3px;padding:2px 4px;cursor:text}
.toolbar{display:flex;flex-wrap:nowrap;align-items:center;gap:8px;overflow-x:auto;scrollbar-width:thin;background:#fff;border:1px solid var(--line);border-radius:6px;box-shadow:var(--shadow);padding:9px 11px;margin-bottom:10px}button,.fileButton{border:1px solid #cbd5e1;border-radius:6px;padding:8px 12px;background:var(--gray-soft);color:#334155;font-size:12px;font-weight:700;cursor:pointer}button:hover,.fileButton:hover{filter:brightness(.97)}.primaryButton{background:var(--blue-soft);border-color:#bae6fd;color:var(--blue-dark)}.greenButton{background:var(--green-soft);border-color:#bbf7d0;color:var(--green)}.toolbarStatus{margin-left:auto;color:var(--muted);font-size:11px}.toolbar input[type=file]{display:none}.toolbar button,.toolbar .fileButton{height:36px;display:inline-flex;flex:0 0 auto;align-items:center;justify-content:center;margin:0;white-space:nowrap}.toolbarStatus{flex:0 0 auto;white-space:nowrap}#logoFile{display:none}
.intro{padding:12px 14px;margin-bottom:10px;background:#eff6ff;border:1px solid #bfdbfe;border-left:4px solid var(--blue);border-radius:6px;color:#1e3a5f;font-size:12px;line-height:1.55}.intro strong{color:#0c4a6e}.scoreNote{display:block;margin-top:5px;color:#475569;font-size:11px}

.listHeader{display:flex;align-items:center;gap:10px;margin:12px 0 8px}.listHeader h2{margin:0;color:#334155;font-size:16px}.listHeader .subcount{color:var(--muted);font-size:11px}.listHeader .sortButton{margin-left:auto}.vulnList{display:flex;flex-direction:column;gap:10px}.vulnCard{background:#fff;border:1px solid var(--line);border-radius:7px;box-shadow:var(--shadow);overflow:hidden;break-inside:auto}.vulnTop{display:grid;grid-template-columns:minmax(0,1fr) auto auto;align-items:center;gap:12px;padding:11px 13px;background:#f8fafc;border-bottom:1px solid var(--line)}.vulnTitle{min-width:0}.vulnTitle strong{display:block;overflow-wrap:anywhere;color:#1e293b;font-size:14px}.vulnTitle small{display:block;margin-top:3px;color:var(--muted);font-size:10.5px}.priorityBadge{min-width:54px;padding:6px 9px;border-radius:999px;text-align:center;font-size:11px;font-weight:800;white-space:nowrap}.p1{color:#991b1b;background:var(--red-soft)}.p2{color:#9a3412;background:var(--orange-soft)}.p3{color:#075985;background:var(--blue-soft)}.p4{color:#166534;background:var(--green-soft)}.priorityBadge.bekliyor{color:#475569;background:#e2e8f0}.scorePill{min-width:76px;text-align:right;color:#334155;font-size:12px;font-weight:800}.vulnBody{padding:12px}.formSection{margin:0 0 9px}.sectionTitle{grid-column:1/-1;margin:4px 0 0;padding:7px 9px;background:#f8fafc;border-left:3px solid var(--blue);color:#334155;font-size:12.5px;font-weight:800;line-height:1.3}.fieldGrid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:9px 11px}.field{display:flex;flex-direction:column;min-width:0;break-inside:avoid}.field.half{grid-column:span 2}.field.full{grid-column:1/-1}label{margin-bottom:4px;color:#334155;font-size:11.5px;font-weight:700;line-height:1.3}.hint{margin-top:4px;color:var(--muted);font-size:10px;line-height:1.4}input,select,textarea{width:100%;min-width:0;border:1px solid #cbd5e1;border-radius:5px;padding:7px 8px;background:#fff;color:var(--text);font-size:12px;outline:none}input:focus,select:focus,textarea:focus{border-color:var(--blue);box-shadow:0 0 0 2px rgba(2,132,199,.12)}textarea{min-height:55px;resize:vertical;line-height:1.4;overflow-wrap:anywhere}.priorityPanel{display:grid;grid-template-columns:190px minmax(0,1fr);gap:12px;align-items:center;border:1px solid #cbd5e1;border-radius:6px;padding:10px;background:#fff}.priorityNumber{font-size:25px;font-weight:850;line-height:1.1}.priorityNumber.pending{font-size:16px;white-space:nowrap;letter-spacing:-.2px}.priorityMeta{margin-top:4px;color:var(--muted);font-size:10px}.priorityReason{color:#334155;font-size:11px;line-height:1.5}.recordActions{display:flex;justify-content:flex-end;margin-top:9px}.removeButton{padding:6px 10px;font-size:11px}.emptyState{padding:28px 12px;border:1px dashed #cbd5e1;border-radius:7px;background:#fff;color:var(--muted);text-align:center;font-size:13px}
@media(max-width:1000px){body{padding:8px}.header{grid-template-columns:46px minmax(0,1fr) max-content;gap:6px}.logoBox{width:42px;height:42px}.headerTitle{font-size:17px}.headerRight{max-width:32vw;font-size:clamp(9px,1.3vw,13px);white-space:nowrap}.fieldGrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:560px){.header{grid-template-columns:38px minmax(0,1fr) max-content;gap:5px;padding:8px}.logoBox{width:36px;height:36px}.headerTitle{font-size:clamp(12px,3.4vw,16px)}.headerSub{font-size:clamp(8px,2vw,10px)}.headerRight{max-width:29vw;font-size:clamp(8px,2.1vw,11px);white-space:nowrap;overflow:hidden;text-overflow:clip}.headerRight[contenteditable=true]{max-width:29vw;min-width:0}.fieldGrid{grid-template-columns:1fr}.field.half,.field.full{grid-column:1/-1}.vulnTop{grid-template-columns:minmax(0,1fr) auto;gap:7px}.scorePill{grid-column:2;grid-row:2;align-self:start}.priorityPanel{grid-template-columns:1fr}.toolbar button,.toolbar .fileButton{flex:0 0 auto}.listHeader{flex-wrap:wrap}.listHeader .sortButton{margin-left:0}}
@page{size:A4 landscape;margin:6mm 7mm 12mm;@bottom-right{content:"Sayfa " counter(page) " / " counter(pages);font-family:"Segoe UI",Arial,sans-serif;font-size:9px;color:#64748b}}
@media print{html,body{width:100%;margin:0!important;padding:7px!important;background:var(--bg)!important;color:var(--text)!important;font-size:10px}.container{width:100%;max-width:none}.toolbar,.sortButton,.removeButton,.recordActions{display:none!important}.header{grid-template-columns:46px minmax(0,1fr) max-content!important;gap:8px;margin:0 0 7px;padding:7px 10px;break-inside:avoid;box-shadow:var(--shadow)}.logoBox{width:46px;height:46px}.headerTitle{font-size:17px}.headerSub{font-size:10px}.headerRight{font-size:13px}.listHeader{margin:8px 0 6px}.listHeader h2{font-size:14px}.listHeader .subcount{font-size:10px}.vulnList{display:block!important;margin:0}.vulnCard{display:block;overflow:visible;break-inside:auto;page-break-inside:auto;margin:0}.vulnTop{gap:8px;padding:7px 10px;break-inside:avoid;page-break-inside:avoid}.vulnBody{padding:8px}.formSection{margin:0 0 6px;break-inside:auto;page-break-inside:auto}.fieldGrid{display:grid!important;grid-template-columns:repeat(4,minmax(0,1fr))!important;gap:6px 8px;align-items:start;align-content:start;break-inside:auto;page-break-inside:auto}.formSection:nth-child(3){break-before:page;page-break-before:always}.sectionTitle{margin:2px 0 0;padding:5px 7px;font-size:10.5px;line-height:1.25;break-after:avoid;page-break-after:avoid}.field{break-inside:avoid;page-break-inside:avoid}.field>label{margin-bottom:2px;font-size:9.5px}.fieldGrid>.field{margin:0}.fieldGrid input,.fieldGrid select,.fieldGrid textarea{padding:4px 6px;font-size:10px;line-height:1.25}.fieldGrid textarea{min-height:42px;resize:none}.prioritySection{break-inside:avoid;page-break-inside:avoid}.priorityPanel{grid-template-columns:160px minmax(0,1fr);gap:8px;padding:7px;break-inside:avoid;page-break-inside:avoid}.priorityNumber{font-size:20px}.priorityNumber.pending{font-size:13px}.priorityMeta{font-size:9px}.priorityReason{font-size:9.5px;line-height:1.35}*{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}}
</style>
</head>
<body>
<div class="container">
  <header class="header">
    <div class="logoBox" id="logoBox" role="button" tabindex="0" aria-label="Logoyu değiştirmek için beş kez tıklayın" title="Logo.jpg varsa başlıkta gösterilir. Değiştirmek için 5 kez art arda tıklayın"><img id="orgLogo" src="logo.jpg" alt="Kurum logosu" onerror="this.style.display='none'"></div>
    <input id="logoFile" type="file" accept="image/jpeg,image/png,image/webp,image/gif">
    <div class="headerTitleBlock"><div class="headerTitle">Zafiyet Önceliklendirme ve Yama Takip Formu</div><div class="headerSub">(İnternete açıklık, aktif istismar, varlık kritiklik seviyesi ve çözüm süresine göre önceliklendirme)</div></div>
    <div class="headerRight" id="headerRight" title="Düzenlemek için 5 kez tıklayın">OLGUN BAŞKANLIĞI</div>
  </header>
  <nav class="toolbar" aria-label="Pano işlemleri">
    <button type="button" class="primaryButton" id="exportButton">Excel Dışarı Aktar</button>
    <label class="fileButton greenButton" for="importFile">Excel İçeri Aktar</label>
    <input id="importFile" type="file" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">
    <button type="button" id="printButton" title="Önlü arkalı çıktı için yazdırma penceresinde İki taraflı seçeneğini etkinleştirin.">PDF / Yazdır</button>
    <span class="toolbarStatus" id="toolbarStatus" aria-live="polite">Açılışta boş form gösterilir.</span>
  </nav>
  <div class="listHeader"><h2>Zafiyet kayıtları</h2><span id="recordCount" class="subcount">1 kayıt · öncelik puanı hesaplanır</span></div>
  <main id="vulnList" class="vulnList" aria-live="polite"></main>
</div>
<script>
(function(){
"use strict";
const HEADER_KEY="olgun_vulnerability_header_v1",LOGO_KEY="olgun_vulnerability_logo_v1",EXCEL_NAME="Zafiyet_Onceliklendirme_ve_Yama_Takip_Formu.xlsx";
const list=document.getElementById("vulnList"),statusText=document.getElementById("toolbarStatus"),logoBox=document.getElementById("logoBox"),orgLogo=document.getElementById("orgLogo"),logoFile=document.getElementById("logoFile"),headerRight=document.getElementById("headerRight");
const fieldDefs=[
 {section:"1. Zafiyet Kaydı"},
 {key:"recordId",label:"Kayıt Numarası",type:"text",placeholder:"ZAF-2026-0001"},
 {key:"cve",label:"CVE / Zafiyet Kimliği",type:"text",placeholder:"CVE-2026-00000"},
 {key:"title",label:"Zafiyet Başlığı",type:"text",half:true},
 {key:"product",label:"Ürün / Üretici",type:"text"},
 {key:"affectedVersion",label:"Etkilenen Sürüm / Bileşen",type:"text"},
 {key:"detectedDate",label:"İlk Tespit Tarihi",type:"date"},
 {key:"source",label:"Tespit Kaynağı",type:"select",options:["Zafiyet tarayıcısı","Üretici duyurusu","Tehdit istihbaratı","Penetrasyon testi","Manuel inceleme","Diğer"]},
 {key:"evidence",label:"Bulgu Kanıtı / Kaynak Bağlantısı",type:"text",full:true,placeholder:"Tarama raporu, danışma veya kayıt referansı"},
 {section:"2. Etkilenen Varlık ve İş Kritiklik Seviyesi"},
 {key:"assetId",label:"Varlık / Envanter Numarası",type:"text"},
 {key:"assetName",label:"Varlık / Sistem Adı",type:"text"},
 {key:"service",label:"İş Hizmeti / Uygulama",type:"text",half:true},
 {key:"assetOwner",label:"İş / Varlık Sahibi",type:"text"},
 {key:"technicalOwner",label:"Teknik Sorumlu / Ekip",type:"text"},
 {key:"criticality",label:"Varlık Kritiklik Seviyesi",type:"select",options:["Kritik","Yüksek","Orta","Düşük","Çok düşük","Bilinmiyor"]},
 {key:"dataClass",label:"Veri Sınıfı",type:"select",options:["Gizli / kritik","Hassas / özel nitelikli","Kişisel veri","Kurum içi","Genel","Bilinmiyor"]},
 {key:"environment",label:"Ortam / Bölge",type:"select",options:["Üretim","Test / kabul","Geliştirme","Bulut","OT / ICS","Diğer / bilinmiyor"]},
 {key:"exposure",label:"İnternet / Ağ Açıklığı",type:"select",options:["İnternet açık ve zafiyet erişilebilir","İnternet açık, erişilebilirlik belirsiz","İç ağdan erişilebilir","Yalıtılmış / erişilemiyor","Bilinmiyor"],full:true},
 {key:"exposureEvidence",label:"Erişilebilirlik Kanıtı / Saldırı Yolu",type:"textarea",full:true,placeholder:"İnternet erişimi, segment, gerekli ön koşul veya doğrulama notu"},
 {section:"3. İstismar ve Teknik Risk Sinyalleri"},
 {key:"exploitStatus",label:"İstismar Durumu",type:"select",options:["Aktif istismar doğrulandı","Kamuya açık PoC / istismar kodu var","Bilinen istismar yok","İnceleniyor","Bilinmiyor"]},
 {key:"kev",label:"CISA KEV Kataloğunda mı?",type:"select",options:["Evet","Hayır","Kontrol edilmedi"]},
 {key:"epss",label:"EPSS (0–1 olasılık)",type:"number",attrs:{min:"0",max:"1",step:"0.001"},placeholder:"Örn. 0.420"},
 {key:"epssDate",label:"EPSS Sorgu Tarihi",type:"date"},
 {key:"cvss",label:"CVSS Puanı (0–10)",type:"number",attrs:{min:"0",max:"10",step:"0.1"}},
 {key:"cvssVersion",label:"CVSS Sürümü / Vektörü",type:"text",placeholder:"CVSS 3.1 veya 4.0; vektör"},
 {key:"vendorSeverity",label:"Üretici Önem Derecesi",type:"select",options:["Kritik","Yüksek","Orta","Düşük","Belirtilmemiş"]},
 {section:"4. Yama ve Giderim Takibi"},
 {key:"patchAvailable",label:"Yama / Düzeltme Durumu",type:"select",options:["Yama mevcut","Geçici çözüm mevcut","Yama bekleniyor","Yama yok / destek dışı","Bilinmiyor"]},
 {key:"fixVersion",label:"Hedef Güvenli Sürüm",type:"text"},
 {key:"remediationOwner",label:"Giderim Sorumlusu",type:"text"},
 {key:"targetDate",label:"Hedef Giderim Tarihi",type:"date"},
 {key:"status",label:"Yama Durumu",type:"select",options:["Yeni","Önceliklendirme bekliyor","Planlandı","Test ediliyor","Uygulama planlandı","Giderildi — doğrulama bekliyor","Kapalı","Risk kabulü / istisna"]},
 {key:"changeTicket",label:"Değişiklik / İş Emri No",type:"text"},
 {key:"patchPlan",label:"Yama / Giderim Planı",type:"textarea",full:true,placeholder:"Yama sürümü, test, uygulama ve geri alma adımları"},
 {key:"workaround",label:"Geçici Azaltım / Telafi Edici Kontrol",type:"textarea",full:true},
 {key:"lastValidation",label:"Son Doğrulama / Yeniden Tarama Tarihi",type:"date"},
 {key:"closureEvidence",label:"Giderim Kanıtı / Doğrulama Sonucu (isteğe bağlı)",type:"textarea",full:true},
 {key:"residualRisk",label:"Kalan Risk / Açıklama (varsa)",type:"textarea",full:true}
];
const defaultValues=()=>({recordId:nextId(),exploitStatus:"Bilinmiyor",kev:"Kontrol edilmedi",criticality:"Bilinmiyor",dataClass:"Bilinmiyor",environment:"Diğer / bilinmiyor",exposure:"Bilinmiyor",patchAvailable:"Bilinmiyor",status:"Yeni",vendorSeverity:"Belirtilmemiş"});
function nextId(){return "ZAF-"+new Date().getFullYear()+"-"+String(document.querySelectorAll(".vulnCard").length+1).padStart(4,"0")}
function esc(s){return String(s??"").replace(/[&<>"']/g,c=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}[c]))}
function dateInputValue(s){if(!s)return "";const text=String(s);if(/^\d{4}-\d{2}-\d{2}$/.test(text))return text;if(/^\d+(\.\d+)?$/.test(text)){const d=new Date(Date.UTC(1899,11,30)+Number(text)*86400000);return Number.isNaN(d.getTime())?"":d.toISOString().slice(0,10)}return text.slice(0,10)}
function createField(def,recordId,value){const wrap=document.createElement("div");wrap.className="field"+(def.full?" full":def.half?" half":"");const label=document.createElement("label"),id="f_"+recordId.replace(/[^a-zA-Z0-9_-]/g,"_")+"_"+def.key;label.htmlFor=id;label.textContent=def.label;wrap.appendChild(label);let el;if(def.type==="select"){el=document.createElement("select");const blank=document.createElement("option");blank.value="";blank.textContent="Seçiniz";el.appendChild(blank);for(const text of def.options){const o=document.createElement("option");o.value=text;o.textContent=text;el.appendChild(o)}el.value=value||""}else if(def.type==="textarea"){el=document.createElement("textarea");el.value=value||"";el.placeholder=def.placeholder||""}else{el=document.createElement("input");el.type=def.type;el.value=def.type==="date"?dateInputValue(value):value||"";if(def.placeholder)el.placeholder=def.placeholder;if(def.attrs)for(const [k,v] of Object.entries(def.attrs))el.setAttribute(k,v)}el.id=id;el.dataset.key=def.key;if(def.type==="number")el.inputMode="decimal";wrap.appendChild(el);return wrap}
function readRecord(card){const item={};card.querySelectorAll("[data-key]").forEach(el=>item[el.dataset.key]=el.value);return item}
function scoreRecord(v){const active=v.exploitStatus==="Aktif istismar doğrulandı"||v.kev==="Evet",exploit=active?5:v.exploitStatus==="Kamuya açık PoC / istismar kodu var"?3:v.exploitStatus==="Bilinen istismar yok"?1:3;const exposure={"İnternet açık ve zafiyet erişilebilir":5,"İnternet açık, erişilebilirlik belirsiz":4,"İç ağdan erişilebilir":3,"Yalıtılmış / erişilemiyor":1,"Bilinmiyor":3}[v.exposure]??3;const crit={"Kritik":5,"Yüksek":4,"Orta":3,"Düşük":2,"Çok düşük":1,"Bilinmiyor":3}[v.criticality]??3;let time=3,daysLeft=null;if(v.targetDate){const today=new Date();today.setHours(0,0,0,0);const due=new Date(v.targetDate+"T00:00:00");if(!Number.isNaN(due.getTime())){daysLeft=Math.ceil((due-today)/86400000);time=daysLeft<=3?5:daysLeft<=7?4:daysLeft<=14?3:daysLeft<=30?2:1}}const score=Math.round((exploit/5*35+exposure/5*25+crit/5*25+time/5*15)*10)/10;const missing=[];if(!v.cve)missing.push("CVE");if(!v.assetId&&!v.assetName)missing.push("varlık kimliği");if(v.exposure==="Bilinmiyor"||!v.exposure)missing.push("erişilebilirlik");if(v.criticality==="Bilinmiyor"||!v.criticality)missing.push("kritiklik");if((v.exploitStatus==="Bilinmiyor"||!v.exploitStatus)&&v.kev!=="Evet")missing.push("istismar durumu");if(!v.targetDate)missing.push("hedef giderim tarihi");const priority=missing.length?"Bekliyor":score>=80?"P1":score>=60?"P2":score>=40?"P3":"P4";let reason;if(missing.length)reason=`Eksik alanlar: ${missing.join(", ")}.`;else{reason=`İstismar: ${v.exploitStatus||"Bilinmiyor"}; erişim: ${v.exposure||"Bilinmiyor"}; varlık kritiklik seviyesi: ${v.criticality||"Bilinmiyor"}.`;reason+=daysLeft<0?` Hedef tarih ${Math.abs(daysLeft)} gün gecikmiş.`:daysLeft===0?" Hedef tarih bugün.":` Hedef tarihe ${daysLeft} gün var.`}return{score:missing.length?null:score,priority,daysLeft,missing,reason,active}}
function fieldLabel(key){return fieldDefs.find(x=>x.key===key)?.label||key}
function renderCard(data){const v={...defaultValues(),...(data||{})};const card=document.createElement("article");card.className="vulnCard";card.dataset.recordId=v.recordId||nextId();const top=document.createElement("div");top.className="vulnTop";const title=document.createElement("div");title.className="vulnTitle";const strong=document.createElement("strong");strong.className="cardHeading";strong.textContent=v.title||v.cve||"Yeni zafiyet kaydı";const small=document.createElement("small");small.className="cardSubheading";small.textContent=[v.recordId,v.assetName||v.assetId].filter(Boolean).join(" · ")||"Kayıt ayrıntılarını doldurun";title.append(strong,small);const badge=document.createElement("span");badge.className="priorityBadge";const score=document.createElement("span");score.className="scorePill";top.append(title,badge,score);card.appendChild(top);const body=document.createElement("div");body.className="vulnBody";let section=null,grid=null;for(const def of fieldDefs){if(def.section){section=document.createElement("section");section.className="formSection";const h=document.createElement("h3");h.className="sectionTitle";h.textContent=def.section;section.appendChild(h);grid=document.createElement("div");grid.className="fieldGrid";section.appendChild(grid);body.appendChild(section);continue}grid.appendChild(createField(def,v.recordId,v[def.key]))}const prioritySection=document.createElement("section");prioritySection.className="formSection prioritySection";prioritySection.innerHTML='<h3 class="sectionTitle">5. Öncelik Kararı</h3><div class="priorityPanel"><div><div class="priorityNumber"></div><div class="priorityMeta"></div></div><div class="priorityReason"></div></div>';body.appendChild(prioritySection);card.appendChild(body);list.appendChild(card);refreshCard(card);return card}
function refreshCard(card){const v=readRecord(card),r=scoreRecord(v);card.dataset.score=r.score===null?"":String(r.score);card.dataset.priority=r.priority;card.dataset.active=r.active?"1":"0";const heading=card.querySelector(".cardHeading");heading.textContent=v.title||v.cve||"Yeni zafiyet kaydı";card.querySelector(".cardSubheading").textContent=[v.recordId,v.assetName||v.assetId].filter(Boolean).join(" · ")||"Kayıt ayrıntılarını doldurun";const badge=card.querySelector(".priorityBadge");badge.textContent=r.priority;badge.className="priorityBadge "+r.priority.toLowerCase();card.querySelector(".scorePill").textContent=r.score===null?"Veri eksik":`${r.score.toFixed(1)} / 100`;const priorityNumber=card.querySelector(".priorityNumber");priorityNumber.textContent=r.score===null?"Değerlendirme bekliyor":`${r.priority} · ${r.score.toFixed(1)} / 100`;priorityNumber.classList.toggle("pending",r.score===null);priorityNumber.style.color=r.priority==="P1"?"#b91c1c":r.priority==="P2"?"#c2410c":r.priority==="P3"?"#0284c7":r.priority==="P4"?"#166534":"#475569";card.querySelector(".priorityMeta").textContent=r.score===null?"":r.daysLeft<0?`${Math.abs(r.daysLeft)} gün gecikmiş`:r.daysLeft===0?"Hedef tarih bugün":`Hedefe ${r.daysLeft} gün`;card.querySelector(".priorityReason").textContent=r.reason;card.title=r.missing.length?"Eksik: "+r.missing.join(", "):"Puanlama bileşenleri hesaplandı"}
function refreshAll(){const cards=[...list.querySelectorAll(".vulnCard")];cards.forEach(refreshCard);document.getElementById("recordCount").textContent=`${cards.length} kayıt · öncelik puanı hesaplanır`}
function emptyValues(){return Object.fromEntries(fieldDefs.filter(x=>x.key).map(x=>[x.key,""]))}
function initializeForm(){try{localStorage.removeItem("olgun_vulnerability_patch_board_v1")}catch(e){}renderCard(emptyValues());refreshAll();statusText.textContent="Boş form hazır. Doldurulan kayıt bu oturumda kalır; saklamak için Excel dışa aktarın."}

function u16(v){return new Uint8Array([v&255,(v>>>8)&255])}function u32(v){return new Uint8Array([v&255,(v>>>8)&255,(v>>>16)&255,(v>>>24)&255])}function concatBytes(parts){let n=0;for(const p of parts)n+=p.length;const o=new Uint8Array(n);let x=0;for(const p of parts){o.set(p,x);x+=p.length}return o}let crcTable=null;function crc32(bytes){if(!crcTable){crcTable=new Uint32Array(256);for(let n=0;n<256;n++){let c=n;for(let k=0;k<8;k++)c=(c&1)?0xedb88320^(c>>>1):c>>>1;crcTable[n]=c>>>0}}let c=0xffffffff;for(const b of bytes)c=crcTable[(c^b)&255]^(c>>>8);return(c^0xffffffff)>>>0}
const enc=new TextEncoder(),dec=new TextDecoder("utf-8");function makeZip(files){const local=[],central=[];let offset=0;for(const f of files){const name=enc.encode(f.name),data=typeof f.data==="string"?enc.encode(f.data):f.data,crc=crc32(data),lh=concatBytes([u32(0x04034b50),u16(20),u16(0),u16(0),u16(0),u16(0),u32(crc),u32(data.length),u32(data.length),u16(name.length),u16(0),name,data]);local.push(lh);central.push(concatBytes([u32(0x02014b50),u16(20),u16(20),u16(0),u16(0),u16(0),u16(0),u32(crc),u32(data.length),u32(data.length),u16(name.length),u16(0),u16(0),u16(0),u16(0),u32(0),u32(offset),name]));offset+=lh.length}const body=concatBytes(local),cd=concatBytes(central);return concatBytes([body,cd,concatBytes([u32(0x06054b50),u16(0),u16(0),u16(files.length),u16(files.length),u32(cd.length),u32(body.length),u16(0)])])}
function colName(n){let s="";while(n){const r=(n-1)%26;s=String.fromCharCode(65+r)+s;n=Math.floor((n-1)/26)}return s}function xmlEsc(s){return String(s??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;")}
function sheetXml(rows){let x='<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetPr><pageSetUpPr fitToPage="1"/></sheetPr><sheetViews><sheetView workbookViewId="0"><pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/></sheetView></sheetViews><sheetData>';rows.forEach((row,ri)=>{x+='<row r="'+(ri+1)+'">';row.forEach((v,ci)=>{x+='<c r="'+colName(ci+1)+(ri+1)+'" t="inlineStr"><is><t xml:space="preserve">'+xmlEsc(v)+'</t></is></c>'});x+="</row>"});return x+'</sheetData><pageMargins left="0.3" right="0.3" top="0.5" bottom="0.5" header="0.2" footer="0.2"/><pageSetup paperSize="9" orientation="portrait" fitToWidth="1" fitToHeight="0"/></worksheet>'}
function makeWorkbook(rows){const types='<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types"><Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/><Default Extension="xml" ContentType="application/xml"/><Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/><Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/></Types>',root='<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/></Relationships>',book='<?xml version="1.0" encoding="UTF-8" standalone="yes"?><workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"><sheets><sheet name="Zafiyetler" sheetId="1" r:id="rId1"/></sheets></workbook>',rels='<?xml version="1.0" encoding="UTF-8" standalone="yes"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/></Relationships>';return makeZip([{name:"[Content_Types].xml",data:types},{name:"_rels/.rels",data:root},{name:"xl/workbook.xml",data:book},{name:"xl/_rels/workbook.xml.rels",data:rels},{name:"xl/worksheets/sheet1.xml",data:sheetXml(rows)}])}
function exportExcel(){const defs=fieldDefs.filter(x=>x.key),card=list.querySelector(".vulnCard"),v=card?readRecord(card):{};if(!Object.values(v).some(x=>String(x??"").trim())){alert("Dışa aktarmadan önce forma en az bir zafiyet bilgisi girin.");return}const rows=[["Alan Kodu","Alan","Değer"],...defs.map(d=>[d.key,d.label,v[d.key]??""])];const blob=new Blob([makeWorkbook(rows)],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}),url=URL.createObjectURL(blob),a=document.createElement("a");a.href=url;a.download=EXCEL_NAME;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1000);statusText.textContent="Zafiyet kaydı dikey A4 sayfa düzeniyle Excel dosyasına aktarıldı."}
function findEOCD(b){const v=new DataView(b.buffer,b.byteOffset,b.byteLength);for(let p=b.length-22;p>=Math.max(0,b.length-65557);p--)if(v.getUint32(p,true)===0x06054b50)return p;throw new Error("XLSX ZIP dizini bulunamadı.")}
async function unzip(buffer){const b=new Uint8Array(buffer),v=new DataView(b.buffer,b.byteOffset,b.byteLength),e=findEOCD(b),count=v.getUint16(e+10,true),start=v.getUint32(e+16,true),files=new Map();let p=start;for(let i=0;i<count;i++){if(v.getUint32(p,true)!==0x02014b50)throw new Error("XLSX merkez dizini bozuk.");const method=v.getUint16(p+10,true),size=v.getUint32(p+20,true),nl=v.getUint16(p+28,true),el=v.getUint16(p+30,true),cl=v.getUint16(p+32,true),lo=v.getUint32(p+42,true),name=dec.decode(b.slice(p+46,p+46+nl));p+=46+nl+el+cl;const ln=v.getUint16(lo+26,true),le=v.getUint16(lo+28,true),packed=b.slice(lo+30+ln+le,lo+30+ln+le+size);let data;if(method===0)data=packed;else if(method===8){if(typeof DecompressionStream==="undefined")throw new Error("Bu tarayıcı XLSX sıkıştırmasını açamıyor; güncel Chrome veya Edge kullanın.");data=new Uint8Array(await new Response(new Blob([packed]).stream().pipeThrough(new DecompressionStream("deflate-raw"))).arrayBuffer())}else throw new Error("Desteklenmeyen XLSX sıkıştırma yöntemi.");files.set(name,data)}return files}
function parseXml(b){const x=new DOMParser().parseFromString(dec.decode(b),"application/xml");if(x.getElementsByTagName("parsererror").length)throw new Error("XLSX XML içeriği okunamadı.");return x}function children(n,name){return Array.from(n.children||[]).filter(x=>x.localName===name)}function colIndex(ref){const s=(ref.match(/^[A-Z]+/i)||["A"])[0].toUpperCase();let n=0;for(const c of s)n=n*26+c.charCodeAt(0)-64;return n-1}function resolvePath(target){if(target.startsWith("/"))return target.slice(1);const a=["xl"];for(const p of target.split("/")){if(p==="..")a.pop();else if(p!==".")a.push(p)}return a.join("/")}
function getShared(files){const b=files.get("xl/sharedStrings.xml");if(!b)return[];return Array.from(parseXml(b).getElementsByTagNameNS("*","si")).map(si=>Array.from(si.getElementsByTagNameNS("*","t")).map(t=>t.textContent).join(""))}function rowsFromXml(b,shared){const doc=parseXml(b),rows=[];for(const row of Array.from(doc.getElementsByTagNameNS("*","row"))){const out=[];for(const c of children(row,"c")){const i=colIndex(c.getAttribute("r")||"A1"),type=c.getAttribute("t"),v=children(c,"v")[0],inline=children(c,"is")[0];let value="";if(type==="inlineStr"&&inline)value=Array.from(inline.getElementsByTagNameNS("*","t")).map(t=>t.textContent).join("");else if(v){value=v.textContent||"";if(type==="s")value=shared[Number(value)]||"";else if(type==="b")value=value==="1"?"TRUE":"FALSE"}out[i]=value}rows.push(out)}return rows}
async function readWorkbook(buf){const files=await unzip(buf),w=parseXml(files.get("xl/workbook.xml")),r=parseXml(files.get("xl/_rels/workbook.xml.rels")),map=new Map();for(const x of Array.from(r.getElementsByTagNameNS("*","Relationship")))map.set(x.getAttribute("Id"),x.getAttribute("Target"));const shared=getShared(files),sheet=Array.from(w.getElementsByTagNameNS("*","sheet"))[0];if(!sheet)throw new Error("Çalışma sayfası bulunamadı.");const id=sheet.getAttributeNS("http://schemas.openxmlformats.org/officeDocument/2006/relationships","id")||sheet.getAttribute("r:id"),target=map.get(id),content=files.get(resolvePath(target));if(!content)throw new Error("Zafiyetler çalışma sayfası okunamadı.");return rowsFromXml(content,shared)}
async function importExcel(file){if(!file)return;try{const rows=await readWorkbook(await file.arrayBuffer());if(rows.length<2)throw new Error("Dosyada içe aktarılacak zafiyet kaydı yok.");const headers=rows[0].map(x=>String(x||"")),defs=fieldDefs.filter(x=>x.key),known=new Set(defs.map(x=>x.key)),normalized=headers.map(x=>x.trim().toLocaleLowerCase("tr-TR")),vertical=normalized.includes("alan kodu")&&normalized.includes("değer");let records;if(vertical){const keyIndex=normalized.indexOf("alan kodu"),valueIndex=normalized.indexOf("değer"),record={};for(const row of rows.slice(1)){const key=String(row[keyIndex]??"").trim();if(known.has(key))record[key]=String(row[valueIndex]??"")}records=Object.keys(record).length?[record]:[]}else{records=rows.slice(1).filter(r=>r.some(x=>String(x||"").trim())).map(row=>{const v={};headers.forEach((h,i)=>{if(known.has(h))v[h]=String(row[i]??"")});return v})}if(!records.length)throw new Error("Tanımlı alanlara ait kayıt bulunamadı.");for(const record of records){for(const d of defs)if(d.type==="date"&&record[d.key])record[d.key]=dateInputValue(record[d.key]);if(dDefsHas("epss")&&record.epss){const n=Number(String(record.epss).replace(",","."));if(Number.isFinite(n))record.epss=String(Math.min(1,Math.max(0,n)))}}list.innerHTML="";renderCard(records[0]);refreshAll();statusText.textContent=records.length>1?"Dosyanın ilk zafiyet kaydı içe aktarıldı; form tek zafiyet içindir.":"Zafiyet kaydı Excel'den içe aktarıldı."}catch(e){console.error(e);alert("Excel dosyası okunamadı. Geçerli bir .xlsx dosyası seçin. "+(e?.message||""))}}
function dDefsHas(key){return fieldDefs.some(x=>x.key===key)}
document.getElementById("exportButton").addEventListener("click",exportExcel);document.getElementById("importFile").addEventListener("change",e=>{importExcel(e.target.files&&e.target.files[0]);e.target.value=""});document.getElementById("printButton").addEventListener("click",()=>window.print());window.addEventListener("beforeprint",()=>refreshAll());list.addEventListener("input",e=>{const card=e.target.closest(".vulnCard");if(card){refreshCard(card);refreshAll()}});list.addEventListener("change",e=>{const card=e.target.closest(".vulnCard");if(card){refreshCard(card);refreshAll()}});
let rightClicks=[];headerRight.addEventListener("click",()=>{const now=Date.now();rightClicks=rightClicks.filter(t=>now-t<2500);rightClicks.push(now);if(rightClicks.length>=5&&headerRight.getAttribute("contenteditable")!=="true"){headerRight.setAttribute("contenteditable","true");headerRight.focus();statusText.textContent="Başlık sağ metni düzenlenebilir; kaydetmek için alan dışına tıklayın."}});headerRight.addEventListener("keydown",e=>{if(e.key==="Enter"){e.preventDefault();headerRight.blur()}});headerRight.addEventListener("blur",()=>{if(headerRight.getAttribute("contenteditable")==="true"){localStorage.setItem(HEADER_KEY,headerRight.textContent.trim());headerRight.removeAttribute("contenteditable");rightClicks=[];statusText.textContent="Başlık metni bu tarayıcıya kaydedildi."}});const savedHeader=localStorage.getItem(HEADER_KEY);if(savedHeader)headerRight.textContent=savedHeader;
const savedLogo=localStorage.getItem(LOGO_KEY);if(savedLogo){orgLogo.src=savedLogo;orgLogo.style.display=""}let logoClicks=[];function requestLogoChange(){const now=Date.now();logoClicks=logoClicks.filter(t=>now-t<2500);logoClicks.push(now);if(logoClicks.length>=5){logoClicks=[];logoFile.click();statusText.textContent="Yeni logo seçin."}}logoBox.addEventListener("click",requestLogoChange);logoBox.addEventListener("keydown",e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();requestLogoChange()}});logoFile.addEventListener("change",()=>{const f=logoFile.files&&logoFile.files[0];logoFile.value="";if(!f)return;if(!f.type.startsWith("image/")){alert("Lütfen bir resim dosyası seçin.");return}if(f.size>2*1024*1024){alert("Logo dosyası 2 MB veya daha küçük olmalıdır.");return}const r=new FileReader();r.onload=()=>{try{const data=String(r.result);localStorage.setItem(LOGO_KEY,data);orgLogo.src=data;orgLogo.style.display="";statusText.textContent="Logo bu tarayıcıya kaydedildi."}catch(e){alert("Logo kaydedilemedi. Daha küçük bir resim deneyin.")}};r.onerror=()=>alert("Logo dosyası okunamadı.");r.readAsDataURL(f)});
initializeForm();
})();
</script>
</body>
</html>