Bulut ve SaaS Güvenlik Kontrol Formu

Kurumların kullandığı bulut hizmetleri ve SaaS uygulamalarının güvenlik durumunu değerlendirmek, olası riskleri ve güvenlik eksikliklerini belirlemek amacıyla hazırlanmıştır. 

Kimlik ve erişim yönetimi, veri güvenliği, yapılandırma, yedekleme ve mevzuat uyumluluğu gibi temel kontrolleri kapsar. Otomatik risk ve uyum değerlendirmesiyle düzeltici faaliyetlerin takibini kolaylaştırır. Temel hedefi, bulut kaynaklı siber riskleri azaltmak, güvenlik standartlarını güçlendirmek ve denetlenebilir bir güvenlik yönetimi sağlamaktır.

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: "Bulut ve SaaS Güvenlik Kontrol 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;

Formun dolu hali:

PDF / Yazdır

Dışarı Aktarılan Excel Verisi:

HTML Kod:
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="color-scheme" content="light">
<title>Bulut ve SaaS Güvenlik Kontrol Formu</title>
<style>
/* OLGUN UI2 - Kurumsal form tasarim standardi */
:root {
  --bg:#f8fafc; --panel:#fff; --primary:#0284c7; --primary-dark:#0369a1;
  --toolbar-bg:#ffffff; --toolbar-border:#d7dee8;
  --border:#cbd5e1; --border-soft:#dbe4ee; --text:#1e293b; --muted:#64748b;
  --shadow:0 2px 12px rgba(15,23,42,.065);
}
*{box-sizing:border-box}
html{background:var(--bg)}
body{margin:0;padding:7px;background:var(--bg);font:13px/1.5 'Segoe UI',Arial,sans-serif;color:var(--text)}
button,input,textarea,select{font:inherit}
button{cursor:pointer}
button:focus-visible,input:focus-visible,textarea:focus-visible,select:focus-visible,
.org:focus-visible,.logo:focus-visible{outline:2px solid var(--primary);outline-offset:2px}

/* Tum ekran genisligini kullanan, en az tam ekran yuksekliginde dis cerceve */
.shell{
  width:100%;max-width:none;min-height:calc(100vh - 14px);
  margin:0;padding:10px 11px 13px;background:#fff;
  border:1px solid #b6c8d7;border-top:4px solid var(--primary);
  border-radius:7px;box-shadow:0 2px 14px rgba(15,23,42,.075);
}
/* Cerceveli ust blok: baslik ve onun ALTINDA ayri buton cercevesi */
.top{
  display:flex;flex-direction:column;gap:9px;width:100%;
  margin:0 0 11px;padding:9px;background:#fff;
  border:1px solid #d7dee8;border-radius:6px;
  box-shadow:0 2px 6px rgba(15,23,42,.045);
}
.header{
  display:grid;grid-template-columns:62px minmax(0,1fr) auto;
  align-items:center;gap:13px;min-height:81px;
  padding:10px 14px;background:var(--panel);
  border:1px solid #cbd5e1;border-left:5px solid var(--primary);
  border-radius:5px;
}
.logo{
  width:62px;height:62px;display:grid;place-items:center;
  border:1px solid #d2e4f1;border-radius:5px;
  background:#fff;overflow:hidden;cursor:pointer;user-select:none;
}
.logo img{display:block;width:100%;height:100%;object-fit:contain}
.logo .fallback{display:none;font-size:12px;font-weight:750;letter-spacing:.4px;color:#075985}
.logo.no-image{border:1px dashed #7dbbdd;background:#f0f9ff}
.logo.no-image img{display:none}
.logo.no-image .fallback{display:block}
.logo-file-input{display:none}
.heading{min-width:0}
h1{font-size:21px;font-weight:750;line-height:1.2;margin:0 0 5px;color:#102b44}
.subtitle{font-size:11.5px;line-height:1.35;color:var(--muted)}
.org{
  justify-self:end;min-width:0;max-width:43vw;
  font-size:14px;line-height:1.3;font-weight:750;color:#000;
  white-space:normal;overflow-wrap:anywhere;text-align:right;
  cursor:pointer;user-select:none;
}
.org[contenteditable=true]{background:#eff8fe;outline:1px dashed var(--primary);border-radius:4px;padding:4px 6px;user-select:text;cursor:text}
/* Olgun UI2: ayri gri cerceveli, beyaz zeminli islem cubugu; renk yalnizca butonlarda */
.toolbar{
  width:100%;display:flex;flex-wrap:wrap;align-items:center;justify-content:flex-start;
  gap:8px;min-height:57px;margin:0;padding:9px 12px;
  background:var(--toolbar-bg);border:1px solid var(--toolbar-border);
  border-radius:5px;box-shadow:0 1px 3px rgba(15,23,42,.025);
}
.toolbar button{display:inline-flex;align-items:center;justify-content:center;min-height:35px;padding:8px 13px;font-size:12px;font-weight:700;white-space:nowrap}
.btn{background:#fff;border:1px solid #cbd5e1;border-radius:5px;color:#334155}
.btn.small{font-size:11.5px;padding:5px 9px}
.btn:hover{filter:brightness(.97)}
.toolbar #excelImport{background:#dbeeff;border:1px solid #9ed1f0;color:#075985}
.toolbar #excelExport{background:#dcfce7;border:1px solid #9bdfaf;color:#166534}
.toolbar #printBtn{background:#f1f5f9;border:1px solid #cbd5e1;color:#334155}
.toolbar #excelImport:hover{background:#cce8ff}
.toolbar #excelExport:hover{background:#bbf7d0}
.toolbar #printBtn:hover{background:#e2e8f0}

/* Tek form, beyaz yuzey, gri hatlar ve mavi bolum basliklari */
.paper{overflow:hidden;background:#fff;border:1px solid var(--border);border-radius:6px;box-shadow:0 1px 8px rgba(15,23,42,.035)}
.section-head{
  display:flex;justify-content:space-between;align-items:center;gap:10px;
  padding:10px 14px;background:#e8f4fc;
  border-top:1px solid #d4e4f0;border-bottom:1px solid #d4e4f0;
  border-left:4px solid var(--primary);
}
.section-head:first-child{border-top:0}
.section-head strong{color:#075985;font-size:13px}
.section-head span{font-size:11px;color:var(--muted);text-align:right}
.block{padding:14px}
.fields,.decision{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px 13px}
.field{min-width:0}.field.two{grid-column:span 2}.field.all{grid-column:1/-1}
.field label{display:block;margin:0 0 5px;color:#334155;font-size:11.5px;font-weight:650}
.inp,select,textarea{
  display:block;width:100%;min-height:36px;padding:8px 9px;
  border:1px solid #cbd5e1;border-radius:5px;
  background:#fff;color:#172b41;
}
textarea{min-height:70px;resize:vertical;line-height:1.48}
.stats{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:10px 14px;background:#eef7ff;border-bottom:1px solid #e0e8f0}
.stat{display:flex;align-items:baseline;gap:5px;white-space:nowrap;padding:7px 10px;font-size:11px;color:var(--muted);background:#fff;border:1px solid #dbe4ee;border-radius:5px}
.stat b{font-size:14px;color:#0f3956}.stat.risk b{font-size:12px}
.note{font-size:11px;color:var(--muted);margin:9px 0 0}
.group-head{display:flex;gap:9px;align-items:baseline;padding:8px 13px;background:#eff7fc;border-top:1px solid #e2e8f0;border-bottom:1px solid #e2e8f0}
.group-head h3{margin:0;font-size:12.5px;color:#26465f;font-weight:700}
.group-head small{color:#64748b;font-size:10.5px}
.check-row{border-bottom:1px solid #edf2f7}.check-row:last-child{border-bottom:0}
.check-line{display:grid;grid-template-columns:75px minmax(0,1fr) 82px 175px 70px;gap:10px;align-items:center;padding:9px 13px}
.code{font-size:11px;color:#075985;font-weight:700}.control-name{font-size:12.5px;min-width:0}
.priority{padding:4px 3px;text-align:center;font-size:10.5px;color:#64748b;border:1px solid #e2e8f0;border-radius:5px;background:#f8fafc}
.priority.Kritik{background:#fef2f2;color:#991b1b;border-color:#fecaca}
.priority.Yüksek{background:#fff7ed;color:#9a3412;border-color:#fed7aa}
.control-state{min-height:33px;padding:5px 6px;font-size:11.5px}
.control-state[data-value=ok]{background:#f0fdf4;border-color:#86efac}
.control-state[data-value=partial]{background:#fffbeb;border-color:#fcd34d}
.control-state[data-value=fail]{background:#fef2f2;border-color:#fca5a5}
.control-state[data-value=na]{background:#f1f5f9}
.open-detail{background:#fff;border:1px solid #cbd5e1;border-radius:5px;padding:6px 8px;color:#0369a1;font-size:11px;font-weight:650}
.open-detail:hover{background:#eff8fd}
.detail-box{background:#fafdff;padding:11px 13px 14px 98px;border-top:1px dashed #dce9f0}
.detail-box[hidden]{display:none}
.detail-grid{display:grid;grid-template-columns:2fr 2fr 1.2fr 1fr;gap:9px}
.detail-grid textarea{min-height:56px}
.notice{color:#64748b;font-size:11px;margin:12px 0 0}
.toast{display:none;position:fixed;bottom:18px;right:18px;max-width:420px;padding:11px 15px;background:#0f334e;color:#fff;border-radius:6px;z-index:50;box-shadow:0 8px 22px #0f172a30}
.toast.active{display:block}.toast.bad{background:#9f1239}
#printReport{display:none}
@media(max-width:950px){
 .header{grid-template-columns:52px minmax(0,1fr) auto;gap:10px}
 .logo{width:52px;height:52px}.org{max-width:39vw;font-size:12.5px}
 .fields,.decision{grid-template-columns:repeat(2,minmax(0,1fr))}
 .field.all{grid-column:1/-1}
 .detail-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
 .check-line{grid-template-columns:58px minmax(0,1fr) 75px 150px 65px;gap:7px}
 .detail-box{padding-left:12px}
}
@media(max-width:620px){
 body{padding:4px}.shell{min-height:calc(100vh - 8px);padding:7px}
 .top{padding:6px;gap:6px}
 .header{grid-template-columns:42px minmax(0,1fr);padding:9px;gap:9px}
 .logo{width:42px;height:42px;grid-column:1;grid-row:1/3}
 .heading{grid-column:2;grid-row:1}
 .heading h1{font-size:16px}.subtitle{font-size:10px}
 .org{grid-column:2;grid-row:2;justify-self:end;max-width:100%;font-size:11px}
 .toolbar{padding:8px;min-height:0}
 .toolbar button{min-height:34px;flex:1 1 auto;font-size:11px;padding:7px 9px}
 .fields,.decision,.detail-grid{grid-template-columns:1fr}
 .field.two,.field.all{grid-column:auto}
 .section-head{align-items:flex-start;flex-wrap:wrap;padding:8px 10px}
 .section-head span{text-align:left}
 .check-line{grid-template-columns:47px minmax(0,1fr) 69px;gap:6px}
 .control-name{font-size:12px}.priority{grid-column:1;grid-row:2}
 .control-state{grid-column:2;grid-row:2}.open-detail{grid-column:3;grid-row:2}
 .code{grid-column:1;grid-row:1}.control-name{grid-column:2/span 2;grid-row:1}
 .stats{gap:5px}.stat{padding:5px 7px}
}
/* Olgun UI2 - Ekran formunun renklerini, kenarliklarini ve metin akislarini koruyan yazdirma */
@page{
 size:A4 landscape;
 margin:9mm 9mm 16mm;
 @bottom-right{
  content:'Sayfa ' counter(page) ' / ' counter(pages);
  font:700 8.5pt 'Segoe UI',Arial,sans-serif;
  color:#334155;text-align:right;vertical-align:middle;
  border-top:.35pt solid #cbd5e1;padding-top:1.5mm;
 }
}
@media print{
 *,*::before,*::after{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}
 html,body{margin:0!important;padding:0!important;height:auto!important;min-height:0!important;
  overflow:visible!important;background:#fff!important;color:#1e293b!important;
  font:8.4pt/1.38 'Segoe UI',Arial,sans-serif!important}
 body>.shell,.toast{display:none!important}
 #printReport{display:block!important;position:static!important;overflow:visible!important;
  width:100%!important;margin:0!important;padding:0!important;height:auto!important;min-height:0!important}
 .print-shell{width:100%;overflow:visible!important;margin:0!important;padding:2mm!important;
  border:0.3mm solid #cbd5e1;border-top:1mm solid #0284c7!important;
  border-radius:0!important;background:#fff!important;box-shadow:none!important}
 .print-shell .top{display:block!important;margin:0 0 2mm!important;padding:2mm!important;
  background:#fff!important;border:1px solid #d7dee8!important;
  box-shadow:none!important;break-after:avoid-page}
 .print-shell .header{display:grid!important;grid-template-columns:14mm minmax(0,1fr) minmax(26mm,auto)!important;
  gap:3mm!important;min-height:0!important;padding:2mm 3mm!important;
  border:1px solid #cbd5e1!important;border-left:1.2mm solid #0284c7!important;
  border-radius:1mm!important;background:#fff!important;break-after:avoid-page}
 .print-shell .logo{width:14mm!important;height:14mm!important;background:#fff!important}
 .print-shell .logo .fallback{font-size:8pt!important}
 .print-shell .heading h1{font-size:14pt!important;line-height:1.18!important;margin:0 0 1mm!important;color:#102b44!important}
 .print-shell .subtitle{font-size:8pt!important;color:#64748b!important}
 .print-shell .org{justify-self:end!important;max-width:72mm!important;min-width:0!important;
  font-size:9.5pt!important;font-weight:750!important;white-space:normal!important;
  overflow-wrap:anywhere!important;line-height:1.2!important;color:#000!important}
 /* Yazdirma kopyasina sahte ikinci baslik veya buton cubugu eklenmez. */
 .print-shell .toolbar{display:none!important}
 .print-shell .paper{display:block!important;width:100%!important;overflow:visible!important;
  margin:0!important;padding:0!important;box-shadow:none!important;
  border:1px solid #cbd5e1!important;border-radius:1mm!important;background:#fff!important;
  break-before:auto!important;break-after:auto!important}
 .print-shell .section-head{padding:2mm 3mm!important;background:#e8f4fc!important;
  border-top:1px solid #d4e4f0!important;border-bottom:1px solid #d4e4f0!important;
  border-left:1mm solid #0284c7!important;break-after:avoid-page!important;
  page-break-after:avoid!important;break-inside:avoid!important}
 .print-shell .section-head strong{font-size:9pt!important;color:#075985!important}
 .print-shell .section-head span{font-size:7.3pt!important;color:#64748b!important}
 .print-shell .block{padding:2mm 3mm!important;overflow:visible!important}
 .print-shell .fields{display:grid!important;grid-template-columns:repeat(4,minmax(0,1fr))!important;
  gap:1.6mm 2mm!important;overflow:visible!important}
 .print-shell .field{min-width:0!important;overflow:visible!important;break-inside:auto!important}
 .print-shell .field label{display:block!important;font-size:7pt!important;color:#334155!important;
  margin-bottom:0.4mm!important;font-weight:700!important}
 .print-value{display:block!important;width:100%!important;min-height:7mm!important;height:auto!important;
  padding:0.8mm 1.5mm!important;border:1px solid #cbd5e1!important;
  background:#fff!important;border-radius:1mm!important;font-size:8pt!important;line-height:1.34!important;
  color:#172b41!important;white-space:pre-wrap!important;overflow-wrap:anywhere!important;
  word-break:normal!important;overflow:visible!important;max-height:none!important;
  text-overflow:clip!important;box-shadow:none!important}
 .print-value.empty{color:#94a3b8!important}
 .print-shell .stats{display:flex!important;align-items:center!important;flex-wrap:wrap!important;
  gap:1.6mm!important;padding:2mm 3mm!important;background:#eef7ff!important;
  border-bottom:1px solid #e0e8f0!important}
 .print-shell .stat{padding:1mm 2mm!important;border:1px solid #dbe4ee!important;
  background:#fff!important;font-size:7.6pt!important;gap:1mm!important;border-radius:1mm!important}
 .print-shell .stat b{font-size:9pt!important;color:#0f3956!important}
 .print-shell .group-head{background:#eff7fc!important;border-top:1px solid #e2e8f0!important;
  border-bottom:1px solid #e2e8f0!important;padding:1mm 3mm!important;
  break-inside:avoid!important;break-after:avoid-page!important;page-break-after:avoid!important}
 .print-shell .group-head h3{font-size:8.8pt!important;color:#26465f!important}
 .print-shell .group-head small{font-size:7pt!important}
 .print-shell .check-row{display:block!important;border-bottom:1px solid #edf2f7!important;
  break-inside:auto!important;page-break-inside:auto!important;overflow:visible!important}
 .print-shell .check-line{display:grid!important;grid-template-columns:14mm minmax(0,1fr) 19mm 32mm!important;
  gap:2mm!important;align-items:start!important;padding:1.3mm 3mm!important;
  break-inside:avoid!important;page-break-inside:avoid!important}
 .print-shell .has-print-detail .check-line{break-after:avoid-page!important;page-break-after:avoid!important}
 .print-shell .code{font-size:7.7pt!important;color:#075985!important}
 .print-shell .control-name{font-size:8pt!important;line-height:1.35!important;
  overflow-wrap:anywhere!important;white-space:normal!important}
 .print-shell .priority{padding:0.65mm!important;font-size:7pt!important;line-height:1.3!important;
  text-align:center!important;border:1px solid #e2e8f0!important;background:#f8fafc!important;color:#64748b!important}
 .print-shell .priority.Kritik{background:#fef2f2!important;color:#991b1b!important;border-color:#fecaca!important}
 .print-shell .priority.Yüksek{background:#fff7ed!important;color:#9a3412!important;border-color:#fed7aa!important}
 .print-shell .print-status{padding:0.65mm 1mm!important;min-height:0!important;
  text-align:center!important;font-size:7.5pt!important;line-height:1.3!important}
 .print-shell .print-status[data-value="ok"]{background:#f0fdf4!important;border-color:#86efac!important;color:#166534!important}
 .print-shell .print-status[data-value="partial"]{background:#fffbeb!important;border-color:#fcd34d!important;color:#92400e!important}
 .print-shell .print-status[data-value="fail"]{background:#fef2f2!important;border-color:#fca5a5!important;color:#991b1b!important}
 .print-shell .print-status[data-value="na"]{background:#f1f5f9!important;border-color:#cbd5e1!important;color:#334155!important}
 .print-shell .detail-box{display:block!important;position:static!important;overflow:visible!important;
  padding:1mm 3mm 1.3mm 19mm!important;background:#fafdff!important;
  border-top:1px dashed #dce9f0!important;break-inside:auto!important;page-break-inside:auto!important}
 .print-shell .detail-box[hidden]{display:none!important}
 /* Tek tek bloklar halinde yazdirma: uzun kanit ve aciklamalar sayfalar arasinda bolunebilir. */
 .print-shell .detail-grid{display:grid!important;grid-template-columns:2fr 2fr 1.2fr 1fr!important;
  gap:1.6mm!important;overflow:visible!important;align-items:start!important}
 .print-shell .detail-grid .field{margin:0!important;break-inside:auto!important}
 .print-shell .detail-grid.long-text{display:block!important}
 .print-shell .detail-grid.long-text .field{display:block!important;margin:0 0 1.5mm!important}
 .print-shell .detail-grid.long-text .field:last-child{margin-bottom:0!important}
 .print-shell .detail-grid .print-value{min-height:0!important}
 .print-shell .notice,.print-shell .note{font-size:7.2pt!important;line-height:1.35!important}
 /* Karar bolumunde grid kesisine takilmayan, sayfalara bolunebilen metin kutulari */
 .print-shell .decision{display:block!important;overflow:visible!important}
 .print-shell .decision .field{display:block!important;margin-bottom:2mm!important}
 .print-shell .decision .field .print-value{min-height:6mm!important}
 .print-shell .decision .field .print-value.multiline{min-height:8mm!important}
 /* Sonuc ve onay alt alanlari tek satirda yer alir; uzun serbest metinler bolunebilir. */
 .print-shell .decision-meta{display:grid!important;grid-template-columns:repeat(4,minmax(0,1fr))!important;
  align-items:start!important;gap:1.6mm 2mm!important;break-inside:avoid!important;
  page-break-inside:avoid!important;overflow:visible!important}
 .print-shell .decision-meta .field{min-width:0!important;margin:0!important}
 .print-shell .decision-meta .print-value{min-height:5mm!important}
 /* Kanit/detay olmayan kisa degerlendirmeleri gereksiz ucuncu sayfaya tasirma. */
 .print-shell.brief .check-line{padding:0.7mm 3mm!important}
 .print-shell.brief .decision{display:grid!important;grid-template-columns:repeat(4,minmax(0,1fr))!important;
  gap:1.4mm 2mm!important}
 .print-shell.brief .decision .field{margin:0!important}
 .print-shell.brief .decision .field.two{grid-column:span 2!important}
 .print-shell.brief .decision-meta{grid-column:1/-1!important}
 .print-shell.brief .decision .field .print-value{min-height:5mm!important}
 #printReport .print-shell .toolbar button,#printReport .open-detail,#printReport .logo-file-input{display:none!important}
}
</style>
</head>
<body>
<div class="shell">
<div class="top">
<header class="header">
 <div id="logoBox" class="logo" tabindex="0" role="button" aria-label="Kurum logosu; değiştirmek için beş kez tıklayın" title="Logoyu değiştirmek için art arda 5 kez tıklayın"><img id="brandLogo" src="logo.jpg" alt="Kurum logosu"><span class="fallback">LOGO</span></div>
 <input id="logoUpload" class="logo-file-input" type="file" accept="image/png,image/jpeg,image/webp,image/gif">
 <div class="heading"><h1>Bulut ve SaaS Güvenlik Kontrol Formu</h1><div class="subtitle">(Bulut ve SaaS hizmetlerinin genel güvenlik değerlendirmesi)</div></div>
 <div id="orgName" class="org" tabindex="0" title="Düzenlemek için art arda beş kez tıklayın">OLGUN BAŞKANLIĞI</div>
</header>
<div class="toolbar">
 <button class="btn" type="button" id="excelImport">↥ Excel İçeri Aktar</button>
 <button class="btn light" type="button" id="excelExport">↧ Excel Dışarı Aktar</button>
 <button class="btn blue" type="button" id="printBtn">PDF / Yazdır</button>
 <input type="file" id="fileImport" accept=".xlsx" hidden>
</div>
</div>
<form id="assessmentForm" class="paper" autocomplete="off" onsubmit="return false">
 <div class="section-head"><strong>01 — Genel Bilgiler</strong><span>Değerlendirilen bulut veya SaaS hizmeti</span></div>
 <div class="block fields">
  <div class="field"><label for="evaluationNo">Değerlendirme No</label><input id="evaluationNo" class="inp" data-field="no" placeholder="Örn. BSA-001"></div>
  <div class="field"><label for="evaluationDate">Tarih</label><input id="evaluationDate" class="inp" type="date" data-field="date"></div>
  <div class="field"><label for="unit">Kurum / Birim</label><input id="unit" class="inp" data-field="unit"></div>
  <div class="field"><label for="assessor">Değerlendiren</label><input id="assessor" class="inp" data-field="assessor"></div>
  <div class="field"><label for="service">Hizmet / Uygulama Adı</label><input id="service" class="inp" data-field="service"></div>
  <div class="field"><label for="provider">Hizmet Sağlayıcısı</label><input id="provider" class="inp" data-field="provider"></div>
  <div class="field"><label for="model">Hizmet Modeli</label><select id="model" data-field="model"><option value="">Seçiniz</option><option>IaaS</option><option>PaaS</option><option>SaaS</option><option>Diğer</option></select></div>
  <div class="field"><label for="criticality">Kritiklik</label><select id="criticality" data-field="criticality"><option value="">Seçiniz</option><option>Kritik</option><option>Yüksek</option><option>Orta</option><option>Düşük</option></select></div>
 </div>
 <div class="section-head"><strong>02 — Güvenlik Kontrolleri</strong><span>Uygun olmayan maddelerde açıklama ve aksiyon yazın</span></div>
 <div class="stats" aria-live="polite">
  <div class="stat">Değerlendirilen <b id="metricEvaluated">0/25</b></div>
  <div class="stat">Uygun <b id="metricGood">0</b></div>
  <div class="stat">Açık Bulgu <b id="metricIssues">0</b></div>
  <div class="stat">Uyum <b id="metricCompliance">—</b></div>
  <div class="stat risk">Risk <b id="metricRisk">Belirsiz</b></div>
 </div>
 <div id="checklist"></div>
 <div class="block"><div class="note">Durumlar: Uygun, Kısmen Uygun, Uygun Değil, Uygulanamaz. Boş kalan kontroller değerlendirilmemiş sayılır. Uyum oranına yalnızca değerlendirilmiş ve uygulanabilir kontroller dahildir.</div></div>
 <div class="section-head"><strong>03 — Sonuç ve Onay</strong><span>Değerlendirme özeti ve sorumlu bilgileri</span></div>
 <div class="block decision">
  <div class="field two"><label for="summary">Genel Değerlendirme</label><textarea id="summary" data-field="summary" placeholder="Güvenlik durumu, öne çıkan riskler ve öneriler"></textarea></div>
  <div class="field two"><label for="nextSteps">Öncelikli İyileştirmeler / Takip</label><textarea id="nextSteps" data-field="nextSteps" placeholder="Alınacak önlemler, sorumlular ve plan"></textarea></div>
  <div class="field"><label for="decision">Sonuç</label><select id="decision" data-field="decision"><option>Değerlendirilmedi</option><option>Uygun</option><option>Şartlı Uygun</option><option>Uygun Değil</option></select></div>
  <div class="field"><label for="approver">Onaylayan / Kontrol Eden</label><input id="approver" class="inp" data-field="approver"></div>
  <div class="field"><label for="approvalDate">Onay Tarihi</label><input id="approvalDate" type="date" class="inp" data-field="approvalDate"></div>
  <div class="field"><label for="nextDate">Sonraki Gözden Geçirme</label><input id="nextDate" type="date" class="inp" data-field="nextDate"></div>
 </div>
</form>

</div>
<div id="printReport"></div><div id="toast" class="toast" role="status" aria-live="polite"></div>
<script>
'use strict';
const CHECKS=[
 ['Kimlik ve Erişim Yönetimi',[
  ['BSA-01','Bulut/SaaS hizmeti envantere alınmış ve sorumlusu belirlenmiş mi?','Orta'],
  ['BSA-02','Kritik erişimlerde çok faktörlü kimlik doğrulama (MFA) kullanılıyor mu?','Kritik'],
  ['BSA-03','Kullanıcı yetkileri görev ve asgari yetki ilkesine uygun mu?','Yüksek'],
  ['BSA-04','Yönetici ve ayrıcalıklı hesaplar sınırlandırılmış ve denetleniyor mu?','Kritik'],
  ['BSA-05','İşten ayrılma ve görev değişikliklerinde erişimler kaldırılıyor mu?','Yüksek'],
  ['BSA-06','Kullanıcı, servis hesabı ve üçüncü taraf erişimleri düzenli gözden geçiriliyor mu?','Yüksek']
 ]],
 ['Veri Koruma',[
  ['BSA-07','Kritik ve kişisel veriler sınıflandırılmış ve yetkisiz erişimden korunuyor mu?','Yüksek'],
  ['BSA-08','Veriler aktarımda ve depolamada uygun şekilde şifreleniyor mu?','Kritik'],
  ['BSA-09','Dosya paylaşımı, herkese açık bağlantılar ve veri dışa aktarımı sınırlandırılmış mı?','Yüksek'],
  ['BSA-10','Yedekleme ve geri yükleme imkânları doğrulanıyor mu?','Yüksek'],
  ['BSA-11','Veri saklama, silme, aktarım ve barındırma koşulları belirlenmiş mi?','Yüksek']
 ]],
 ['Yapılandırma ve Teknik Güvenlik',[
  ['BSA-12','Varsayılan ayarlar gözden geçirilerek güvenli yapılandırma uygulanmış mı?','Yüksek'],
  ['BSA-13','İnternete açık servis, yönetim paneli ve depolama erişimleri sınırlandırılmış mı?','Kritik'],
  ['BSA-14','Güvenlik güncellemeleri ve zafiyetler takip edilerek gideriliyor mu?','Yüksek'],
  ['BSA-15','API anahtarları, tokenlar ve uygulama entegrasyonları güvenli yönetiliyor mu?','Yüksek'],
  ['BSA-16','Gereksiz servisler ve eski entegrasyonlar devre dışı bırakılıyor mu?','Orta']
 ]],
 ['İzleme ve Olay Yönetimi',[
  ['BSA-17','Kullanıcı, yönetici ve güvenlik olayları için günlük kayıtları tutuluyor mu?','Yüksek'],
  ['BSA-18','Şüpheli erişimler ve kritik değişiklikler için izleme/uyarı sağlanıyor mu?','Yüksek'],
  ['BSA-19','Güvenlik olayı bildirim ve müdahale sorumlulukları tanımlı mı?','Yüksek'],
  ['BSA-20','Olay kayıtlarına ve kanıtlara erişim ile saklama güvenliği sağlanıyor mu?','Orta']
 ]],
 ['Süreklilik, Tedarikçi ve Uyum',[
  ['BSA-21','Hizmet kesintisi, iş sürekliliği ve kurtarma hedefleri değerlendirilmiş mi?','Yüksek'],
  ['BSA-22','Sağlayıcının güvenlik sorumlulukları, SLA ve güvenceleri incelenmiş mi?','Yüksek'],
  ['BSA-23','KVKK ve uygulanabilir mevzuat/veri aktarımı yükümlülükleri kontrol edilmiş mi?','Yüksek'],
  ['BSA-24','Sözleşme sonu veri iadesi, dışa aktarma ve güvenli silme şartları belirlenmiş mi?','Orta'],
  ['BSA-25','SaaS yapay zekâ işlevleri ve üçüncü taraf paylaşımı için veri güvenliği değerlendirilmiş mi?','Orta']
 ]]
];
const ALL=CHECKS.flatMap(([group,items])=>items.map(([id,text,priority])=>({id,text,priority,group})));
const VALID_STATUS=['untested','ok','partial','fail','na'];
const STATUS_LABEL={untested:'Değerlendirilmedi',ok:'Uygun',partial:'Kısmen Uygun',fail:'Uygun Değil',na:'Uygulanamaz'};
const FIELD_KEYS=['no','date','unit','assessor','service','provider','model','criticality','summary','nextSteps','decision','approver','approvalDate','nextDate'];
const state=Object.fromEntries(ALL.map(c=>[c.id,{status:'untested',note:'',action:'',owner:'',due:''}]));
const $=id=>document.getElementById(id);
const escapeHTML=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
let noticeTimeout;
function notify(message,bad=false){const b=$('toast');b.textContent=message;b.className='toast active'+(bad?' bad':'');clearTimeout(noticeTimeout);noticeTimeout=setTimeout(()=>b.className='toast',4400);}
function valuesRead(){return Object.fromEntries(FIELD_KEYS.map(k=>[k,document.querySelector(`[data-field="${k}"]`).value]));}
function valuesWrite(data){for(const k of FIELD_KEYS){const e=document.querySelector(`[data-field="${k}"]`);e.value=String(data?.[k]??(k==='decision'?'Değerlendirilmedi':''));}}
function checkedCount(){return ALL.filter(c=>state[c.id].status!=='untested').length;}
function metrics(){let ok=0,partial=0,fail=0,na=0,criticalFail=0,highFail=0,criticalPartial=0;
 for(const c of ALL){const s=state[c.id].status;if(s==='ok')ok++;else if(s==='partial'){partial++;if(c.priority==='Kritik')criticalPartial++;}else if(s==='fail'){fail++;if(c.priority==='Kritik')criticalFail++;if(c.priority==='Yüksek')highFail++;}else if(s==='na')na++;}
 const reviewed=ok+partial+fail+na,denom=ok+partial+fail;
 let risk='Belirsiz';if(criticalFail>0)risk='Kritik';else if(highFail>0||criticalPartial>=2)risk='Yüksek';else if(partial+fail>0)risk='Orta';else if(reviewed===ALL.length)risk='Düşük';
 return {reviewed,ok,partial,fail,na,issues:partial+fail,compliance:denom?Math.round(ok/denom*100):null,risk};}
function updateMetrics(){const m=metrics();$('metricEvaluated').textContent=`${m.reviewed}/${ALL.length}`;$('metricGood').textContent=m.ok;$('metricIssues').textContent=m.issues;$('metricCompliance').textContent=m.compliance===null?'—':m.compliance+'%';$('metricRisk').textContent=m.risk;}
function buildRows(){const root=$('checklist');root.replaceChildren();for(const [group,items] of CHECKS){const h=document.createElement('div');h.className='group-head';h.innerHTML=`<h3>${escapeHTML(group)}</h3><small>${items.length} kontrol</small>`;root.appendChild(h);
 for(const [id,text,priority] of items){const row=document.createElement('div');row.className='check-row';row.dataset.id=id;row.innerHTML=`<div class="check-line"><span class="code">${id}</span><span class="control-name">${escapeHTML(text)}</span><span class="priority ${priority}">${priority}</span><select class="control-state" data-status="${id}" aria-label="${id} kontrol durumu"><option value="untested">Değerlendirilmedi</option><option value="ok">Uygun</option><option value="partial">Kısmen Uygun</option><option value="fail">Uygun Değil</option><option value="na">Uygulanamaz</option></select><button type="button" class="open-detail" data-toggle="${id}" aria-expanded="false">Detay</button></div><div class="detail-box" id="detail-${id}" hidden><div class="detail-grid"><div class="field"><label for="note-${id}" data-note-label="${id}">Açıklama / Kanıt</label><textarea id="note-${id}" data-control="${id}" data-prop="note" placeholder="Kanıt referansı, bulgu veya gerekçe"></textarea></div><div class="field"><label for="action-${id}">Düzeltici Faaliyet</label><textarea id="action-${id}" data-control="${id}" data-prop="action" placeholder="Yapılacak işlem (varsa)"></textarea></div><div class="field"><label for="owner-${id}">Sorumlu</label><input class="inp" id="owner-${id}" data-control="${id}" data-prop="owner" placeholder="Adı / Birim"></div><div class="field"><label for="due-${id}">Hedef Tarih</label><input class="inp" id="due-${id}" type="date" data-control="${id}" data-prop="due"></div></div></div>`;root.appendChild(row);
 }}syncChecks();}
function setDetails(id,open){const block=$('detail-'+id),btn=document.querySelector(`[data-toggle="${id}"]`);block.hidden=!open;btn.setAttribute('aria-expanded',String(open));btn.textContent=open?'Kapat':'Detay';}
function syncChecks(){for(const c of ALL){const s=state[c.id],sel=document.querySelector(`[data-status="${c.id}"]`);sel.value=s.status;sel.dataset.value=s.status;for(const prop of ['note','action','owner','due']){document.querySelector(`[data-control="${c.id}"][data-prop="${prop}"]`).value=s[prop]||'';}const label=document.querySelector(`[data-note-label="${c.id}"]`);label.textContent=s.status==='na'?'Uygulanamaz Gerekçesi':'Açıklama / Kanıt';setDetails(c.id,s.status==='partial'||s.status==='fail'||s.status==='na'||Boolean(s.note||s.action||s.owner||s.due));}updateMetrics();}
function normalizeOrgName(value){const name=String(value??'').trim();return (!name||name.toLocaleUpperCase('tr-TR')==='ÖRNEK KURUM')?'OLGUN BAŞKANLIĞI':name;}
function getSnapshot(){return {version:1,info:valuesRead(),org:$('orgName').textContent.trim(),controls:Object.fromEntries(ALL.map(c=>[c.id,{...state[c.id]}]))};}
function applySnapshot(s){if(!s||!s.info||!s.controls)throw Error('Uygun form verisi bulunamadı.');valuesWrite(s.info);for(const c of ALL){const v=s.controls[c.id]||{};state[c.id]={status:VALID_STATUS.includes(v.status)?v.status:'untested',note:String(v.note||''),action:String(v.action||''),owner:String(v.owner||''),due:String(v.due||'')};}if(s.org){$('orgName').textContent=normalizeOrgName(s.org).slice(0,150);try{localStorage.setItem('olgun_org_cloud',$('orgName').textContent);}catch(_){}}syncChecks();}
function initOrg(){
 const el=$('orgName');
 try{el.textContent=normalizeOrgName(localStorage.getItem('olgun_org_cloud'));}catch(_){}
 let count=0,last=0,original='';
 function activate(){
  if(el.isContentEditable)return;
  const now=Date.now();count=now-last<=4000?count+1:1;last=now;
  if(count<5)return;
  count=0;original=el.textContent;
  el.setAttribute('contenteditable','true');el.focus();
  const range=document.createRange(),selection=window.getSelection();
  range.selectNodeContents(el);selection.removeAllRanges();selection.addRange(range);
  notify('Kurum adını düzenleyin; Enter ile kaydedebilirsiniz.');
 }
 el.addEventListener('pointerdown',activate);
 el.addEventListener('keydown',e=>{
  if(el.isContentEditable){
   if(e.key==='Enter'){e.preventDefault();el.blur();}
   else if(e.key==='Escape'){e.preventDefault();el.textContent=original;el.blur();}
  } else if(e.key==='Enter'||e.key===' '){e.preventDefault();activate();}
 });
 el.addEventListener('blur',()=>{
  if(!el.isContentEditable)return;
  el.removeAttribute('contenteditable');
  el.textContent=el.textContent.trim().slice(0,150)||'OLGUN BAŞKANLIĞI';
  try{localStorage.setItem('olgun_org_cloud',el.textContent);}catch(_){}
 });
}
function initLogo(){
 const box=$('logoBox'),img=$('brandLogo'),upload=$('logoUpload');
 function refresh(){box.classList.toggle('no-image',!(img.complete&&img.naturalWidth>0));}
 img.addEventListener('load',refresh);img.addEventListener('error',refresh);
 try{const saved=localStorage.getItem('olgunUI2_cloud_logo');if(saved)img.src=saved;}catch(_){}
 refresh(); // Onbellekten acilan logo.jpg dosyalarini da dogrula
 let count=0,last=0;
 function clicked(){
  if(box.classList.contains('no-image')){upload.click();return;}
  const now=Date.now();count=now-last<=4000?count+1:1;last=now;
  if(count>=5){count=0;upload.click();}
 }
 box.addEventListener('click',clicked);
 box.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' '){e.preventDefault();clicked();}});
 upload.addEventListener('change',()=>{
  const file=upload.files?.[0];upload.value='';if(!file)return;
  if(!/^image\/(png|jpeg|webp|gif)$/.test(file.type)){notify('PNG, JPG, WebP veya GIF seçin.',true);return;}
  if(file.size>3*1024*1024){notify('Logo 3 MB üzerinde olamaz.',true);return;}
  const reader=new FileReader();
  reader.onload=()=>{img.src=String(reader.result);refresh();try{localStorage.setItem('olgunUI2_cloud_logo',String(reader.result));}catch(_){notify('Logo görüntülendi ancak kalıcı olarak kaydedilemedi.');}};
  reader.readAsDataURL(file);
 });
}

function download(blob,name){const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=name;document.body.append(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1500);}
const encoder=new TextEncoder(),decoder=new TextDecoder();
const xml=s=>String(s??'').replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g,'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&apos;'}[c]));
let crcTable;function crc32(data){if(!crcTable){crcTable=Array.from({length:256},(_,i)=>{let c=i;for(let j=0;j<8;j++)c=c&1?0xedb88320^(c>>>1):c>>>1;return c>>>0;});}let crc=0xffffffff;for(const x of data)crc=crcTable[(crc^x)&255]^(crc>>>8);return (crc^0xffffffff)>>>0;}
function zipPack(items){const blobs=[],entries=[];let offset=0;for(const [name,content] of items){const bytes=typeof content==='string'?encoder.encode(content):content,fn=encoder.encode(name),crc=crc32(bytes),head=new Uint8Array(30+fn.length),d=new DataView(head.buffer);d.setUint32(0,0x04034b50,true);d.setUint16(4,20,true);d.setUint16(6,0x800,true);d.setUint16(8,0,true);d.setUint32(14,crc,true);d.setUint32(18,bytes.length,true);d.setUint32(22,bytes.length,true);d.setUint16(26,fn.length,true);head.set(fn,30);blobs.push(head,bytes);entries.push({name:fn,bytes,crc,offset});offset+=head.length+bytes.length;}
const centralOffset=offset;for(const e of entries){const head=new Uint8Array(46+e.name.length),d=new DataView(head.buffer);d.setUint32(0,0x02014b50,true);d.setUint16(4,20,true);d.setUint16(6,20,true);d.setUint16(8,0x800,true);d.setUint16(10,0,true);d.setUint32(16,e.crc,true);d.setUint32(20,e.bytes.length,true);d.setUint32(24,e.bytes.length,true);d.setUint16(28,e.name.length,true);d.setUint32(42,e.offset,true);head.set(e.name,46);blobs.push(head);offset+=head.length;}
const end=new Uint8Array(22),ed=new DataView(end.buffer);ed.setUint32(0,0x06054b50,true);ed.setUint16(8,entries.length,true);ed.setUint16(10,entries.length,true);ed.setUint32(12,offset-centralOffset,true);ed.setUint32(16,centralOffset,true);blobs.push(end);return new Blob(blobs,{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});}
function columnName(n){let s='';for(n++;n;n=Math.floor((n-1)/26))s=String.fromCharCode(65+(n-1)%26)+s;return s;}
function makeSheet(rows){const width=Math.max(...rows.map(r=>r.length),1),body=rows.map((r,i)=>`<row r="${i+1}">${r.map((v,j)=>{if(v===null||v===undefined||v==='')return '';const ref=columnName(j)+(i+1);return typeof v==='number'&&Number.isFinite(v)?`<c r="${ref}"><v>${v}</v></c>`:`<c r="${ref}" t="inlineStr"><is><t xml:space="preserve">${xml(v)}</t></is></c>`;}).join('')}</row>`).join('');return `<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><dimension ref="A1:${columnName(width-1)}${Math.max(rows.length,1)}"/><sheetViews><sheetView workbookViewId="0"/></sheetViews><sheetFormatPr defaultRowHeight="15"/><sheetData>${body}</sheetData></worksheet>`;}
function makeWorkbook(sheets){const rels='http://schemas.openxmlformats.org/officeDocument/2006/relationships',main='http://schemas.openxmlformats.org/spreadsheetml/2006/main';const wb=`<?xml version="1.0" encoding="UTF-8"?><workbook xmlns="${main}" xmlns:r="${rels}"><sheets>${sheets.map((s,i)=>`<sheet name="${xml(s.name)}" sheetId="${i+1}" r:id="rId${i+1}"/>`).join('')}</sheets></workbook>`;const wr=`<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">${sheets.map((s,i)=>`<Relationship Id="rId${i+1}" Type="${rels}/worksheet" Target="worksheets/sheet${i+1}.xml"/>`).join('')}</Relationships>`;const types=`<?xml version="1.0" encoding="UTF-8"?><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"/>${sheets.map((s,i)=>`<Override PartName="/xl/worksheets/sheet${i+1}.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`).join('')}</Types>`;const rootRels=`<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId1" Type="${rels}/officeDocument" Target="xl/workbook.xml"/></Relationships>`;return zipPack([['[Content_Types].xml',types],['_rels/.rels',rootRels],['xl/workbook.xml',wb],['xl/_rels/workbook.xml.rels',wr],...sheets.map((s,i)=>[`xl/worksheets/sheet${i+1}.xml`,makeSheet(s.rows)])]);}

async function unzip(file){const u=new Uint8Array(await file.arrayBuffer()),v=new DataView(u.buffer),limit=Math.max(0,u.length-65557);let end=-1;for(let i=u.length-22;i>=limit;i--)if(v.getUint32(i,true)===0x06054b50){end=i;break;}if(end<0)throw Error('Geçerli ZIP/XLSX yapısı bulunamadı.');const count=v.getUint16(end+10,true),offset=v.getUint32(end+16,true),map=new Map();let pos=offset;for(let i=0;i<count;i++){if(v.getUint32(pos,true)!==0x02014b50)throw Error('ZIP dizini okunamadı.');const method=v.getUint16(pos+10,true),length=v.getUint32(pos+20,true),nameLen=v.getUint16(pos+28,true),extra=v.getUint16(pos+30,true),comment=v.getUint16(pos+32,true),local=v.getUint32(pos+42,true),name=decoder.decode(u.slice(pos+46,pos+46+nameLen)),start=local+30+v.getUint16(local+26,true)+v.getUint16(local+28,true);if(start+length>u.length)throw Error('Hatalı ZIP kayıt uzunluğu.');const raw=u.slice(start,start+length);let contents;if(method===0)contents=raw;else if(method===8){if(typeof DecompressionStream!=='function')throw Error('Bu tarayıcı sıkıştırılmış XLSX açmayı desteklemiyor.');try{contents=new Uint8Array(await new Response(new Blob([raw]).stream().pipeThrough(new DecompressionStream('deflate-raw'))).arrayBuffer());}catch(e){throw Error('XLSX sıkıştırılmış veri açılamadı: '+e.message);}}else throw Error('Desteklenmeyen XLSX sıkıştırma türü: '+method);map.set(name,decoder.decode(contents));pos+=46+nameLen+extra+comment;}return map;}
function parseXML(txt){const doc=new DOMParser().parseFromString(txt,'application/xml');if(doc.querySelector('parsererror'))throw Error('Excel XML verisi okunamadı.');return doc;}
function getChildren(node,name){return [...node.getElementsByTagName('*')].filter(e=>e.localName===name);}
function readSheet(xmlStr,shared){const doc=parseXML(xmlStr),rows=[];for(const node of getChildren(doc,'row')){const idx=Math.max(0,Number(node.getAttribute('r')||rows.length+1)-1),out=[];for(const c of [...node.children].filter(n=>n.localName==='c')){const addr=c.getAttribute('r')||'',col=addr.match(/[A-Z]+/i)?.[0]?.toUpperCase()||'A';let j=0;for(const letter of col)j=j*26+letter.charCodeAt(0)-64;j--;const type=c.getAttribute('t'),v=getChildren(c,'v')[0]?.textContent||'',inline=getChildren(c,'is')[0];let val='';if(type==='s')val=shared[+v]??'';else if(type==='inlineStr')val=inline?getChildren(inline,'t').map(e=>e.textContent).join(''):'';else if(type==='b')val=v==='1'?'Evet':'Hayır';else val=v;out[j]=val;}rows[idx]=out;}return rows.filter(Boolean);}
function pathJoin(base,relative){if(relative.startsWith('/'))return relative.replace(/^\//,'');const parts=(base+'/'+relative).split('/'),out=[];for(const p of parts){if(!p||p==='.')continue;if(p==='..')out.pop();else out.push(p);}return out.join('/');}
function parseXlsx(zip){const book=zip.get('xl/workbook.xml');if(!book)throw Error('Çalışma kitabı bulunamadı.');const root=parseXML(book),relFile=zip.get('xl/_rels/workbook.xml.rels')||'',rels=relFile?parseXML(relFile):null,paths={};if(rels)for(const x of getChildren(rels,'Relationship'))paths[x.getAttribute('Id')]=pathJoin('xl',x.getAttribute('Target')||'');let shared=[];if(zip.has('xl/sharedStrings.xml')){const strings=parseXML(zip.get('xl/sharedStrings.xml'));shared=getChildren(strings,'si').map(si=>getChildren(si,'t').map(t=>t.textContent).join(''));}const result={};let index=0;for(const sh of getChildren(root,'sheet')){index++;const id=sh.getAttribute('r:id')||sh.getAttributeNS('http://schemas.openxmlformats.org/officeDocument/2006/relationships','id'),file=paths[id]||`xl/worksheets/sheet${index}.xml`;if(zip.has(file))result[sh.getAttribute('name')]=readSheet(zip.get(file),shared);}return result;}
function sheetObjects(rows){if(!rows?.length)return [];const headings=rows[0];return rows.slice(1).filter(r=>r?.some(x=>x!==''&&x!==undefined)).map(r=>Object.fromEntries(headings.map((h,i)=>[String(h??''),r[i]??''])));}
const EXCEL_COLUMNS=['Kod','Kontrol Grubu','Kontrol Açıklaması','Önem','Durum','Açıklama / Kanıt','Düzeltici Faaliyet','Sorumlu','Hedef Tarih'];
function makeExport(){const s=getSnapshot(),m=metrics();const general=[['Alan','Değer'],...FIELD_KEYS.map(key=>[key,s.info[key]||'']),['org',s.org],['toplam_kontrol',ALL.length],['degerlendirilen',m.reviewed],['uygun',m.ok],['acik_bulgu',m.issues],['uyum_orani',m.compliance===null?'':m.compliance+'%'],['genel_risk',m.risk]];
 const controls=[EXCEL_COLUMNS,...ALL.map(c=>[c.id,c.group,c.text,c.priority,STATUS_LABEL[state[c.id].status],state[c.id].note,state[c.id].action,state[c.id].owner,state[c.id].due])];return makeWorkbook([{name:'Genel',rows:general},{name:'Kontroller',rows:controls}]);}
function exportExcel(){try{const no=(valuesRead().no||'Bulut_SaaS_Guvenlik').replace(/[^a-zA-Z0-9_-]/g,'_');download(makeExport(),no+'.xlsx');notify('Excel dosyası oluşturuldu.');}catch(e){console.error(e);notify('Excel aktarılamadı: '+e.message,true);}}
async function importExcel(file){if(file.size>25*1024*1024)throw Error('Dosya boyutu 25 MB sınırını aşıyor.');const sheets=parseXlsx(await unzip(file));if(!sheets.Genel||!sheets.Kontroller)throw Error('Dosyada Genel ve Kontroller sayfaları bulunamadı.');
 const general=sheetObjects(sheets.Genel),controls=sheetObjects(sheets.Kontroller),pairs=Object.fromEntries(general.map(r=>[String(r.Alan||''),String(r.Değer??'')]));const info=Object.fromEntries(FIELD_KEYS.map(k=>[k,pairs[k]||'']));const controlMap={};for(const row of controls){const id=String(row.Kod||'').trim();if(!ALL.some(c=>c.id===id))continue;const st=Object.entries(STATUS_LABEL).find(([,v])=>v===row.Durum)?.[0]||'untested';controlMap[id]={status:st,note:String(row['Açıklama / Kanıt']||''),action:String(row['Düzeltici Faaliyet']||''),owner:String(row.Sorumlu||''),due:String(row['Hedef Tarih']||'')};}return {info,org:normalizeOrgName(pairs.org),controls:controlMap};}
function showPrint(){
 // Ekranda gosterilen Olgun UI2 tasariminin fotograflanabilir/yazdirilabilir kopyasi.
 // Gercek input/select/textarea dugumlerini, yukseklik kisitlamasi olmayan metin alanlarina cevirir.
 // Bu sayede kaydirma cubuklari ve metin kirpilmasi olmaz.
 const data=valuesRead();
 const root=document.createElement('div');root.className='print-shell';
 if(ALL.every(c=>['note','action','owner','due'].every(k=>!state[c.id][k]))
    && data.summary.length<300&&data.nextSteps.length<300)root.classList.add('brief');
 const top=document.querySelector('body > .shell > .top').cloneNode(true);
 // Islemlere ait buton satiri ve sonradan eklenen rapor basligi yazdirilmaz.
 // Yalnizca formun gercek Olgun UI2 basligi kalir.
 top.querySelector('.toolbar')?.remove();
 // Basligin aksiyonlari olmadigindan kopyadaki tum ID'leri gecersiz kil.
 top.querySelectorAll('[id]').forEach(el=>el.removeAttribute('id'));
 const form=$('assessmentForm').cloneNode(true);form.removeAttribute('id');form.removeAttribute('onsubmit');
 form.querySelectorAll('.open-detail').forEach(el=>el.remove());
 for(const node of form.querySelectorAll('.detail-box')){
   const id=node.id.slice('detail-'.length),s=state[id];
   const hasData=Boolean(s.note||s.action||s.owner||s.due);
   if(!(hasData||['partial','fail','na'].includes(s.status)))node.remove();
   else {
     node.hidden=false;node.closest('.check-row').classList.add('has-print-detail');
     // Uzun metinlerde CSS grid yerine sayfalar arasinda bolunebilen blok akisi kullan.
     const maxText=Math.max(s.note.length,s.action.length,s.owner.length);
     const hasManyLines=[s.note,s.action].some(x=>x.split('\n').length>18);
     if(maxText>700||hasManyLines)node.querySelector('.detail-grid').classList.add('long-text');
   }
 }
 // Kopyadaki form elemanlari yerine dinamik boyutlanan yazdirma degerleri kullanilir.
 for(const el of [...form.querySelectorAll('input,select,textarea')]){
   const live=el.id?document.getElementById(el.id):(el.hasAttribute('data-status')?document.querySelector(`[data-status="${el.dataset.status}"]`):null);
   const value=live?.value??'';
   const isStatus=el.hasAttribute('data-status');
   const v=document.createElement('div');v.className='print-value'+(el.tagName==='TEXTAREA'?' multiline':'')+(isStatus?' print-status':'')+(!value?' empty':'');
   if(isStatus)v.setAttribute('data-value',value);
   if(el.tagName==='SELECT'){
     v.textContent=live?.selectedOptions?.[0]?.textContent?.trim()||'—';
   }else{v.textContent=value||'—';}
   el.replaceWith(v);
 }
 // Genel degerlendirme ve takip metinleri uzun oldugunda sayfalar arasinda akabilir.
 // Alt onay/karar alanlari ise tek satirlik gridde kompakt ve tam gorunur.
 const decisionPrint=form.querySelector('.decision');
 const metaFields=[...decisionPrint.children].filter(el=>el.classList.contains('field')&&!el.classList.contains('two'));
 const decisionMeta=document.createElement('div');decisionMeta.className='decision-meta';
 for(const field of metaFields)decisionMeta.append(field);
 decisionPrint.append(decisionMeta);
 // Yazdirma kopyasinda yinelenen kimlikler birakilmaz.
 form.querySelectorAll('[id]').forEach(el=>el.removeAttribute('id'));
 // Grup basliklarinin ve tamamlanan kontrollerin ekrandaki sirasi degismez.
 root.append(top,form);
 $('printReport').replaceChildren(root);
}

function init(){buildRows();initOrg();initLogo();
 $('checklist').addEventListener('change',e=>{const id=e.target.dataset.status;if(!id)return;state[id].status=e.target.value;const label=document.querySelector(`[data-note-label="${id}"]`);label.textContent=state[id].status==='na'?'Uygulanamaz Gerekçesi':'Açıklama / Kanıt';e.target.dataset.value=e.target.value;if(['partial','fail','na'].includes(e.target.value))setDetails(id,true);updateMetrics();});
 $('checklist').addEventListener('input',e=>{const {control,prop}=e.target.dataset;if(control&&['note','action','owner','due'].includes(prop))state[control][prop]=e.target.value;});
 $('checklist').addEventListener('click',e=>{const b=e.target.closest('[data-toggle]');if(b){const id=b.dataset.toggle;setDetails(id,$('detail-'+id).hidden);}});
 $('excelExport').addEventListener('click',exportExcel);$('excelImport').addEventListener('click',()=>$('fileImport').click());
 $('fileImport').addEventListener('change',async e=>{const file=e.target.files?.[0];e.target.value='';if(!file)return;try{const result=await importExcel(file);if(confirm('Excel verileri bu formun üzerine aktarılsın mı?')){applySnapshot(result);notify('Excel verileri forma yüklendi.');}}catch(err){console.error(err);notify('Excel yüklenemedi: '+err.message,true);}});
 $('printBtn').addEventListener('click',()=>{showPrint();window.print();});window.addEventListener('beforeprint',showPrint);}
init();
</script>
</body>
</html>