@artilingo/artiframe-cli 2.0.4 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/LICENSE +660 -660
  2. package/bin/artiframe.js +0 -0
  3. package/bin/artiframe.php +0 -0
  4. package/core-stubs/app/ApiAuthControl.php +76 -0
  5. package/core-stubs/app/ApiControl.php +81 -35
  6. package/core-stubs/app/AuthControl.php +39 -0
  7. package/core-stubs/app/Database.php +36 -35
  8. package/core-stubs/app/DotEnv.php +19 -1
  9. package/core-stubs/app/ViewControl.php +47 -1
  10. package/core-stubs/bin/SQLMethod.php +261 -0
  11. package/core-stubs/bin/SystemMethod.php +115 -74
  12. package/core-stubs/bin/ViewMethod.php +19 -5
  13. package/core-stubs/docs/de.html +4325 -4347
  14. package/core-stubs/docs/en.html +4325 -4347
  15. package/core-stubs/docs/es.html +4325 -4347
  16. package/core-stubs/docs/fr.html +4325 -4347
  17. package/core-stubs/docs/tr.html +117 -46
  18. package/core-stubs/public/403.php +52 -0
  19. package/core-stubs/public/assets/css/components/header.css +174 -0
  20. package/core-stubs/public/assets/css/components/mobilenav.css +198 -0
  21. package/core-stubs/public/assets/css/components/sidebar.css +184 -0
  22. package/core-stubs/public/assets/css/root/app.css +975 -38
  23. package/core-stubs/public/assets/css/root/root.css +64 -0
  24. package/core-stubs/public/assets/js/components/header.js +380 -0
  25. package/core-stubs/public/assets/js/components/mobilenav.js +97 -0
  26. package/core-stubs/public/assets/js/components/theme-modal.js +74 -0
  27. package/core-stubs/public/assets/js/root/app.js +1870 -0
  28. package/core-stubs/public/includes/footer.php +4 -5
  29. package/core-stubs/public/includes/head-auth.php +65 -0
  30. package/core-stubs/public/includes/head.php +56 -17
  31. package/core-stubs/public/includes/header.php +135 -1
  32. package/core-stubs/public/includes/mobilenav.php +75 -3
  33. package/core-stubs/public/includes/sidebar.php +186 -1
  34. package/core-stubs/public/includes/theme-modal.php +1 -0
  35. package/db-studio/debug.log +3 -0
  36. package/db-studio/index.html +1755 -0
  37. package/db-studio/launcher.html +102 -0
  38. package/db-studio/launcher.js +180 -0
  39. package/db-studio/logo.svg +22 -0
  40. package/db-studio/main.js +568 -0
  41. package/db-studio/package-lock.json +1034 -0
  42. package/db-studio/package.json +16 -0
  43. package/db-studio/preload.js +29 -0
  44. package/devops-studio/api-inspector.js +222 -0
  45. package/devops-studio/index.html +1892 -0
  46. package/devops-studio/logo.svg +22 -0
  47. package/devops-studio/main.js +67 -0
  48. package/devops-studio/make-wizard.js +635 -0
  49. package/devops-studio/package-lock.json +871 -0
  50. package/devops-studio/package.json +12 -0
  51. package/package.json +5 -2
  52. package/scripts/postinstall.js +44 -8
  53. package/server-studio/.json-404-guard +1 -0
  54. package/server-studio/index.html +283 -0
  55. package/server-studio/logo.svg +22 -0
  56. package/server-studio/main.js +133 -0
  57. package/server-studio/package-lock.json +1179 -0
  58. package/server-studio/package.json +15 -0
  59. package/server-studio/router.php +99 -0
  60. package/src/App.php +29 -0
  61. package/src/Commands/AddCommand.php +26 -9
  62. package/src/Commands/DbExportCommand.php +131 -0
  63. package/src/Commands/DbStartCommand.php +46 -0
  64. package/src/Commands/DevopsCommand.php +35 -0
  65. package/src/Commands/GoCommand.php +48 -48
  66. package/src/Commands/ListCommand.php +68 -68
  67. package/src/Commands/MakeApiCommand.php +6 -1
  68. package/src/Commands/MakeViewCommand.php +25 -2
  69. package/src/Commands/NewProjectCommand.php +16 -11
  70. package/src/Commands/OptimizeCommand.php +61 -0
  71. package/src/Commands/RemoveCommand.php +35 -7
  72. package/src/Commands/ServeCommand.php +46 -91
  73. package/src/Commands/ShowCommand.php +24 -24
  74. package/src/Commands/UpgradeCommand.php +39 -10
  75. package/stubs/make/api-switch-case.stub +1 -1
  76. package/stubs/make/auth-api-standart.stub +20 -0
  77. package/stubs/make/auth-api-switch-case.stub +38 -0
  78. package/stubs/make/auth-view.stub +26 -0
  79. package/stubs/make/class.stub +10 -2
  80. package/stubs/make/view.stub +3 -0
  81. package/stubs/service/image.stub +334 -334
  82. package/stubs/service/iyzico.stub +637 -637
  83. package/stubs/service/jwt.stub +312 -312
  84. package/stubs/service/phpmailer.stub +143 -143
  85. package/stubs/service/pusher.stub +232 -232
  86. package/stubs/service/qrcode.stub +169 -169
  87. package/stubs/service/redis.stub +361 -361
  88. package/stubs/service/s3.stub +377 -377
  89. package/stubs/service/stripe.stub +526 -526
  90. package/stubs/service/twilio.stub +361 -361
@@ -0,0 +1,1755 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ArtiFrame DB Studio V2.5</title>
7
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/leader-line/1.0.7/leader-line.min.js"></script>
8
+ <style>
9
+ body { margin: 0; font-family: -apple-system, sans-serif; background: #1e1e1e; color: #d4d4d4; display: flex; height: 100vh; overflow: hidden; }
10
+ #sidebar { width: 250px; background: #252526; border-right: 1px solid #333; display: flex; flex-direction: column; }
11
+ .sidebar-header { padding: 12px 15px; font-weight: bold; background: #333333; font-size: 13px; letter-spacing: 0.5px; border-bottom: 1px solid #1e1e1e; }
12
+ #table-list { list-style: none; padding: 0; margin: 0; overflow-y: auto; flex: 1; }
13
+ .table-list-item { padding: 8px 15px; font-size: 13px; cursor: pointer; border-bottom: 1px solid #2d2d2d; display: flex; align-items: center; justify-content: space-between;}
14
+ .table-list-item:hover { background: #2a2d2e; }
15
+ .table-list-item .icon { font-size: 14px; margin-right: 8px; }
16
+ #main-area { flex: 1; display: flex; flex-direction: column; background: #1e1e1e; position: relative; }
17
+ #tab-bar { display: flex; background: #2d2d2d; border-bottom: 1px solid #1e1e1e; overflow-x: auto; }
18
+ .tab { padding: 8px 15px; font-size: 13px; cursor: pointer; border-right: 1px solid #1e1e1e; background: #2d2d2d; color: #999; display: flex; align-items: center; }
19
+ .tab.active { background: #1e1e1e; color: #fff; border-top: 2px solid #007acc; }
20
+ .tab-close { margin-left: 8px; font-size: 14px; opacity: 0.6; cursor: pointer; }
21
+ .tab-close:hover { opacity: 1; color: #f48771; }
22
+ #tab-content { flex: 1; overflow: hidden; position: relative; display: flex; }
23
+ .tab-pane { flex: 1; display: none; flex-direction: column; height: 100%; position: absolute; top:0; left:0; width: 100%; }
24
+ .tab-pane.active { display: flex; }
25
+ #board-container { flex: 1; overflow: hidden; cursor: grab; background: #1e1e1e; position: relative; }
26
+ #transform-container { transform-origin: 0 0; width: 100%; height: 100%; position: absolute; }
27
+ .table-card { position: absolute; width: 300px; background: #1e1e1e; border: 1px solid #444; border-radius: 6px; box-shadow: 0 4px 6px rgba(0,0,0,0.5); z-index: 10; cursor: move; }
28
+ .table-header { background: #007acc; padding: 0 12px; height: 34px; line-height: 34px; font-weight: bold; color: white; font-size: 13px; border-radius: 5px 5px 0 0; display: flex; justify-content: space-between; }
29
+ .table-body { padding-bottom: 12px; background: #252526; border-radius: 0 0 5px 5px; }
30
+ .table-row { display: flex; justify-content: space-between; align-items: center; padding: 0 12px; height: 26px; border-bottom: 1px solid #333; }
31
+ .badge-pk { background:#d7ba7d; color:#000; padding:2px 4px; border-radius:3px; font-size:10px; margin-right:4px; font-weight:bold; }
32
+ .badge-uk { background:#c586c0; color:#fff; padding:2px 4px; border-radius:3px; font-size:10px; margin-right:4px; font-weight:bold; }
33
+ .badge-ai { background:#569cd6; color:#fff; padding:2px 4px; border-radius:3px; font-size:10px; margin-right:4px; font-weight:bold; }
34
+ .badge-fk { background:#ce9178; color:#000; padding:2px 4px; border-radius:3px; font-size:10px; margin-right:4px; font-weight:bold; }
35
+ .badge-nn { background:#555; color:#fff; padding:2px 4px; border-radius:3px; font-size:10px; margin-right:4px; font-weight:bold; }
36
+ .stats-badge { background: rgba(0,0,0,0.3); padding: 2px 6px; border-radius: 12px; font-size: 10px; font-weight: normal; }
37
+ .details-header { padding: 15px; background: #252526; border-bottom: 1px solid #333; display: flex; gap: 15px; align-items: center; }
38
+ .sub-tab { padding: 5px 10px; cursor: pointer; color: #888; font-size: 13px; border-bottom: 2px solid transparent; }
39
+ .sub-tab.active { color: #fff; border-bottom: 2px solid #007acc; font-weight: bold; }
40
+ .sub-tab-content { flex: 1; overflow: auto; display: none; flex-direction: column; }
41
+ .sub-tab-content.active { display: flex; }
42
+ .data-table { width: 100%; border-collapse: collapse; font-size: 13px; }
43
+ .data-table th { background: #2d2d2d; color: #4ec9b0; padding: 10px; text-align: left; position: sticky; top: 0; border-bottom: 1px solid #444; z-index: 10; min-width: 100px; }
44
+ .data-table td { padding: 8px 10px; border-bottom: 1px solid #333; color: #d4d4d4; }
45
+ /* Sticky İşlem Column for horizontal scrolling */
46
+ .data-table th:last-child { position: sticky; right: 0; top: 0; z-index: 12; border-left: 1px solid #444; text-align: center; min-width: 80px; }
47
+ .data-table td:last-child { position: sticky; right: 0; z-index: 2; background: #1e1e1e; border-left: 1px solid #333; text-align: center; }
48
+ .data-table td[contenteditable=true] { outline: none; cursor: text; }
49
+ .data-table td[contenteditable=true]:focus { background: #094771; }
50
+ .btn { background: #0e639c; color: white; border: none; padding: 6px 12px; border-radius: 3px; cursor: pointer; font-size: 12px; }
51
+ .btn:hover { background: #1177bb; }
52
+ .btn-seed { background: #ce9178; color: #1e1e1e; font-weight: bold; }
53
+ .btn-seed:hover { background: #d7ba7d; }
54
+ .btn-danger { background: #d16969; }
55
+ .btn-danger:hover { background: #e06c6c; }
56
+ #status { font-size: 12px; padding: 10px; display: none; position: fixed; bottom: 20px; right: 20px; z-index: 9999; border-radius: 4px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
57
+ .success { background: #1b4b24; color: #50f17b; border: 1px solid #236a30; }
58
+ .error { background: #5a1d1d; color: #f48771; border: 1px solid #822222; }
59
+ .schema-row { display: flex; gap: 10px; align-items: center; padding: 10px 15px; border-bottom: 1px solid #333; }
60
+ .schema-row input[type="text"], .schema-row select { background: #3c3c3c; border: 1px solid #555; color: #ccc; padding: 5px; border-radius: 3px; font-size: 13px; }
61
+ .code-preview { background: #1e1e1e; border: 1px solid #333; padding: 15px; margin: 15px; border-radius: 5px; font-family: 'Courier New', Courier, monospace; font-size: 13px; color: #9cdcfe; }
62
+
63
+ /* Custom Tooltip */
64
+ .tooltip-container { position: relative; display: inline-flex; align-items: center; cursor: help; margin-left: 8px; }
65
+ .tooltip-icon { background: #007acc; color: white; border-radius: 50%; width: 16px; height: 16px; display: inline-flex; align-items: center; justify-content: center; font-size: 11px; font-weight: bold; border: 1px solid #1177bb; }
66
+ .tooltip-content {
67
+ visibility: hidden; width: 280px; background-color: #252526; color: #d4d4d4; text-align: left;
68
+ border-radius: 6px; padding: 12px; position: absolute; z-index: 999;
69
+ top: -10px; left: 100%; margin-left: 5px; opacity: 0; transition: opacity 0.2s, margin-left 0.2s;
70
+ font-size: 12px; box-shadow: 0 8px 16px rgba(0,0,0,0.6); border: 1px solid #444; line-height: 1.5;
71
+ }
72
+ .tooltip-content strong { color: #4ec9b0; font-size: 13px; display: block; margin-bottom: 5px; border-bottom: 1px solid #444; padding-bottom: 4px; }
73
+ .tooltip-content::after { content: ""; position: absolute; top: 12px; right: 100%; margin-left: 0; border-width: 6px; border-style: solid; border-color: transparent #444 transparent transparent; }
74
+ .tooltip-container:hover .tooltip-content { visibility: visible; opacity: 1; margin-left: 12px; }
75
+
76
+ .ctx-item { padding: 10px 15px; cursor: pointer; color: #d4d4d4; font-size: 13px; transition: 0.1s; }
77
+ .ctx-item:hover { background: #007acc; color: white; }
78
+ </style>
79
+
80
+ </head>
81
+ <body>
82
+ <div id="sidebar">
83
+ <div style="padding: 15px; text-align: center; border-bottom: 1px solid #1e1e1e; background: #252526;">
84
+ <img src="logo.svg" alt="ArtiFrame Logo" style="height: 35px; margin-bottom: 8px;">
85
+ <div style="font-weight: bold; color: #fff; font-size: 14px;">ArtiFrame <span style="color: #2ea043;">DB</span></div>
86
+ </div>
87
+ <div class="sidebar-header">TABLOLAR
88
+ <div style="float: right;">
89
+ <button class="btn" style="padding: 2px 6px; font-size:10px; background:#f39c12; margin-right:5px;" onclick="window.api.restartOffline()">🏠 Başlatıcı</button>
90
+ <button class="btn" style="padding: 2px 6px; font-size:10px;" onclick="openNewTableModal()">+ Yeni</button>
91
+ </div>
92
+ </div>
93
+ <ul id="table-list"></ul>
94
+ </div>
95
+ <div id="main-area"><div id="tab-bar"></div><div id="tab-content"></div></div>
96
+
97
+ <div id="status"></div>
98
+
99
+ <!-- ER Diagram Context Menu -->
100
+ <div id="er-context-menu" style="display:none; position:fixed; background:#252526; border:1px solid #444; box-shadow:0 4px 12px rgba(0,0,0,0.5); z-index:9999; border-radius:4px; overflow:hidden; min-width:200px;">
101
+ <div class="ctx-item" onclick="document.getElementById('er-context-menu').style.display='none'; openNewTableModal()">✨ Yeni Tablo Ekle</div>
102
+ <div style="height:1px; background:#444;"></div>
103
+ <div class="ctx-item" onclick="document.getElementById('er-context-menu').style.display='none'; triggerImportArtiframe()">📥 İçe Aktar (.artiframe)</div>
104
+ <div class="ctx-item" onclick="document.getElementById('er-context-menu').style.display='none'; exportArtiframe()">📤 Dışa Aktar (.artiframe)</div>
105
+ </div>
106
+
107
+
108
+
109
+
110
+ <div id="sync-modal" style="display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.8); z-index:9999; justify-content:center; align-items:center;">
111
+ <div style="background:#252526; padding:25px; border-radius:8px; border:1px solid #444; width:500px;">
112
+ <h3 style="margin-top:0; color:#d4d4d4; font-size:16px; border-bottom:1px solid #444; padding-bottom:10px;">⚡ Şema Senkronizasyonu (Migration)</h3>
113
+ <p style="font-size:12px; color:#aaa; margin-bottom:15px; line-height:1.5;">İçe aktardığınız dosya mevcut canlı veritabanınızla karşılaştırıldı. Veri kaybını önlemek amacıyla <b>hiçbir kolon silinmeyecektir</b>. Sadece yeni tablolar ve eksik kolonlar eklenecektir.</p>
114
+
115
+ <div id="sync-diff-content" style="background:#1e1e1e; border:1px solid #333; border-radius:4px; padding:10px; max-height:200px; overflow-y:auto; font-size:11px; color:#4ec9b0; font-family:monospace; margin-bottom:20px; white-space:pre-wrap;">
116
+ </div>
117
+
118
+ <div style="display:flex; flex-direction:column; gap:10px;">
119
+ <button class="btn" style="background:#007acc; padding:10px;" onclick="executeMigration()">🚀 Uygula (SQL Kodunu Hazırla)</button>
120
+ <div style="text-align:center; color:#888; font-size:10px;">veya</div>
121
+ <button class="btn" style="background:#f39c12; padding:10px;" onclick="restartInOfflineMode()">👁️ Çevrimdışı (Offline) Modda İncele</button>
122
+ <button class="btn btn-danger" style="padding:10px; margin-top:5px;" onclick="document.getElementById('sync-modal').style.display='none'">İptal</button>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ <div id="new-table-modal" style="display:none; position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.7); z-index:9999; justify-content:center; align-items:center;">
128
+ <div style="background:#252526; padding:20px; border-radius:8px; border:1px solid #444; width:300px; text-align:center;">
129
+ <h3 style="margin-top:0; color:#d4d4d4; font-size:16px;">Yeni Tablo Ekle</h3>
130
+ <p style="font-size:11px; color:#888; margin-bottom:10px;">İngilizce ve çoğul isim kullanmanız önerilir (örn: orders).</p>
131
+ <input type="text" id="new-table-name" placeholder="Tablo adı..." style="width:100%; padding:8px; background:#1e1e1e; border:1px solid #555; color:white; margin-bottom:15px; box-sizing:border-box; border-radius:4px;">
132
+ <div style="display:flex; gap:10px; justify-content:center;">
133
+ <button class="btn btn-danger" style="flex:1;" onclick="document.getElementById('new-table-modal').style.display='none'">İptal</button>
134
+ <button class="btn" style="background:#007acc; flex:1;" onclick="executeNewTable()">Oluştur</button>
135
+ </div>
136
+ </div>
137
+ </div>
138
+
139
+
140
+ <datalist id="dl-varchar"><option value="16"><option value="32"><option value="64"><option value="128"><option value="255"><option value="512"><option value="1024"><option value="65535"></datalist>
141
+ <datalist id="dl-decimal"><option value="10,2"><option value="8,2"><option value="14,4"></datalist>
142
+ <datalist id="dl-binary"><option value="16"><option value="32"><option value="64"></datalist>
143
+ <datalist id="dl-int"><option value="11"></datalist>
144
+
145
+
146
+ <script>
147
+ const typeHints = {
148
+ 'INT': '<strong>INT (Tam Sayı)</strong>Maksimum: 2147483647 (İşaretli).<br>ID, adet, yaş gibi standart tam sayı verileri için kullanılır.',
149
+ 'BIGINT': '<strong>BIGINT (Büyük Sayı)</strong>Maksimum: 9223372036854775807.<br>Çok yüksek rakamlar veya Snowflake ID\'ler için (örn: izlenme sayısı).',
150
+ 'TINYINT': '<strong>TINYINT (Küçük Sayı)</strong>Aralık: -128 ile 127.<br>Durum (Status) kodları, aşamalar veya kategoriler için bellek dostudur.',
151
+ 'DECIMAL': '<strong>DECIMAL (Hassas Küsürat)</strong>Veri kaybı yaşatmayan ondalıklı sayı.<br>Uzunluk "10,2" girilirse: 8 haneli tam sayı ve 2 haneli küsürat tutar. Finans ve maaş için zorunludur.',
152
+ 'VARCHAR': '<strong>VARCHAR (Kısa Metin)</strong>Maksimum: 65,535 byte.<br>Uzunluk belirtmek zorunludur. (Örn: İsim için 50, E-posta için 255).',
153
+ 'TEXT': '<strong>TEXT (Uzun Metin)</strong>Maksimum: 64 KB (Yaklaşık 65,000 karakter).<br>Uzunluk belirtilmez. Makale gövdesi veya uzun yorumlar için.',
154
+ 'LONGTEXT': '<strong>LONGTEXT (Dev Metin)</strong>Maksimum: 4 GB.<br>Uzunluk belirtilmez. Base64 formatlı görseller veya devasa veriler için.',
155
+ 'DATETIME': '<strong>DATETIME (Tarih ve Saat)</strong>Format: YYYY-MM-DD HH:MM:SS.<br>Sunucu zaman diliminden bağımsız sabit tarih tutar.',
156
+ 'DATE': '<strong>DATE (Tarih)</strong>Format: YYYY-MM-DD.<br>Sadece gün, ay, yıl tutar (örn: Doğum tarihi).',
157
+ 'TIMESTAMP': '<strong>TIMESTAMP (Zaman Damgası)</strong>1970\'ten bugüne saniye sayar.<br>CURRENT_TIMESTAMP ile kayıt eklendiğinde/güncellendiğinde otomatik saat tutmak için idealdir.',
158
+ 'BOOLEAN': '<strong>BOOLEAN (Mantıksal)</strong>MySQL\'de yerleşik Boolean yoktur, bu yüzden TINYINT(1) olarak çevrilir.<br><em>Dikkat:</em> NoSQL\'deki gibi "true/false" yerine SQL sorgularınızda <strong>1 (True)</strong> ve <strong>0 (False)</strong> değerlerini kullanmalısınız.',
159
+ 'JSON': '<strong>JSON (Dizi / Obje)</strong>İlişkisel olmayan esnek dizi ve objeleri tutar.<br>MySQL\'in yerleşik JSON fonksiyonlarıyla (Örn: JSON_EXTRACT) veritabanı seviyesinde sorgulanabilir.',
160
+ 'BINARY': '<strong>BINARY (Sabit Bayt)</strong>Sabit uzunluklu bayt dizisi.<br>Özellikle <em>UUID (16 byte)</em> saklamak için VARCHAR(36)\'dan çok daha yüksek performans sağlar. Uzunluk genelde 16 girilir.',
161
+ 'VARBINARY': '<strong>VARBINARY (Esnek Bayt)</strong>Değişken uzunluklu bayt dizisi.<br>Şifrelenmiş (encrypted) verileri, hash kodlarını veya küçük dosyaları saklamak için kullanılır.'
162
+ };
163
+
164
+ function updateTypeHint(selectEl) {
165
+ const container = selectEl.parentElement.querySelector('.tooltip-container');
166
+ const hintContent = container.querySelector('.tooltip-content');
167
+ const lenInput = selectEl.parentElement.querySelector('.col-len');
168
+ const val = selectEl.value;
169
+ const oldLen = lenInput.value.trim();
170
+
171
+ hintContent.innerHTML = typeHints[val] || 'Bilgi bulunamadı.';
172
+
173
+ const noLengthTypes = ['TEXT', 'LONGTEXT', 'DATE', 'DATETIME', 'TIMESTAMP', 'JSON', 'BOOLEAN'];
174
+ lenInput.removeAttribute('list');
175
+
176
+ if (noLengthTypes.includes(val)) {
177
+ lenInput.value = '';
178
+ lenInput.placeholder = '-';
179
+ lenInput.disabled = true;
180
+ } else {
181
+ lenInput.disabled = false;
182
+ lenInput.placeholder = 'Uzunluk';
183
+
184
+ if (val === 'VARCHAR') {
185
+ lenInput.setAttribute('list', 'dl-varchar');
186
+ if (!oldLen || oldLen.includes(',')) lenInput.value = '255';
187
+ }
188
+ else if (val === 'BINARY' || val === 'VARBINARY') {
189
+ lenInput.setAttribute('list', 'dl-binary');
190
+ if (!oldLen || oldLen.includes(',')) lenInput.value = '16';
191
+ }
192
+ else if (val === 'DECIMAL') {
193
+ lenInput.setAttribute('list', 'dl-decimal');
194
+ if (!oldLen || !oldLen.includes(',')) lenInput.value = '10,2';
195
+ }
196
+ else if (val === 'INT' || val === 'BIGINT' || val === 'TINYINT') {
197
+ lenInput.setAttribute('list', 'dl-int');
198
+ if (oldLen === '255' || oldLen === '16' || oldLen.includes(',')) lenInput.value = '';
199
+ }
200
+ }
201
+ }
202
+
203
+ const sidebarList = document.getElementById('table-list');
204
+ const tabBar = document.getElementById('tab-bar');
205
+ const tabContent = document.getElementById('tab-content');
206
+ const statusDiv = document.getElementById('status');
207
+
208
+ let currentTables = []; let lines = []; let tabs = []; let activeTabId = null; const pageState = {}; let globalDbData = null; let currentMutatingSql = ""; let currentAppMode = "live"; let currentProjectRoot = "";
209
+
210
+
211
+ function openNewTableModal() {
212
+ document.getElementById('new-table-modal').style.display = 'flex';
213
+ document.getElementById('new-table-name').value = '';
214
+ document.getElementById('new-table-name').focus();
215
+ }
216
+
217
+ async function executeNewTable() {
218
+ const tName = document.getElementById('new-table-name').value.trim();
219
+ if (!tName) {
220
+ showStatus('Lütfen tablo adı girin.', false);
221
+ return;
222
+ }
223
+ if (!/^[a-zA-Z0-9_]+$/.test(tName)) {
224
+ showStatus('Tablo adı sadece harf, rakam ve alt tire (_) içerebilir.', false);
225
+ return;
226
+ }
227
+
228
+ const sql = `CREATE TABLE IF NOT EXISTS \`${tName}\` (
229
+ \`id\` int NOT NULL AUTO_INCREMENT,
230
+ PRIMARY KEY (\`id\`)
231
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`;
232
+
233
+ document.getElementById('new-table-modal').style.display = 'none';
234
+ showStatus('Tablo oluşturuluyor...', true);
235
+
236
+ const res = await window.api.executeSql(sql);
237
+ if (res.success) {
238
+ showStatus(tName + ' tablosu başarıyla oluşturuldu.', true);
239
+ await loadSchema(); // Refresh
240
+ const tableObj = currentTables.find(t => t.name === tName);
241
+ openTab('table-' + tName, tName, 'table', tableObj);
242
+ } else {
243
+ showStatus('Hata: ' + res.error, false);
244
+ }
245
+ }
246
+
247
+
248
+
249
+
250
+ function asyncPrompt(msg) {
251
+ return new Promise((resolve) => {
252
+ document.getElementById('index-prompt-desc').innerText = msg;
253
+ const input = document.getElementById('index-prompt-input');
254
+ input.value = '';
255
+ document.getElementById('index-prompt-modal').style.display = 'flex';
256
+ input.focus();
257
+
258
+ document.getElementById('index-prompt-cancel').onclick = () => {
259
+ document.getElementById('index-prompt-modal').style.display = 'none';
260
+ resolve(null);
261
+ };
262
+ document.getElementById('index-prompt-ok').onclick = () => {
263
+ document.getElementById('index-prompt-modal').style.display = 'none';
264
+ resolve(input.value);
265
+ };
266
+ });
267
+ }
268
+ function customAlert(msg) {
269
+ document.getElementById('index-alert-desc').innerText = msg;
270
+ document.getElementById('index-alert-modal').style.display = 'flex';
271
+ }
272
+
273
+ function asyncConfirm(msg) {
274
+ return new Promise((resolve) => {
275
+ document.getElementById('index-confirm-desc').innerText = msg;
276
+ document.getElementById('index-confirm-modal').style.display = 'flex';
277
+ document.getElementById('index-confirm-modal').querySelector('.btn[onclick]').onclick = () => {
278
+ document.getElementById('index-confirm-modal').style.display = 'none';
279
+ resolve(false);
280
+ };
281
+ document.getElementById('index-confirm-ok').onclick = () => {
282
+ document.getElementById('index-confirm-modal').style.display = 'none';
283
+ resolve(true);
284
+ };
285
+ });
286
+ }
287
+
288
+ async function loadSchema() {
289
+ if (currentAppMode === 'offline') {
290
+ globalDbData = { tables: [] };
291
+ currentTables = [];
292
+ return;
293
+ }
294
+
295
+ const data = await window.api.getSchema();
296
+
297
+ if (currentAppMode === 'preview') {
298
+ globalDbData = data.schema || { tables: [] };
299
+ currentTables = globalDbData.tables || []; // FIX: set currentTables so the sidebar/links work if needed
300
+ tabs = [];
301
+
302
+ if (!globalDbData.tables || globalDbData.tables.length === 0) {
303
+ customAlert('Uyarı: Okunan şema dosyasında tablo bulunamadı.');
304
+ }
305
+
306
+ openTab('er-diagram', 'Şema Önizlemesi', 'er', globalDbData);
307
+ showStatus("Şema önizleme modunda açıldı.", true);
308
+ return;
309
+ }
310
+
311
+ if (data.error) { showStatus(data.error, false); return; }
312
+ currentTables = data.json ? (data.json.tables || []) : []; globalDbData = data.json;
313
+ renderSidebar();
314
+ if (!tabs.find(t => t.id === 'er-diagram')) { openTab('er-diagram', 'ER Diyagramı', 'er', data.json); }
315
+ else { renderBoard(data.json); }
316
+ if (!tabs.find(t => t.id === 'query-builder')) { openTab('query-builder', '⚡ Sorgu & Kod', 'query-builder', data.json); }
317
+ }
318
+
319
+
320
+
321
+ // --- VISUAL QUERY BUILDER (VQB) ---
322
+
323
+ function buildGlobalQueryPane(pane, json) {
324
+ let tableOpts = '<option value="">-- Tablo Seç --</option>';
325
+ if(json && json.tables) {
326
+ json.tables.forEach(t => tableOpts += `<option value="${t.name}">${t.name}</option>`);
327
+ }
328
+
329
+ pane.innerHTML = `
330
+ <div style="padding:15px; background:#252526; border-bottom:1px solid #333;">
331
+ <div style="display:flex; gap:15px; margin-bottom:15px;">
332
+ <div style="flex:1;">
333
+ <div style="font-size:11px; color:#888; margin-bottom:4px;">AKSYON TİPİ</div>
334
+ <select id="vqb-action" onchange="vqbRenderUI()" style="width:100%; padding:8px; background:#3c3c3c; color:white; border:1px solid #555; border-radius:3px;">
335
+ <option value="SELECT">SELECT (Kayıtları Getir)</option>
336
+ <option value="UPDATE">UPDATE (Toplu Güncelle)</option>
337
+ <option value="DELETE">DELETE (Toplu Sil)</option>
338
+ </select>
339
+ </div>
340
+ <div style="flex:3;">
341
+ <div style="font-size:11px; color:#888; margin-bottom:4px;">ANA TABLO</div>
342
+ <select id="vqb-main-table" onchange="vqbRenderUI(true)" style="width:100%; padding:8px; background:#3c3c3c; color:white; border:1px solid #555; border-radius:3px;">
343
+ ${tableOpts}
344
+ </select>
345
+ </div>
346
+ </div>
347
+
348
+ <!-- SELECT & GROUP BY Alanı -->
349
+ <div id="vqb-select-group-area" style="margin-bottom:15px; border-left:3px solid #2ecc71; padding-left:15px;">
350
+ <div style="font-size:11px; color:#2ecc71; margin-bottom:8px; font-weight:bold;">KOLON SEÇİMİ VE KÜMELEME (SELECT & GROUP BY)</div>
351
+ <div style="display:flex; gap:10px; align-items:center;">
352
+ <span style="color:#888; font-size:11px; width:50px;">SELECT:</span>
353
+
354
+ <div id="vqb-select-container" style="position:relative; flex:1;">
355
+ <div id="vqb-select-btn" style="padding:6px; background:#3c3c3c; color:white; border:1px solid #555; cursor:pointer; display:flex; justify-content:space-between; align-items:center; border-radius:3px;" onclick="const d = document.getElementById('vqb-select-dropdown'); d.style.display = d.style.display === 'block' ? 'none' : 'block'">
356
+ <span id="vqb-select-label" style="overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:90%;">Tüm Kolonlar (*)</span>
357
+ <span style="font-size:10px;">▼</span>
358
+ </div>
359
+ <div id="vqb-select-dropdown" style="display:none; position:absolute; top:100%; left:0; width:100%; max-height:250px; overflow-y:auto; background:#252526; border:1px solid #555; z-index:999; box-shadow: 0 4px 12px rgba(0,0,0,0.5);">
360
+ <div id="vqb-select-dropdown-list" style="padding:5px;" onchange="vqbUpdateSelectLabel(event)">
361
+ <label style="display:block; padding:4px;"><input type="checkbox" class="vqb-sel-chk" value="*" checked> Tüm Kolonlar (*)</label>
362
+ </div>
363
+ </div>
364
+ </div>
365
+
366
+
367
+ <span style="color:#888; font-size:11px; margin-left:15px;">GROUP BY:</span>
368
+ <select id="vqb-group-col" class="vqb-dynamic-col" style="padding:6px; background:#3c3c3c; color:white; border:1px solid #555; width:200px;">
369
+ <option value="">- Gruplama Yok -</option>
370
+ </select>
371
+ </div>
372
+ </div>
373
+
374
+ <!-- SET Alanı (Sadece UPDATE) -->
375
+ <div id="vqb-set-area" style="display:none; margin-bottom:15px; border-left:3px solid #f39c12; padding-left:15px;">
376
+ <div style="font-size:11px; color:#f39c12; margin-bottom:8px; font-weight:bold;">GÜNCELLENECEK ALANLAR (SET)</div>
377
+ <div id="vqb-sets" style="display:flex; flex-direction:column; gap:8px; margin-bottom:8px;"></div>
378
+ <button class="btn" style="background:#444; font-size:11px;" onclick="vqbAddSet()">+ Yeni Değer Ekle</button>
379
+ </div>
380
+
381
+ <!-- JOIN Alanı -->
382
+ <div id="vqb-join-area" style="margin-bottom:15px; border-left:3px solid #007acc; padding-left:15px;">
383
+ <div style="font-size:11px; color:#007acc; margin-bottom:8px; font-weight:bold;">TABLO BAĞLANTILARI (JOIN)</div>
384
+ <div id="vqb-joins" style="display:flex; flex-direction:column; gap:8px; margin-bottom:8px;"></div>
385
+ <button class="btn" style="background:#444; font-size:11px;" onclick="vqbAddJoin()">+ JOIN Ekle</button>
386
+ </div>
387
+
388
+ <!-- WHERE Alanı -->
389
+ <div id="vqb-where-area" style="margin-bottom:15px; border-left:3px solid #e74c3c; padding-left:15px;">
390
+ <div style="font-size:11px; color:#e74c3c; margin-bottom:8px; font-weight:bold;">FİLTRELER / KOŞULLAR (WHERE)</div>
391
+ <div id="vqb-wheres" style="display:flex; flex-direction:column; gap:8px; margin-bottom:8px;"></div>
392
+ <button class="btn" style="background:#444; font-size:11px;" onclick="vqbAddWhere()">+ Koşul Ekle</button>
393
+ </div>
394
+
395
+ <!-- ORDER & LIMIT Alanı -->
396
+ <div id="vqb-sort-limit-area" style="margin-bottom:15px; border-left:3px solid #9b59b6; padding-left:15px;">
397
+ <div style="font-size:11px; color:#9b59b6; margin-bottom:8px; font-weight:bold;">SIRALAMA VE LİMİT (ORDER BY & LIMIT)</div>
398
+ <div style="display:flex; gap:10px; align-items:center;">
399
+ <select id="vqb-order-col" class="vqb-dynamic-col" style="padding:6px; background:#3c3c3c; color:white; border:1px solid #555; width:200px;">
400
+ <option value="">- Sıralama Yok -</option>
401
+ </select>
402
+ <select id="vqb-order-dir" style="padding:6px; background:#3c3c3c; color:white; border:1px solid #555; width:120px;">
403
+ <option value="ASC">Artan (ASC)</option>
404
+ <option value="DESC">Azalan (DESC)</option>
405
+ </select>
406
+
407
+ <span style="color:#888; font-size:11px; margin-left:10px;">LİMİT:</span>
408
+ <input type="number" id="vqb-limit" value="50" min="1" max="10000" style="width:80px; padding:6px; background:#3c3c3c; color:white; border:1px solid #555;">
409
+ </div>
410
+ </div>
411
+
412
+ <button class="btn" id="vqb-run-btn" style="background:#007acc; font-weight:bold; padding:8px 20px; font-size:14px; width:100%;" onclick="vqbExecute()">🚀 SORGULA VE KOD ÜRET</button>
413
+ </div>
414
+
415
+ <div class="code-preview" id="gqb-code" style="position:relative; cursor:pointer; margin:15px; display:none;" onclick="copyGlobalCode()" title="Kopyalamak için tıklayın">
416
+ <div style="position:absolute; top:10px; right:15px; font-size:16px; opacity:0.7;">📋</div>
417
+ <div id="gqb-code-text" style="white-space:pre-wrap;"></div>
418
+ </div>
419
+
420
+
421
+ <div id="vqb-execute-btn-container" style="display:none; text-align:right; margin-bottom: 15px; margin-right: 15px;">
422
+ <button class="btn btn-danger" style="font-weight:bold; padding:8px 15px;" onclick="vqbRunMutatingSql()">🔥 BU SORGUYU VERİTABANINDA ÇALIŞTIR (TEHLİKELİ)</button>
423
+ </div>
424
+
425
+ <div id="vqb-results-info" style="padding:5px 15px; font-size:11px; color:#888;"></div>
426
+ <div style="flex:1; overflow:auto;"><table class="data-table"><thead id="gqb-th"></thead><tbody id="gqb-tb"></tbody></table></div>
427
+ `;
428
+ }
429
+
430
+ function vqbGetColOptions() {
431
+ const mainT = document.getElementById('vqb-main-table').value;
432
+ let opts = '<option value="">- Seç -</option>';
433
+ if(mainT) {
434
+ const mt = currentTables.find(t => t.name === mainT);
435
+ if(mt) {
436
+ opts += `<optgroup label="${mt.name}">`;
437
+ mt.columns.forEach(c => opts += `<option value="${mt.name}.${c.name}">${mt.name}.${c.name}</option>`);
438
+ opts += '</optgroup>';
439
+ }
440
+ }
441
+ // Add joined tables cols
442
+ const joinSelects = document.querySelectorAll('.vqb-join-table');
443
+ joinSelects.forEach(js => {
444
+ if(js.value) {
445
+ const jt = currentTables.find(t => t.name === js.value);
446
+ if(jt) {
447
+ opts += `<optgroup label="${jt.name}">`;
448
+ jt.columns.forEach(c => opts += `<option value="${jt.name}.${c.name}">${jt.name}.${c.name}</option>`);
449
+ opts += '</optgroup>';
450
+ }
451
+ }
452
+ });
453
+ return opts;
454
+ }
455
+
456
+ function vqbRenderUI(clearAll = false) {
457
+ const action = document.getElementById('vqb-action').value;
458
+ const mainTable = document.getElementById('vqb-main-table').value;
459
+
460
+ document.getElementById('vqb-set-area').style.display = (action === 'UPDATE') ? 'block' : 'none';
461
+ document.getElementById('vqb-select-group-area').style.display = (action === 'SELECT') ? 'block' : 'none';
462
+
463
+ const btn = document.getElementById('vqb-run-btn');
464
+ if(action === 'SELECT') { btn.style.background = '#007acc'; btn.innerHTML = '🔍 SORGULA VE KOD ÜRET'; }
465
+ if(action === 'UPDATE') { btn.style.background = '#f39c12'; btn.innerHTML = '⚠️ GÜNCELLE (UPDATE) VE KOD ÜRET'; }
466
+ if(action === 'DELETE') { btn.style.background = '#e74c3c'; btn.innerHTML = '🛑 SİL (DELETE) VE KOD ÜRET'; }
467
+
468
+ if(clearAll) {
469
+ document.getElementById('vqb-sets').innerHTML = '';
470
+ document.getElementById('vqb-joins').innerHTML = '';
471
+ document.getElementById('vqb-wheres').innerHTML = '';
472
+ }
473
+
474
+ // Refresh column dropdowns in existing rows
475
+ const opts = vqbGetColOptions();
476
+ document.querySelectorAll('.vqb-dynamic-col').forEach(sel => {
477
+ const oldVal = sel.value;
478
+ sel.innerHTML = opts;
479
+ sel.value = oldVal;
480
+ });
481
+ vqbUpdateSelectCheckboxes();
482
+ }
483
+
484
+ function vqbUpdateSelectCheckboxes() {
485
+ const list = document.getElementById('vqb-select-dropdown-list');
486
+ if(!list) return;
487
+ // Preserve currently checked items
488
+ const oldChecked = Array.from(list.querySelectorAll('.vqb-sel-chk:checked')).map(c => c.value);
489
+
490
+ const mainT = document.getElementById('vqb-main-table').value;
491
+ let html = `
492
+ <label style="display:block; padding:6px; cursor:pointer; border-bottom:1px solid #333;"><input type="checkbox" class="vqb-sel-chk" value="*" ${oldChecked.includes('*') || oldChecked.length===0 ? 'checked' : ''}> Tüm Kolonlar (*)</label>
493
+ <label style="display:block; padding:6px; cursor:pointer; border-bottom:1px solid #333;"><input type="checkbox" class="vqb-sel-chk" value="COUNT(*) AS toplam_kayit" ${oldChecked.includes('COUNT(*) AS toplam_kayit') ? 'checked' : ''}> <span style="color:#2ecc71;">Toplam Kayıt Sayısı (COUNT)</span></label>
494
+ `;
495
+
496
+ if(mainT) {
497
+ const mt = currentTables.find(t => t.name === mainT);
498
+ if(mt) {
499
+ html += `<div style="font-size:10px; color:#aaa; margin-top:8px; padding-left:4px; font-weight:bold;">${mt.name} (Ana Tablo)</div>`;
500
+ mt.columns.forEach(c => {
501
+ const val = `\`${mt.name}\`.\`${c.name}\``;
502
+ html += `<label style="display:block; padding:4px; margin-left:10px; cursor:pointer;"><input type="checkbox" class="vqb-sel-chk" value="${val}" ${oldChecked.includes(val) ? 'checked' : ''}> ${c.name}</label>`;
503
+ });
504
+ }
505
+ }
506
+
507
+ const joinSelects = document.querySelectorAll('.vqb-join-table');
508
+ joinSelects.forEach(js => {
509
+ if(js.value) {
510
+ const jt = currentTables.find(t => t.name === js.value);
511
+ if(jt) {
512
+ html += `<div style="font-size:10px; color:#aaa; margin-top:8px; padding-left:4px; font-weight:bold;">${jt.name} (JOIN)</div>`;
513
+ jt.columns.forEach(c => {
514
+ const val = `\`${jt.name}\`.\`${c.name}\``;
515
+ html += `<label style="display:block; padding:4px; margin-left:10px; cursor:pointer;"><input type="checkbox" class="vqb-sel-chk" value="${val}" ${oldChecked.includes(val) ? 'checked' : ''}> ${c.name}</label>`;
516
+ });
517
+ }
518
+ }
519
+ });
520
+ list.innerHTML = html;
521
+ vqbUpdateSelectLabel();
522
+ }
523
+
524
+ function vqbUpdateSelectLabel(e) {
525
+ const list = document.getElementById('vqb-select-dropdown-list');
526
+ const asterisk = list.querySelector('input[value="*"]');
527
+
528
+ // If asterisk was clicked
529
+ if (e && e.target === asterisk && asterisk.checked) {
530
+ list.querySelectorAll('.vqb-sel-chk').forEach(c => { if(c !== asterisk) c.checked = false; });
531
+ } else if (e && e.target !== asterisk && e.target.checked) {
532
+ if(asterisk) asterisk.checked = false;
533
+ }
534
+
535
+ const checked = Array.from(list.querySelectorAll('.vqb-sel-chk:checked'));
536
+ if(checked.length === 0 && asterisk) {
537
+ asterisk.checked = true;
538
+ checked.push(asterisk);
539
+ }
540
+
541
+ const label = document.getElementById('vqb-select-label');
542
+ if(asterisk && asterisk.checked) {
543
+ label.innerText = "Tüm Kolonlar (*)";
544
+ label.style.color = "white";
545
+ } else {
546
+ label.innerText = checked.map(c => c.parentElement.innerText.trim()).join(', ');
547
+ label.style.color = "#4ec9b0";
548
+ }
549
+ }
550
+
551
+ // Hide on outside click
552
+ document.addEventListener('click', function(event) {
553
+ const container = document.getElementById('vqb-select-container');
554
+ const dropdown = document.getElementById('vqb-select-dropdown');
555
+ if(container && dropdown && !container.contains(event.target)) {
556
+ dropdown.style.display = 'none';
557
+ }
558
+ });
559
+
560
+
561
+ function vqbAddSet() {
562
+ const row = document.createElement('div'); row.style.display = 'flex'; row.style.gap = '10px';
563
+ row.innerHTML = `
564
+ <select class="vqb-dynamic-col vqb-set-col" style="padding:6px; background:#3c3c3c; color:white; border:1px solid #555; width:200px;">${vqbGetColOptions()}</select>
565
+ <span style="color:#888; padding-top:6px;">=</span>
566
+ <input type="text" class="vqb-set-val" placeholder="Yeni Değer..." style="flex:1; padding:6px; background:#3c3c3c; color:white; border:1px solid #555;">
567
+ <button class="btn btn-danger" onclick="this.parentElement.remove()">X</button>
568
+ `;
569
+ document.getElementById('vqb-sets').appendChild(row);
570
+ }
571
+
572
+ function vqbAddJoin() {
573
+ let tableOpts = '<option value="">- Tablo -</option>';
574
+ currentTables.forEach(t => tableOpts += `<option value="${t.name}">${t.name}</option>`);
575
+
576
+ const row = document.createElement('div'); row.style.display = 'flex'; row.style.gap = '10px';
577
+ row.innerHTML = `
578
+ <select class="vqb-join-type" style="padding:6px; background:#3c3c3c; color:white; border:1px solid #555;">
579
+ <option value="INNER JOIN">INNER JOIN</option><option value="LEFT JOIN">LEFT JOIN</option><option value="RIGHT JOIN">RIGHT JOIN</option>
580
+ </select>
581
+ <select class="vqb-join-table" onchange="vqbRenderUI()" style="padding:6px; background:#3c3c3c; color:white; border:1px solid #555;">${tableOpts}</select>
582
+ <span style="color:#888; padding-top:6px;">ON</span>
583
+ <select class="vqb-dynamic-col vqb-join-on1" style="padding:6px; background:#3c3c3c; color:white; border:1px solid #555; width:150px;">${vqbGetColOptions()}</select>
584
+ <span style="color:#888; padding-top:6px;">=</span>
585
+ <select class="vqb-dynamic-col vqb-join-on2" style="padding:6px; background:#3c3c3c; color:white; border:1px solid #555; width:150px;">${vqbGetColOptions()}</select>
586
+ <button class="btn btn-danger" onclick="this.parentElement.remove(); vqbRenderUI();">X</button>
587
+ `;
588
+ document.getElementById('vqb-joins').appendChild(row);
589
+ }
590
+
591
+ function vqbAddWhere() {
592
+ const isFirst = document.getElementById('vqb-wheres').children.length === 0;
593
+ const row = document.createElement('div'); row.style.display = 'flex'; row.style.gap = '10px';
594
+
595
+ let logicHtml = isFirst ? `<span style="width:60px; display:inline-block; text-align:right; color:#888; padding-top:6px;">WHERE</span>` : `<select class="vqb-where-logic" style="width:60px; padding:6px; background:#3c3c3c; color:white; border:1px solid #555;"><option value="AND">AND</option><option value="OR">OR</option></select>`;
596
+
597
+ row.innerHTML = `
598
+ ${logicHtml}
599
+ <select class="vqb-dynamic-col vqb-where-col" style="padding:6px; background:#3c3c3c; color:white; border:1px solid #555; width:180px;">${vqbGetColOptions()}</select>
600
+ <select class="vqb-where-op" onchange="const val = this.nextElementSibling; if(this.value.includes('NULL')){val.disabled=true;val.style.opacity=0.5;}else{val.disabled=false;val.style.opacity=1;}" style="padding:6px; background:#3c3c3c; color:white; border:1px solid #555; width:120px;">
601
+ <option value="=">=</option><option value="!=">!=</option><option value=">">></option><option value=">=">>=</option>
602
+ <option value="<"><</option><option value="<="><=</option><option value="LIKE">LIKE</option>
603
+ <option value="IN">IN</option><option value="BETWEEN">BETWEEN</option><option value="IS NULL">IS NULL</option><option value="IS NOT NULL">IS NOT NULL</option>
604
+ </select>
605
+ <input type="text" class="vqb-where-val" placeholder="Değer..." style="flex:1; padding:6px; background:#3c3c3c; color:white; border:1px solid #555;">
606
+ <button class="btn btn-danger" onclick="this.parentElement.remove()">X</button>
607
+ `;
608
+ document.getElementById('vqb-wheres').appendChild(row);
609
+ }
610
+
611
+
612
+ function translateMySqlError(err) {
613
+ if(!err) return "Bilinmeyen bir veritabanı hatası oluştu.";
614
+ if(err.includes("only_full_group_by") || err.includes("not in GROUP BY clause")) {
615
+ return "💡 Kural Hatası (GROUP BY):\nKümeleme (GROUP BY) yaptığınızda, SELECT kısmında seçtiğiniz kolonlar ya GROUP BY alanında da seçili olmalı ya da COUNT(), MAX(), SUM() gibi bir fonksiyonun içinde (Aggregate) olmalıdır. Lütfen SELECT kısmındaki 'salt' kolonları kaldırın veya sadece grupladığınız kolonu/fonksiyonları seçin.";
616
+ }
617
+ if(err.includes("Unknown column")) {
618
+ const col = err.match(/Unknown column '(.*?)'/);
619
+ return `💡 Kolon Bulunamadı:\nSorguda (${col ? col[1] : 'Bilinmeyen'}) isimli bir kolon kullanmaya çalıştınız ancak bağlı tablolarda böyle bir kolon yok. JOIN bağlantılarınızı veya kolon isimlerini kontrol edin.`;
620
+ }
621
+ if(err.includes("You have an error in your SQL syntax")) {
622
+ return "💡 Sözdizimi (Syntax) Hatası:\nÜretilen SQL sorgusunda yapısal bir hata var. Muhtemelen bir değeri veya koşulu eksik bıraktınız.";
623
+ }
624
+ if(err.includes("ambiguous")) {
625
+ return "💡 Belirsiz Kolon (Ambiguous):\nİki tabloda da aynı isimli kolon var (örn: 'id'). Sistem hangisini kastettiğinizi anlayamıyor. Lütfen kolonları seçerken 'tablo_adi.kolon_adi' şeklinde belirtin.";
626
+ }
627
+ return "💡 Veritabanı Hatası: " + err;
628
+ }
629
+
630
+ function copyGlobalCode() {
631
+ const text = document.getElementById('gqb-code-text').innerText;
632
+ navigator.clipboard.writeText(text);
633
+ showStatus('Kod Panoya Kopyalandı!', true);
634
+ }
635
+
636
+
637
+ async function vqbRunMutatingSql() {
638
+ if(!currentMutatingSql) return;
639
+
640
+ if(!(await asyncConfirm(`Aşağıdaki kalıcı işlem veritabanında ÇALIŞTIRILACAK:\n\n${currentMutatingSql}\n\nBu işlemin geri dönüşü yoktur. Onaylıyor musunuz?`))) return;
641
+
642
+ const info = document.getElementById('vqb-results-info');
643
+ info.innerHTML = `<span style="color:#007acc; font-weight:bold;">İşlem yürütülüyor...</span>`;
644
+
645
+ const res = await window.api.executeSql(currentMutatingSql);
646
+ if(res.success) {
647
+ info.innerHTML = `<span style="color:#4ec9b0; font-weight:bold;">İşlem başarıyla tamamlandı! (Kalıcı olarak veritabanına işlendi)</span>`;
648
+ showStatus('Veritabanı işlemi başarılı!', true);
649
+ document.getElementById('vqb-execute-btn-container').style.display = 'none';
650
+ } else {
651
+ info.innerHTML = `<span style="color:#f48771; font-weight:bold; white-space:pre-wrap;">${translateMySqlError(res.error)}</span>`;
652
+ showStatus('Hata oluştu', false);
653
+ }
654
+ }
655
+
656
+ async function vqbExecute() {
657
+ const action = document.getElementById('vqb-action').value;
658
+ const mainTable = document.getElementById('vqb-main-table').value;
659
+ if(!mainTable) { showStatus('Lütfen bir ana tablo seçin!', false); return; }
660
+
661
+ let sql = ""; let phpCode = "";
662
+ let joins = []; let wheres = []; let sets = [];
663
+
664
+ // Build JOINs
665
+ document.getElementById('vqb-joins').querySelectorAll('div').forEach(r => {
666
+ const type = r.querySelector('.vqb-join-type').value;
667
+ const tbl = r.querySelector('.vqb-join-table').value;
668
+ const on1 = r.querySelector('.vqb-join-on1').value;
669
+ const on2 = r.querySelector('.vqb-join-on2').value;
670
+ if(tbl && on1 && on2) joins.push(`${type} \`${tbl}\` ON ${on1} = ${on2}`);
671
+ });
672
+
673
+ // Build SETs (Update)
674
+ document.getElementById('vqb-sets').querySelectorAll('div').forEach(r => {
675
+ const col = r.querySelector('.vqb-set-col').value;
676
+ const val = r.querySelector('.vqb-set-val').value;
677
+ const isNumeric = !isNaN(val) && val !== '';
678
+ if(col) sets.push(`${col} = ${isNumeric ? val : "'" + val + "'"}`);
679
+ });
680
+
681
+ // Build WHEREs
682
+ document.getElementById('vqb-wheres').querySelectorAll('div').forEach((r, idx) => {
683
+ const logic = r.querySelector('.vqb-where-logic') ? r.querySelector('.vqb-where-logic').value : '';
684
+ const col = r.querySelector('.vqb-where-col').value;
685
+ const op = r.querySelector('.vqb-where-op').value;
686
+ const rawVal = r.querySelector('.vqb-where-val').value.trim();
687
+ if(!col) return;
688
+
689
+ let valStr = '';
690
+ if (op === 'IS NULL' || op === 'IS NOT NULL') {
691
+ valStr = '';
692
+ } else if (op === 'BETWEEN') {
693
+ const bw = rawVal.split(',').map(s => isNaN(s.trim()) ? `'${s.trim()}'` : s.trim());
694
+ if(bw.length >= 2) valStr = `${bw[0]} AND ${bw[1]}`;
695
+ else valStr = rawVal;
696
+ } else if (op === 'IN') {
697
+ const sqlArr = rawVal.split(',').map(s => isNaN(s.trim()) ? `'${s.trim()}'` : s.trim()).join(', ');
698
+ valStr = `(${sqlArr})`;
699
+ } else if (op === 'LIKE') {
700
+ valStr = `'%${rawVal}%'`;
701
+ } else {
702
+ valStr = (!isNaN(rawVal) && rawVal !== '') ? rawVal : `'${rawVal}'`;
703
+ }
704
+
705
+ wheres.push(`${idx > 0 ? logic : ''} ${col} ${op} ${valStr}`.trim());
706
+ });
707
+
708
+ const joinStr = joins.length > 0 ? ' ' + joins.join(' ') : '';
709
+ const whereStr = wheres.length > 0 ? ' WHERE ' + wheres.join(' ') : '';
710
+
711
+ const checkedSelects = Array.from(document.querySelectorAll('#vqb-select-dropdown-list .vqb-sel-chk:checked')).map(c => c.value);
712
+ const selectCols = checkedSelects.length > 0 ? checkedSelects.join(', ') : '*';
713
+ const groupCol = document.getElementById('vqb-group-col').value;
714
+ const groupStr = groupCol ? ` GROUP BY ${groupCol}` : '';
715
+
716
+ const orderCol = document.getElementById('vqb-order-col').value;
717
+ const orderDir = document.getElementById('vqb-order-dir').value;
718
+ const limit = document.getElementById('vqb-limit').value.trim();
719
+
720
+ const orderStr = orderCol ? ` ORDER BY ${orderCol} ${orderDir}` : '';
721
+ const limitStr = limit ? ` LIMIT ${limit}` : '';
722
+
723
+ if(action === 'SELECT') {
724
+ sql = `SELECT ${selectCols} FROM \`${mainTable}\`${joinStr}${whereStr}${groupStr}${orderStr}${limitStr}`;
725
+ if(joins.length === 0 && wheres.length === 1 && wheres[0].includes('=')) {
726
+ // Simple ArtiFrame Code
727
+
728
+ let phpOrderBy = orderCol ? `'${orderCol.replace(mainTable+'.', '')} ${orderDir}'` : 'null';
729
+ let phpLimit = limit ? limit : 'null';
730
+ phpCode = `// Basit Kullanım (ArtiFrame ORM):\n$sonuclar = dbList('${mainTable}', [...koşullar...], ${phpOrderBy}, ${phpLimit});\n\n// Tam SQL Sorgusu:\n$sql = "${sql}";\n$sonuclar = dbQuery($sql);`;
731
+ } else {
732
+ phpCode = `// Karmaşık sorgu sebebiyle saf SQL metodu önerilir:\n$sql = "${sql}";\n$sonuclar = dbQuery($sql);`;
733
+ }
734
+ } else if (action === 'UPDATE') {
735
+ if(sets.length === 0) { showStatus('UPDATE için en az bir SET değeri girmelisiniz!', false); return; }
736
+ if(wheres.length === 0) {
737
+ if(!(await asyncConfirm('DİKKAT: WHERE koşulu girmediniz! Tablodaki TÜM kayıtlar güncellenecek. Emin misiniz?'))) return;
738
+ }
739
+ sql = `UPDATE \`${mainTable}\`${joinStr} SET ${sets.join(', ')}${whereStr}${orderStr}${limitStr}`;
740
+ phpCode = `// Toplu Güncelleme Kodu:\n$sql = "${sql}";\n$etkilenenKayit = dbExecute($sql);`;
741
+ } else if (action === 'DELETE') {
742
+ if(wheres.length === 0) {
743
+ if(!(await asyncConfirm('DİKKAT: WHERE koşulu girmediniz! Tablodaki TÜM kayıtlar SİLİNECEK. Emin misiniz?'))) return;
744
+ }
745
+ sql = `DELETE FROM \`${mainTable}\`${joinStr}${whereStr}${orderStr}${limitStr}`;
746
+ phpCode = `// Toplu Silme Kodu:\n$sql = "${sql}";\n$etkilenenKayit = dbExecute($sql);`;
747
+ }
748
+
749
+ document.getElementById('gqb-code').style.display = 'block';
750
+ document.getElementById('gqb-code-text').innerText = phpCode;
751
+
752
+ const thead = document.getElementById('gqb-th');
753
+ const tbody = document.getElementById('gqb-tb');
754
+ const info = document.getElementById('vqb-results-info');
755
+
756
+ // --- SAFETY PATTERN ---
757
+ if (action !== 'SELECT') {
758
+ currentMutatingSql = sql;
759
+ document.getElementById('vqb-execute-btn-container').style.display = 'block';
760
+ thead.innerHTML = ''; tbody.innerHTML = '';
761
+ info.innerHTML = `<span style="color:#f39c12; font-weight:bold;">Güvenlik Uyarısı:</span> Kod başarıyla üretildi. Sadece kodu kopyalamak istiyorsanız yukarıdan alabilirsiniz. Eğer işlemi lokal veritabanınızda kalıcı olarak çalıştırmak istiyorsanız yandaki kırmızı butona tıklayın.`;
762
+ return;
763
+ }
764
+
765
+ // IF SELECT, Execute immediately to preview data
766
+ document.getElementById('vqb-execute-btn-container').style.display = 'none';
767
+ const res = await window.api.executeSql(sql);
768
+
769
+
770
+ // If SELECT
771
+ if(res.success && res.data && res.data.length > 0) {
772
+ info.innerText = `Bulunan sonuçları aşağıdaki tablodan doğrudan düzenleyebilir ve kaydedebilirsiniz.`;
773
+ const t = currentTables.find(x => x.name === mainTable);
774
+ const pkCol = t ? t.columns.find(c => c.isPk) : null;
775
+
776
+ // Extract all column names from first result row (because of joins, columns might not just be from main table)
777
+ const colNames = Object.keys(res.data[0]);
778
+
779
+ let headHtml = '<tr>'; colNames.forEach(c => headHtml += `<th>${c}</th>`); headHtml += '<th>İşlem</th></tr>';
780
+ thead.innerHTML = headHtml;
781
+
782
+ let bodyHtml = '';
783
+ res.data.forEach(row => {
784
+ let tr = '<tr>';
785
+ colNames.forEach(c => {
786
+ // Only main table columns can be cleanly updated via generic saveRow, but let's allow trying.
787
+ const val = row[c] !== null ? row[c] : '';
788
+ tr += `<td contenteditable="true" data-col="${c}">${val}</td>`;
789
+ });
790
+ const pkVal = pkCol && row[pkCol.name] ? row[pkCol.name] : '';
791
+ if(pkVal) {
792
+ tr += `<td><button class="btn" onclick="saveRow(this, '${mainTable}', '${pkCol.name}', '${pkVal}')">💾</button> <button class="btn btn-danger" onclick="deleteRow(this, '${mainTable}', '${pkCol.name}', '${pkVal}')">🗑️</button></td></tr>`;
793
+ } else {
794
+ tr += `<td><span style="font-size:10px; color:#888;">(JOIN Salt Okunur)</span></td></tr>`;
795
+ }
796
+ bodyHtml += tr;
797
+ });
798
+ tbody.innerHTML = bodyHtml;
799
+ } else {
800
+ thead.innerHTML = '';
801
+ tbody.innerHTML = `<tr><td style="padding: 20px; text-align: left; color: #f48771; white-space:pre-wrap; line-height:1.6; border:1px solid #e74c3c; background:rgba(231,76,60,0.1);">${res.error ? translateMySqlError(res.error) : 'Bu kritere uygun sonuç bulunamadı.'}</td></tr>`;
802
+ info.innerText = '';
803
+ }
804
+ }
805
+
806
+
807
+ // --- CONTEXT MENU & .ARTIFRAME EXPORT/IMPORT ---
808
+
809
+ document.addEventListener('contextmenu', function(e) {
810
+ // Sadece ER Diyagramı içinde sağ tıklamaya izin ver
811
+ const pane = document.getElementById('pane-er-diagram');
812
+ if (pane && pane.contains(e.target)) {
813
+ e.preventDefault();
814
+ const menu = document.getElementById('er-context-menu');
815
+ menu.style.display = 'block';
816
+ menu.style.left = e.clientX + 'px';
817
+ menu.style.top = e.clientY + 'px';
818
+ }
819
+ });
820
+
821
+ document.addEventListener('click', function(e) {
822
+ const menu = document.getElementById('er-context-menu');
823
+ if (menu && menu.style.display === 'block') {
824
+ menu.style.display = 'none';
825
+ }
826
+ });
827
+
828
+ async function exportArtiframe() {
829
+ if (!globalDbData) return;
830
+
831
+ // Mevcut dizilimi (UI koordinatlarını) kaydet
832
+ document.querySelectorAll('.table-card').forEach(card => {
833
+ const tableName = card.dataset.tableName;
834
+ if (tableName) {
835
+ const t = globalDbData.tables.find(x => x.name === tableName);
836
+ if (t) {
837
+ if (!t.ui) t.ui = {};
838
+ t.ui.left = card.style.left;
839
+ t.ui.top = card.style.top;
840
+ }
841
+ }
842
+ });
843
+
844
+ const res = await window.api.saveArtiframeFile(globalDbData);
845
+ if (res) showStatus("Diyagram şifreli .artiframe dosyası olarak başarıyla dışa aktarıldı.", true);
846
+ }
847
+
848
+
849
+ let pendingMigrationSql = "";
850
+
851
+ function generateSchemaDiff(liveData, importedData) {
852
+ let sqlLines = [];
853
+ let summary = "";
854
+
855
+ const liveTables = liveData.tables || [];
856
+ const impTables = importedData.tables || [];
857
+
858
+ impTables.forEach(impT => {
859
+ const liveT = liveTables.find(t => t.name === impT.name);
860
+
861
+ if (!liveT) {
862
+ // NEW TABLE
863
+ summary += `[+] Yeni Tablo Eklenecek: ${impT.name}\n`;
864
+ let cols = [];
865
+ let pks = [];
866
+ impT.columns.forEach(c => {
867
+ let def = `\`${c.name}\` ${c.type}`;
868
+ if(!c.nullable) def += ' NOT NULL';
869
+ if(c.isAi) def += ' AUTO_INCREMENT';
870
+ if(c.defaultVal !== null && c.defaultVal !== undefined) {
871
+ if (c.defaultVal.toUpperCase() === 'CURRENT_TIMESTAMP') def += ' DEFAULT CURRENT_TIMESTAMP';
872
+ else def += ` DEFAULT '${c.defaultVal}'`;
873
+ }
874
+ cols.push(def);
875
+ if(c.isPk) pks.push(`\`${c.name}\``);
876
+ });
877
+ if(pks.length > 0) cols.push(`PRIMARY KEY (${pks.join(',')})`);
878
+
879
+ sqlLines.push(`CREATE TABLE IF NOT EXISTS \`${impT.name}\` (\n ${cols.join(',\n ')}\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;`);
880
+ } else {
881
+ // COMPARE COLUMNS
882
+ impT.columns.forEach(impC => {
883
+ const liveC = liveT.columns.find(c => c.name === impC.name);
884
+ if (!liveC) {
885
+ // NEW COLUMN
886
+ summary += `[+] Yeni Kolon (${impT.name}): ${impC.name} (${impC.type})\n`;
887
+ let def = `\`${impC.name}\` ${impC.type}`;
888
+ if(!impC.nullable) def += ' NOT NULL';
889
+ if(impC.defaultVal !== null && impC.defaultVal !== undefined) {
890
+ if (impC.defaultVal.toUpperCase() === 'CURRENT_TIMESTAMP') def += ' DEFAULT CURRENT_TIMESTAMP';
891
+ else def += ` DEFAULT '${impC.defaultVal}'`;
892
+ }
893
+ sqlLines.push(`ALTER TABLE \`${impT.name}\` ADD COLUMN ${def};`);
894
+ }
895
+ });
896
+ }
897
+ });
898
+
899
+ if (sqlLines.length === 0) {
900
+ summary = "✅ Canlı veritabanınız içe aktarılan şema ile birebir uyumlu. Herhangi bir eksik bulunamadı.";
901
+ }
902
+
903
+ return { summary, sql: sqlLines.join('\n\n') };
904
+ }
905
+
906
+ function executeMigration() {
907
+ document.getElementById('sync-modal').style.display = 'none';
908
+ if (!pendingMigrationSql) {
909
+ showStatus('Uygulanacak bir SQL bulunamadı!', false);
910
+ return;
911
+ }
912
+
913
+ // Go to Query Tab and inject
914
+ openTab('query-builder', '⚡ Sorgu & Kod', 'query-builder', globalDbData);
915
+
916
+ // Wait for tab render
917
+ setTimeout(() => {
918
+ document.getElementById('vqb-action').value = 'UPDATE'; // Force red button mode conceptually
919
+ document.getElementById('gqb-code').style.display = 'block';
920
+ document.getElementById('gqb-code-text').innerText = `/* MIGRATION (ŞEMA GÖÇÜ) SCRIPT'I */\n\n${pendingMigrationSql}`;
921
+
922
+ currentMutatingSql = pendingMigrationSql;
923
+ document.getElementById('vqb-execute-btn-container').style.display = 'block';
924
+
925
+ document.getElementById('vqb-results-info').innerHTML = `<span style="color:#f39c12; font-weight:bold;">Migration Hazır:</span> İçe aktarılan şemayı kurmak için gereken SQL kodu yukarıdadır. Lütfen inceleyin ve hazırsanız yandaki kırmızı butona basın.`;
926
+ }, 200);
927
+ }
928
+
929
+ async function restartInOfflineMode() {
930
+ if(window.api && window.api.restartOffline) {
931
+ await window.api.restartOffline();
932
+ }
933
+ }
934
+
935
+ async function importArtiframe(event) {
936
+ const file = event.target.files[0];
937
+ if (!file) return;
938
+
939
+ const reader = new FileReader();
940
+ reader.onload = async function(e) {
941
+ try {
942
+ const data = JSON.parse(e.target.result);
943
+ if (data.metadata?.type !== "ArtiFrameDBDiagram") {
944
+ showStatus("Geçersiz dosya formatı! Sadece .artiframe dosyaları kabul edilir.", false);
945
+ return;
946
+ }
947
+
948
+ if (currentAppMode === 'offline') {
949
+ // Welcome ekranını kapat, Canvas'ı aç
950
+ document.getElementById('welcome-screen').style.display = 'none';
951
+ document.getElementById('tab-bar').style.display = 'flex';
952
+ document.getElementById('tab-content').style.display = 'flex';
953
+
954
+ tabs = []; // Sadece ER Diyagramı olacak
955
+ openTab('er-diagram', 'İçe Aktarılan Diyagram', 'er', data.schema);
956
+
957
+ globalDbData = data.schema;
958
+
959
+ const btnC = document.getElementById('scaffold-btn-container');
960
+ if (btnC) btnC.style.display = 'block';
961
+
962
+ showStatus("Diyagram çevrimdışı modda başarıyla yüklendi.", true);
963
+ } else {
964
+ // Live modda diff analizini aç
965
+ const diff = generateSchemaDiff(globalDbData, data.schema);
966
+ pendingMigrationSql = diff.sql;
967
+ document.getElementById('sync-diff-content').innerText = diff.summary;
968
+ document.getElementById('sync-modal').style.display = 'flex';
969
+ }
970
+
971
+ } catch (err) {
972
+ showStatus("Dosya okunurken hata oluştu: Bozuk JSON", false);
973
+ }
974
+ event.target.value = ''; // Reset input
975
+ };
976
+ reader.readAsText(file);
977
+ }
978
+
979
+
980
+
981
+ async function promptScaffoldProject(isFromWelcome = false) {
982
+ if (!isFromWelcome && (!globalDbData || !globalDbData.tables || globalDbData.tables.length === 0)) {
983
+ // If clicked from top bar but diagram is empty (shouldn't really happen, but safety first)
984
+ }
985
+
986
+ const msg = isFromWelcome ?
987
+ "Sıfırdan boş bir proje klasörü oluşturulacak. Klasör adı girin (Örn: my-app):" :
988
+ "Şu anki diyagramınızla kurulacak projenin klasör adını girin (Örn: my-app):";
989
+
990
+ const pName = await asyncPrompt(msg);
991
+ if (!pName || pName.trim() === '') return;
992
+
993
+ showStatus("Proje oluşturuluyor, lütfen bekleyin...", true);
994
+ if(window.api && window.api.scaffoldProject) {
995
+ const res = await window.api.scaffoldProject(pName.trim(), globalDbData);
996
+ if(res.success) {
997
+ showStatus("Proje başarıyla oluşturuldu! (" + res.path + ")", true);
998
+ customAlert('Proje başarıyla kuruldu! Dizin: ' + res.path + '\n\n1. Terminalden bu yeni dizine gidin.\n2. .env dosyasındaki veritabanı şifrenizi vb. ayarlayın.\n3. \'artiframe db:onproject\' komutunu çalıştırarak az önce ürettiğiniz veritabanı şemasını (schema.sql) canlıya alın!');
999
+ } else {
1000
+ showStatus("Hata: " + res.error, false);
1001
+ }
1002
+ }
1003
+ }
1004
+
1005
+ function formatBytes(bytes) {
1006
+ if(bytes === 0) return '0 B';
1007
+ const k = 1024, sizes = ['B', 'KB', 'MB', 'GB'];
1008
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1009
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
1010
+ }
1011
+
1012
+ function renderSidebar() {
1013
+ sidebarList.innerHTML = '';
1014
+
1015
+ // Sabit Araçlar (Tools)
1016
+ const toolsDiv = document.createElement('div');
1017
+ toolsDiv.style.marginBottom = '15px';
1018
+ toolsDiv.innerHTML = `
1019
+ <div style="font-size: 10px; color: #666; margin-bottom: 5px; padding-left: 15px; font-weight: bold; letter-spacing: 1px;">GENEL ARAÇLAR</div>
1020
+ <li class="table-list-item" style="border-left: 3px solid #f39c12; margin-bottom: 2px;" onclick="openTab('er-diagram', 'ER Diyagramı', 'er', globalDbData)">
1021
+ <div><span class="icon">📊</span>ER Diyagramı</div>
1022
+ </li>
1023
+ <li class="table-list-item" style="border-left: 3px solid #007acc; margin-bottom: 10px;" onclick="openTab('query-builder', '⚡ Sorgu & Kod', 'query-builder', globalDbData)">
1024
+ <div><span class="icon">⚡</span>Sorgu & Kod Üretici</div>
1025
+ </li>
1026
+ <div style="font-size: 10px; color: #666; margin-bottom: 5px; padding-left: 15px; font-weight: bold; letter-spacing: 1px; border-top: 1px solid #333; padding-top: 10px;">TABLOLAR</div>
1027
+ `;
1028
+ sidebarList.appendChild(toolsDiv);
1029
+
1030
+ currentTables.forEach(t => {
1031
+ const li = document.createElement('li'); li.className = 'table-list-item';
1032
+ li.onclick = () => openTab('table-' + t.name, t.name, 'table', t);
1033
+ li.innerHTML = `<div><span class="icon">📄</span>${t.name}</div><div style="font-size:10px; color:#666;">${t.stats ? formatBytes(t.stats.sizeBytes) : ''}</div>`;
1034
+ sidebarList.appendChild(li);
1035
+ });
1036
+ }
1037
+
1038
+ function openTab(id, title, type, data) {
1039
+ if (tabs.find(t => t.id === id)) { setActiveTab(id); return; }
1040
+ tabs.push({ id, title, type, data }); renderTabs();
1041
+ const pane = document.createElement('div'); pane.className = 'tab-pane'; pane.id = 'pane-' + id;
1042
+ tabContent.appendChild(pane);
1043
+ if (type === 'er') buildERPane(pane, data);
1044
+ else if (type === 'table') buildTablePane(pane, data);
1045
+ else if (type === 'sql') buildSqlPane(pane, data);
1046
+ else if (type === 'query-builder') buildGlobalQueryPane(pane, data);
1047
+ setActiveTab(id);
1048
+ }
1049
+
1050
+ function closeTab(id, e) {
1051
+ e.stopPropagation(); const index = tabs.findIndex(t => t.id === id);
1052
+ if(index > -1) {
1053
+ tabs.splice(index, 1); document.getElementById('pane-' + id).remove();
1054
+ if(activeTabId === id) { if(tabs.length > 0) setActiveTab(tabs[tabs.length-1].id); else activeTabId = null; }
1055
+ renderTabs();
1056
+ }
1057
+ }
1058
+
1059
+
1060
+ function applyAppModeUI() {
1061
+ if (currentAppMode === 'offline' || currentAppMode === 'preview') {
1062
+ document.getElementById('sidebar').style.display = 'none';
1063
+ document.getElementById('tab-bar').style.display = 'flex';
1064
+ document.getElementById('tab-content').style.display = 'flex';
1065
+
1066
+ if(document.getElementById('welcome-screen')) document.getElementById('welcome-screen').style.display = 'none';
1067
+
1068
+ if(!document.getElementById('scaffold-btn-container')) {
1069
+ const topBar = document.getElementById('tab-bar');
1070
+ const btnContainer = document.createElement('div');
1071
+ btnContainer.id = 'scaffold-btn-container';
1072
+ btnContainer.style.position = 'absolute';
1073
+ btnContainer.style.right = '10px';
1074
+ btnContainer.style.top = '10px';
1075
+ btnContainer.innerHTML = '<button class="btn" style="background:#2ecc71; font-weight:bold; padding:6px 12px; color:#111;" onclick="promptScaffoldProject()">🚀 Proje Oluştur</button>';
1076
+ topBar.appendChild(btnContainer);
1077
+ } else {
1078
+ document.getElementById('scaffold-btn-container').style.display = 'block';
1079
+ }
1080
+
1081
+ // Tabs array should only contain ER diagram
1082
+ tabs = tabs.filter(t => t.id === 'er-diagram');
1083
+ renderTabs();
1084
+ } else {
1085
+ document.getElementById('sidebar').style.display = 'flex';
1086
+ document.getElementById('tab-bar').style.display = 'flex';
1087
+ document.getElementById('tab-content').style.display = 'flex';
1088
+ if(document.getElementById('welcome-screen')) document.getElementById('welcome-screen').style.display = 'none';
1089
+
1090
+ const btnContainer = document.getElementById('scaffold-btn-container');
1091
+ if(btnContainer) btnContainer.style.display = 'none';
1092
+ }
1093
+ }
1094
+
1095
+ function renderTabs() {
1096
+ tabBar.innerHTML = '';
1097
+ tabs.forEach(t => {
1098
+ const tab = document.createElement('div'); tab.className = 'tab' + (t.id === activeTabId ? ' active' : '');
1099
+ tab.onclick = () => setActiveTab(t.id);
1100
+ tab.innerHTML = `${t.title} <span class="tab-close" onclick="closeTab('${t.id}', event)">&times;</span>`;
1101
+ tabBar.appendChild(tab);
1102
+ });
1103
+ }
1104
+
1105
+ function setActiveTab(id) {
1106
+ activeTabId = id; renderTabs();
1107
+ document.querySelectorAll('.tab-pane').forEach(p => p.classList.remove('active'));
1108
+ const activePane = document.getElementById('pane-' + id);
1109
+ if(activePane) activePane.classList.add('active');
1110
+
1111
+ if (id === 'er-diagram') {
1112
+ if (globalDbData) renderBoard(globalDbData);
1113
+ } else {
1114
+ lines.forEach(l => l.hide('none'));
1115
+ }
1116
+ }
1117
+
1118
+ let scale = 1, panning = false, pointX = 0, pointY = 0, startX = 0, startY = 0;
1119
+ function buildERPane(pane, json) {
1120
+ pane.innerHTML = `
1121
+ <div id="board-container"><div id="transform-container"></div></div>
1122
+ <button class="btn" style="position:absolute; top:15px; right:165px; z-index:100; background:#f39c12; color:#111; font-weight:bold;" onclick="saveLiveLayout()">💾 Dizilimi Kaydet</button>
1123
+ <button class="btn" style="position:absolute; top:15px; right:15px; z-index:100;" onclick="openTab('sql-console', 'SQL Konsolu', 'sql')">📝 Özel SQL Çalıştır</button>`;
1124
+ const board = pane.querySelector('#board-container'); const transform = pane.querySelector('#transform-container');
1125
+ board.onmousedown = function(e) {
1126
+ if (e.target.closest('.table-card') || e.target.closest('button')) return;
1127
+ e.preventDefault(); panning = true; board.style.cursor = 'grabbing';
1128
+ startX = e.clientX - pointX; startY = e.clientY - pointY;
1129
+ };
1130
+ board.onmousemove = function(e) { if (!panning) return; pointX = e.clientX - startX; pointY = e.clientY - startY; updateTransform(transform); };
1131
+ board.onmouseup = function() { panning = false; board.style.cursor = 'grab'; };
1132
+ board.onmouseleave = function() { panning = false; board.style.cursor = 'grab'; };
1133
+ board.onwheel = function(e) {
1134
+ e.preventDefault(); const xs = (e.clientX - pointX) / scale, ys = (e.clientY - pointY) / scale;
1135
+ const delta = e.deltaY || e.wheelDelta; scale *= (delta > 0) ? 0.9 : 1.1;
1136
+ pointX = e.clientX - xs * scale; pointY = e.clientY - ys * scale; updateTransform(transform);
1137
+ };
1138
+ renderBoard(json);
1139
+ }
1140
+
1141
+ function updateTransform(tc) { tc.style.transform = `translate(${pointX}px, ${pointY}px) scale(${scale})`; if (activeTabId === 'er-diagram') lines.forEach(l => l.position()); }
1142
+
1143
+ function renderBoard(json) {
1144
+ const tc = document.getElementById('transform-container'); if(!tc) return;
1145
+ tc.innerHTML = ''; lines.forEach(l => l.remove()); lines = [];
1146
+ if (!json || !json.tables || json.tables.length === 0) return;
1147
+ let maxX = 50, maxY = 50;
1148
+ json.tables.forEach(table => {
1149
+ if (table.ui && table.ui.left && table.ui.top) {
1150
+ const l = parseInt(table.ui.left) || 50;
1151
+ const t = parseInt(table.ui.top) || 50;
1152
+ if (l > maxX) maxX = l;
1153
+ if (t > maxY) maxY = t;
1154
+ }
1155
+ });
1156
+ let nextX = maxX > 50 ? maxX + 350 : 50;
1157
+ if (nextX > 1500) { nextX = 50; maxY += 300; }
1158
+ let y = maxX > 50 ? maxY : 50;
1159
+
1160
+ json.tables.forEach(table => {
1161
+ const card = document.createElement('div'); card.className = 'table-card'; card.dataset.tableName = table.name;
1162
+ if (table.ui && table.ui.left && table.ui.top) {
1163
+ card.style.left = table.ui.left; card.style.top = table.ui.top;
1164
+ } else {
1165
+ card.style.left = nextX + 'px'; card.style.top = y + 'px';
1166
+ nextX += 350; if (nextX > 1500) { nextX = 50; y += 300; }
1167
+ }
1168
+ let rowsHtml = '';
1169
+ (table.columns || []).forEach(col => {
1170
+ let badges = '';
1171
+ if (col.isPk) badges += `<span class="badge-pk">PK</span>`;
1172
+ if (col.isUk) badges += `<span class="badge-uk">UK</span>`;
1173
+ if (col.isAi) badges += `<span class="badge-ai">AI</span>`;
1174
+ const isFk = (json.relationships || []).some(r => r.fromTable === table.name && r.fromCol === col.name);
1175
+ if (isFk) badges += `<span class="badge-fk">FK</span>`;
1176
+ if (col.isNotNull) badges += `<span class="badge-nn">NN</span>`;
1177
+ rowsHtml += `<div class="table-row" id="col-${table.name}-${col.name}"><div>${badges}<span style="color:#9cdcfe; font-weight:bold;">${col.name}</span></div><span style="color:#4ec9b0; font-size:12px;">${col.type}</span></div>`;
1178
+ });
1179
+ const stats = table.stats || {rows:0, sizeBytes:0};
1180
+ card.innerHTML = `<div class="table-header"><span>${table.name} <span class="stats-badge">${stats.rows} Satır | ${formatBytes(stats.sizeBytes)}</span></span><span style="cursor:pointer;" onclick="openTab('table-${table.name}', '${table.name}', 'table', currentTables.find(t=>t.name==='${table.name}'))">↗️</span></div><div class="table-body">${rowsHtml}</div>`;
1181
+ tc.appendChild(card);
1182
+ const header = card.querySelector('.table-header'); let pos1=0, pos2=0, pos3=0, pos4=0;
1183
+ header.onmousedown = (e) => {
1184
+ if (e.target.tagName === 'SPAN' && e.target.innerText === '↗️') return;
1185
+ document.querySelectorAll('.table-card').forEach(c=>c.style.zIndex=10); card.style.zIndex=100;
1186
+ e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY;
1187
+ document.onmouseup = () => { document.onmouseup=null; document.onmousemove=null; };
1188
+ document.onmousemove = (ev) => {
1189
+ ev.preventDefault(); pos1 = pos3 - ev.clientX; pos2 = pos4 - ev.clientY; pos3 = ev.clientX; pos4 = ev.clientY;
1190
+ card.style.top = (card.offsetTop - (pos2/scale)) + "px"; card.style.left = (card.offsetLeft - (pos1/scale)) + "px";
1191
+ if (activeTabId === 'er-diagram') lines.forEach(l => l.position());
1192
+ };
1193
+ };
1194
+ });
1195
+ setTimeout(() => {
1196
+ (json.relationships || []).forEach(r => {
1197
+ const s = document.getElementById(`col-${r.fromTable}-${r.fromCol}`); const e = document.getElementById(`col-${r.toTable}-${r.toCol}`);
1198
+ if(s && e) lines.push(new LeaderLine(s, e, { color: '#007acc', size: 2, path: 'fluid', startSocket: 'right', endSocket: 'left' }));
1199
+ });
1200
+ if(activeTabId !== 'er-diagram') lines.forEach(l => l.hide('none'));
1201
+ }, 100);
1202
+ }
1203
+
1204
+ function buildTablePane(pane, t) {
1205
+ pane.innerHTML = `
1206
+ <div class="details-header">
1207
+ <div class="sub-tab active" onclick="switchSubTab(this, 'data-${t.name}')">🗄️ Veri (Data)</div>
1208
+ <div class="sub-tab" onclick="switchSubTab(this, 'struct-${t.name}')">🏗️ Yapı (Tasarımcı)</div>
1209
+
1210
+ </div>
1211
+
1212
+ <div class="sub-tab-content active" id="data-${t.name}">
1213
+ <div style="padding:15px; background:#252526; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #333;">
1214
+ <div>
1215
+ <button class="btn btn-seed" onclick="seedData('${t.name}')">🎲 50 Sahte Veri Doldur</button>
1216
+ <select id="data-limit-${t.name}" onchange="changeDataLimit('${t.name}')" style="margin-left: 10px; background: #3c3c3c; border: 1px solid #555; color: white; padding: 5px; border-radius:3px;">
1217
+ <option value="50">50 Kayıt</option>
1218
+ <option value="100">100 Kayıt</option>
1219
+ <option value="500">500 Kayıt</option>
1220
+ <option value="1000">1000 Kayıt</option>
1221
+ <option value="5000">5000 Kayıt</option>
1222
+ </select>
1223
+ </div>
1224
+ <div class="pagination-controls" style="display:flex; gap:10px; align-items:center;">
1225
+ <button class="btn" style="background:#444;" onclick="prevPage('${t.name}')">◀ Önceki</button>
1226
+ <span id="page-info-${t.name}" style="font-size: 13px; color:#ccc;">Sayfa 1</span>
1227
+ <button class="btn" style="background:#444;" onclick="nextPage('${t.name}')">Sonraki ▶</button>
1228
+ </div>
1229
+ </div>
1230
+ <div style="flex:1; overflow:auto;"><table class="data-table"><thead id="thead-${t.name}"></thead><tbody id="tbody-${t.name}"></tbody></table></div>
1231
+ </div>
1232
+
1233
+ <div class="sub-tab-content" id="struct-${t.name}">
1234
+ <div style="padding:15px; background:#252526; display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid #333; position:sticky; top:0; z-index:100;">
1235
+ <div>
1236
+ <button class="btn" onclick="addColumnRow('${t.name}')">+ Kolon Ekle</button>
1237
+ <button class="btn" style="background:#569cd6; margin-left:10px;" onclick="addIndexRow('${t.name}')">+ İndeks Ekle</button>
1238
+ <button class="btn" style="background:#ce9178; color:#1e1e1e;" onclick="addFkRow('${t.name}')">+ Yabancı Anahtar Ekle</button>
1239
+ </div>
1240
+ <button class="btn" style="background:#1b4b24;" onclick="executeAlter('${t.name}')">💾 Değişiklikleri Veritabanına Yaz</button>
1241
+ </div>
1242
+ <div style="flex:1; overflow:auto; padding: 15px;">
1243
+ <h4 style="margin-top:0; border-bottom:1px solid #444; padding-bottom:5px; color:#d7ba7d;">Kolonlar (Columns)</h4>
1244
+ <div id="struct-list-${t.name}" style="margin-bottom: 30px;"></div>
1245
+
1246
+ <h4 style="margin-top:0; border-bottom:1px solid #444; padding-bottom:5px; color:#569cd6;">İndeksler (Indexes)</h4>
1247
+ <div id="idx-list-${t.name}" style="margin-bottom: 30px;"></div>
1248
+
1249
+ <h4 style="margin-top:0; border-bottom:1px solid #444; padding-bottom:5px; color:#ce9178;">Yabancı Anahtarlar (Foreign Keys)</h4>
1250
+ <div id="fk-list-${t.name}" style="margin-bottom: 30px;"></div>
1251
+ </div>
1252
+ </div>
1253
+
1254
+ `;
1255
+ loadTableData(t.name); populateStructure(t);
1256
+ }
1257
+
1258
+ function switchSubTab(el, targetId) {
1259
+ const parent = el.parentElement; parent.querySelectorAll('.sub-tab').forEach(e => e.classList.remove('active')); el.classList.add('active');
1260
+ const pane = parent.parentElement; pane.querySelectorAll('.sub-tab-content').forEach(e => e.classList.remove('active')); pane.querySelector('#' + targetId).classList.add('active');
1261
+ }
1262
+
1263
+ function buildSqlPane(pane, data) {
1264
+ pane.innerHTML = `
1265
+ <div style="display:flex; flex-direction:column; height:100%; width:100%; overflow:hidden;">
1266
+ <div style="padding:10px; background:#252526; border-bottom: 1px solid #333; display:flex; justify-content:space-between; align-items:center; flex-shrink:0;">
1267
+ <span style="color:#d4d4d4; font-weight:bold;">SQL Konsolu</span>
1268
+ <button class="btn" style="background:#007acc;" onclick="runConsoleSql()">▶️ Sorguyu Çalıştır (F5)</button>
1269
+ </div>
1270
+ <div style="flex:1; display:flex; flex-direction:column; min-height:0;">
1271
+ <textarea id="sql-console-input" style="height:200px; min-height:100px; flex-shrink:0; background:#1e1e1e; color:#d4d4d4; border:none; border-bottom:1px solid #333; padding:15px; font-family:monospace; font-size:14px; resize:vertical; outline:none;" placeholder="Örn: SELECT * FROM users;"></textarea>
1272
+ <div id="sql-console-results" style="flex:1; overflow:auto; background:#252526; padding:0; min-height:0; position:relative;">
1273
+ <div style="padding:15px;"><span style="color:#666;">Sonuçlar burada görünecek...</span></div>
1274
+ </div>
1275
+ </div>
1276
+ </div>`;
1277
+
1278
+ // Add F5 listener
1279
+ const input = pane.querySelector('#sql-console-input');
1280
+ input.addEventListener('keydown', (e) => {
1281
+ if (e.key === 'F5') {
1282
+ e.preventDefault();
1283
+ runConsoleSql();
1284
+ }
1285
+ });
1286
+ setTimeout(() => input.focus(), 100);
1287
+ }
1288
+
1289
+ async function runConsoleSql() {
1290
+ const input = document.getElementById('sql-console-input');
1291
+ const resultsDiv = document.getElementById('sql-console-results');
1292
+ if(!input || !resultsDiv) return;
1293
+
1294
+ const sql = input.value.trim();
1295
+ if(!sql) return;
1296
+
1297
+ resultsDiv.innerHTML = '<div style="padding:15px;"><span style="color:#007acc;">Sorgu çalıştırılıyor...</span></div>';
1298
+ const res = await window.api.executeSql(sql);
1299
+
1300
+ if(res.success) {
1301
+ showStatus('Sorgu başarıyla çalıştırıldı.', true);
1302
+ loadSchema();
1303
+
1304
+ if (res.data && Array.isArray(res.data)) {
1305
+ if (res.data.length === 0) {
1306
+ resultsDiv.innerHTML = '<div style="padding:15px;"><span style="color:#999;">Sorgu başarılı, ancak 0 kayıt döndü.</span></div>';
1307
+ } else {
1308
+ // Build result table
1309
+ const cols = Object.keys(res.data[0]);
1310
+ let html = '<table class="data-table" style="width:auto; min-width:100%;"><thead><tr>';
1311
+ cols.forEach(c => html += `<th>${c}</th>`);
1312
+ html += '</tr></thead><tbody>';
1313
+ res.data.forEach(row => {
1314
+ html += '<tr>';
1315
+ cols.forEach(c => html += `<td>${row[c] !== null ? row[c] : '<i style="color:#666">NULL</i>'}</td>`);
1316
+ html += '</tr>';
1317
+ });
1318
+ html += '</tbody></table>';
1319
+ resultsDiv.innerHTML = html;
1320
+ }
1321
+ } else {
1322
+ resultsDiv.innerHTML = '<div style="padding:15px;"><span style="color:#2ecc71;">İşlem başarılı (Kayıt döndürmeyen sorgu).</span></div>';
1323
+ }
1324
+ } else {
1325
+ showStatus('Hata: ' + res.error, false);
1326
+ resultsDiv.innerHTML = `<div style="padding:15px;"><div style="color:#e74c3c; padding:10px; background:rgba(231,76,60,0.1); border:1px solid #e74c3c; border-radius:4px;">${res.error}</div></div>`;
1327
+ }
1328
+ }
1329
+
1330
+
1331
+ function changeDataLimit(tableName) {
1332
+ const val = parseInt(document.getElementById('data-limit-'+tableName).value);
1333
+ if(!pageState[tableName]) pageState[tableName] = { limit: 50, page: 1, total: 0 };
1334
+ pageState[tableName].limit = val;
1335
+ pageState[tableName].page = 1;
1336
+ loadTableData(tableName);
1337
+ }
1338
+ function prevPage(tableName) {
1339
+ if (!pageState[tableName] || pageState[tableName].page <= 1) return;
1340
+ pageState[tableName].page--;
1341
+ loadTableData(tableName);
1342
+ }
1343
+ function nextPage(tableName) {
1344
+ if (!pageState[tableName]) return;
1345
+ const state = pageState[tableName];
1346
+ const totalPages = Math.ceil(state.total / state.limit) || 1;
1347
+ if (state.page < totalPages) {
1348
+ state.page++;
1349
+ loadTableData(tableName);
1350
+ }
1351
+ }
1352
+ async function loadTableData(tableName) {
1353
+ if (!pageState[tableName]) pageState[tableName] = { limit: 50, page: 1, total: 0 };
1354
+ const state = pageState[tableName];
1355
+ const offset = (state.page - 1) * state.limit;
1356
+
1357
+ const t = currentTables.find(x => x.name === tableName);
1358
+ const res = await window.api.getTableData(tableName, state.limit, offset);
1359
+ if(!res.success) return;
1360
+
1361
+ state.total = res.total;
1362
+ const totalPages = Math.ceil(state.total / state.limit) || 1;
1363
+ const pageInfo = document.getElementById('page-info-'+tableName);
1364
+ if (pageInfo) pageInfo.innerText = `Sayfa ${state.page} / ${totalPages} (Toplam: ${state.total})`;
1365
+
1366
+ let headHtml = '<tr>'; t.columns.forEach(c => headHtml += `<th>${c.name}${c.isPk?' 🔑':''}</th>`); headHtml += '<th>İşlem</th></tr>';
1367
+ document.getElementById('thead-'+tableName).innerHTML = headHtml;
1368
+ let bodyHtml = ''; const pkCol = t.columns.find(c => c.isPk);
1369
+ res.data.forEach(row => {
1370
+ let tr = '<tr>';
1371
+ t.columns.forEach(c => { const editable = (c.isPk && c.isAi) ? 'false' : 'true'; const val = row[c.name] !== null ? row[c.name] : ''; tr += `<td contenteditable="${editable}" data-col="${c.name}">${val}</td>`; });
1372
+ const pkVal = pkCol ? row[pkCol.name] : '';
1373
+ tr += `<td><button class="btn" onclick="saveRow(this, '${tableName}', '${pkCol?.name}', '${pkVal}')">💾</button> <button class="btn btn-danger" onclick="deleteRow(this, '${tableName}', '${pkCol?.name}', '${pkVal}')">🗑️</button></td></tr>`;
1374
+ bodyHtml += tr;
1375
+ });
1376
+ document.getElementById('tbody-'+tableName).innerHTML = bodyHtml;
1377
+ }
1378
+ async function saveRow(btn, tableName, pkCol, pkVal) {
1379
+ if(!pkVal) return; const tr = btn.closest('tr'); const updateData = {};
1380
+ tr.querySelectorAll('td[contenteditable=true]').forEach(td => updateData[td.getAttribute('data-col')] = td.innerText);
1381
+ btn.innerText = '⏳'; const res = await window.api.updateRow(tableName, pkCol, pkVal, updateData); btn.innerText = '💾';
1382
+ if(res.success) showStatus('Kaydedildi', true); else showStatus(res.error, false);
1383
+ }
1384
+ async function deleteRow(btn, tableName, pkCol, pkVal) {
1385
+ if(!pkVal || !(await asyncConfirm('Silmek istediğinize emin misiniz?'))) return;
1386
+ const res = await window.api.deleteRow(tableName, pkCol, pkVal);
1387
+ if(res.success) { btn.closest('tr').remove(); showStatus('Silindi', true); }
1388
+ }
1389
+ async function seedData(tableName) {
1390
+ const t = currentTables.find(x => x.name === tableName);
1391
+ const res = await window.api.seedTable(tableName, t.columns, 50);
1392
+ if(res.success) { showStatus(res.message, true); loadTableData(tableName); loadSchema(); } else showStatus(res.error, false);
1393
+ }
1394
+
1395
+ // --- Structure Builder (Columns, Indexes, FKs) ---
1396
+ function populateStructure(t) {
1397
+ const list = document.getElementById('struct-list-'+t.name); list.innerHTML = '';
1398
+ t.columns.forEach(c => { list.appendChild(createStructRow(c.name, c.type, c.isPk, c.isAi, c.isNotNull, false)); });
1399
+
1400
+ const idxList = document.getElementById('idx-list-'+t.name); idxList.innerHTML = '';
1401
+ if (t.indexes) t.indexes.forEach(i => { idxList.appendChild(createIndexRow(t.name, i.name, i.col, i.isUnique, false)); });
1402
+
1403
+ const fkList = document.getElementById('fk-list-'+t.name); fkList.innerHTML = '';
1404
+ if (t.fks) t.fks.forEach(f => { fkList.appendChild(createFkRow(t.name, f.name, f.localCol, f.refTable, f.refCol, f.onDelete, f.onUpdate, false)); });
1405
+ }
1406
+
1407
+ function addColumnRow(tableName) { document.getElementById('struct-list-'+tableName).appendChild(createStructRow('', 'VARCHAR(255)', false, false, false, true)); }
1408
+ function addIndexRow(tableName) { document.getElementById('idx-list-'+tableName).appendChild(createIndexRow(tableName, '', '', false, true)); }
1409
+ function addFkRow(tableName) { document.getElementById('fk-list-'+tableName).appendChild(createFkRow(tableName, '', '', '', '', 'RESTRICT', 'RESTRICT', true)); }
1410
+
1411
+ function createStructRow(name, type, isPk, isAi, isNotNull, isNew) {
1412
+ const div = document.createElement('div'); div.className = 'schema-row col-row';
1413
+ let baseType = type.toUpperCase(); let length = '';
1414
+ const match = type.match(/^([A-Z]+)\(([^)]+)\)/i);
1415
+ if (match) { baseType = match[1]; length = match[2]; }
1416
+ if (baseType === 'TINYINT' && length === '1') baseType = 'BOOLEAN';
1417
+
1418
+ div.innerHTML = `
1419
+ <input type="text" class="col-name" value="${name}" placeholder="Kolon Adı" style="width: 140px;" ${isNew ? '' : 'readonly title="Mevcut kolon değiştirilemez"'}>
1420
+ <select class="col-type" style="width: 130px;" onchange="updateTypeHint(this)">
1421
+ <optgroup label="Sayısal">
1422
+ <option value="INT" ${baseType==='INT'?'selected':''}>INT</option>
1423
+ <option value="BIGINT" ${baseType==='BIGINT'?'selected':''}>BIGINT</option>
1424
+ <option value="TINYINT" ${baseType==='TINYINT'?'selected':''}>TINYINT</option>
1425
+ <option value="DECIMAL" ${baseType==='DECIMAL'?'selected':''}>DECIMAL</option>
1426
+ </optgroup>
1427
+ <optgroup label="Metin">
1428
+ <option value="VARCHAR" ${baseType==='VARCHAR'?'selected':''}>VARCHAR</option>
1429
+ <option value="TEXT" ${baseType==='TEXT'?'selected':''}>TEXT</option>
1430
+ <option value="LONGTEXT" ${baseType==='LONGTEXT'?'selected':''}>LONGTEXT</option>
1431
+ </optgroup>
1432
+ <optgroup label="Tarih & Zaman">
1433
+ <option value="DATETIME" ${baseType==='DATETIME'?'selected':''}>DATETIME</option>
1434
+ <option value="DATE" ${baseType==='DATE'?'selected':''}>DATE</option>
1435
+ <option value="TIMESTAMP" ${baseType==='TIMESTAMP'?'selected':''}>TIMESTAMP</option>
1436
+ </optgroup>
1437
+ <optgroup label="Diğer">
1438
+ <option value="BOOLEAN" ${baseType==='BOOLEAN'?'selected':''}>BOOLEAN</option>
1439
+ <option value="JSON" ${baseType==='JSON'?'selected':''}>JSON</option>
1440
+ <option value="BINARY" ${baseType==='BINARY'?'selected':''}>BINARY</option>
1441
+ <option value="VARBINARY" ${baseType==='VARBINARY'?'selected':''}>VARBINARY</option>
1442
+ </optgroup>
1443
+ </select>
1444
+ <input type="text" class="col-len" value="${length}" placeholder="Uzunluk" style="width: 60px;">
1445
+ <div class="tooltip-container"><div class="tooltip-icon">?</div><div class="tooltip-content"></div></div>
1446
+ <label style="font-size:12px; margin-left:10px;"><input type="checkbox" class="cb-pk" ${isPk?'checked':''}> Primary Key</label>
1447
+ <label style="font-size:12px;"><input type="checkbox" class="cb-ai" ${isAi?'checked':''}> Auto Inc</label>
1448
+ <label style="font-size:12px;"><input type="checkbox" class="cb-nn" ${isNotNull?'checked':''}> Not Null</label>
1449
+ ${isNew ? `<button class="btn btn-danger" onclick="this.parentElement.remove()">İptal</button>` : `<button class="btn btn-danger" onclick="markDrop(this)">Sil (Drop)</button>`}
1450
+ `;
1451
+ div.dataset.oldName = name; div.dataset.isNew = isNew;
1452
+ setTimeout(() => updateTypeHint(div.querySelector('.col-type')), 10);
1453
+ return div;
1454
+ }
1455
+
1456
+
1457
+ function buildIdxColSelect(tableName, selectedVal, isNew, isFirst = false) {
1458
+ const t = currentTables.find(x => x.name === tableName);
1459
+ let h = `<div style="display:inline-flex; align-items:center; background:#3c3c3c; border-radius:3px; border:1px solid #555; padding-right:${!isFirst && isNew ? '5px' : '0'}">`;
1460
+ h += `<select class="idx-col-select" style="width: 90px; padding: 3px; border:none; background:transparent; color:#ccc; outline:none;" ${isNew ? 'onchange="updateIdxOptions(this.closest(\'.idx-row\'))"' : 'disabled'}>`;
1461
+ h += `<option value="" style="background:#3c3c3c;">- Seç -</option>`;
1462
+ t.columns.forEach(c => {
1463
+ h += `<option value="${c.name}" style="background:#3c3c3c;" ${selectedVal === c.name ? 'selected' : ''}>${c.name}</option>`;
1464
+ });
1465
+ h += `</select>`;
1466
+ if (!isFirst && isNew) {
1467
+ h += `<span style="cursor:pointer; color:#f48771; font-weight:bold; font-size:14px; margin-left:3px; line-height:1;" title="Kolonu Çıkar" onclick="this.parentElement.remove(); updateIdxOptions(this.closest(\'.idx-row\'))">&times;</span>`;
1468
+ }
1469
+ h += `</div>`;
1470
+ return h;
1471
+ }
1472
+
1473
+ function updateIdxOptions(row) {
1474
+ const selects = Array.from(row.querySelectorAll('.idx-col-select'));
1475
+ const selectedValues = selects.map(s => s.value).filter(v => v);
1476
+ selects.forEach(sel => {
1477
+ const currentVal = sel.value;
1478
+ Array.from(sel.options).forEach(opt => {
1479
+ if (!opt.value) return;
1480
+ if (selectedValues.includes(opt.value) && opt.value !== currentVal) {
1481
+ opt.disabled = true;
1482
+ } else {
1483
+ opt.disabled = false;
1484
+ }
1485
+ });
1486
+ });
1487
+ }
1488
+ function createIndexRow(tableName, name, col, isUnique, isNew) {
1489
+ const t = currentTables.find(x => x.name === tableName);
1490
+ const div = document.createElement('div'); div.className = 'schema-row idx-row';
1491
+
1492
+ let colsWrapperHtml = `<div class="idx-cols-wrapper" style="display:flex; gap:5px; flex-wrap:wrap; align-items:center;">`;
1493
+ const selectedCols = col ? col.split(',').map(s=>s.trim()) : [''];
1494
+ selectedCols.forEach((sc, idx) => {
1495
+ colsWrapperHtml += buildIdxColSelect(tableName, sc, isNew, idx === 0);
1496
+ });
1497
+ colsWrapperHtml += `</div>`;
1498
+
1499
+ const addColBtn = isNew ? `<button class="btn btn-idx-add-col" style="padding:2px 6px; background:#444;" title="Composite (Bileşik) İndeks için yeni kolon ekle">+</button>` : '';
1500
+
1501
+ div.innerHTML = `
1502
+ <input type="text" class="idx-name" value="${name}" placeholder="İndeks Adı" style="width: 130px;" ${isNew ? '' : 'readonly'}>
1503
+ <select class="idx-type" style="width: 120px;" ${isNew ? '' : 'disabled'}>
1504
+ <option value="INDEX" ${!isUnique?'selected':''}>Normal (INDEX)</option>
1505
+ <option value="UNIQUE" ${isUnique?'selected':''}>Benzersiz (UNIQUE)</option>
1506
+ </select>
1507
+ ${colsWrapperHtml}
1508
+ ${addColBtn}
1509
+ ${isNew ? `<button class="btn btn-danger" style="margin-left:auto;" onclick="this.parentElement.remove()">İptal</button>` : `<button class="btn btn-danger" style="margin-left:auto;" onclick="markDrop(this)">Sil (Drop)</button>`}
1510
+ `;
1511
+ div.dataset.oldName = name; div.dataset.isNew = isNew;
1512
+
1513
+ if (isNew) {
1514
+ setTimeout(() => updateIdxOptions(div), 10);
1515
+ div.querySelector('.btn-idx-add-col').onclick = () => {
1516
+ const wrapper = div.querySelector('.idx-cols-wrapper');
1517
+ if (wrapper.children.length < t.columns.length) {
1518
+ wrapper.insertAdjacentHTML('beforeend', buildIdxColSelect(tableName, '', true, false));
1519
+ updateIdxOptions(div);
1520
+ } else {
1521
+ showStatus('Tablodaki toplam kolon sayısından fazla indeks ekleyemezsiniz!', false);
1522
+ }
1523
+ };
1524
+ }
1525
+ return div;
1526
+ }
1527
+
1528
+ function createFkRow(tableName, name, localCol, refTable, refCol, onDelete, onUpdate, isNew) {
1529
+ const t = currentTables.find(x => x.name === tableName);
1530
+ const div = document.createElement('div'); div.className = 'schema-row fk-row';
1531
+ const colsHtml = t.columns.map(c => `<option value="${c.name}" ${localCol===c.name?'selected':''}>${c.name}</option>`).join('');
1532
+
1533
+ let tablesHtml = '<option value="">-- Tablo --</option>';
1534
+ currentTables.forEach(xt => { tablesHtml += `<option value="${xt.name}" ${refTable===xt.name?'selected':''}>${xt.name}</option>`; });
1535
+
1536
+ div.innerHTML = `
1537
+ <input type="text" class="fk-name" value="${name}" placeholder="fk_adi" style="width: 130px;" ${isNew ? '' : 'readonly'}>
1538
+ <select class="fk-local" style="width: 120px;" ${isNew ? '' : 'disabled'}>
1539
+ <option value="">-- Kendi Kolonu --</option>${colsHtml}
1540
+ </select>
1541
+ <span style="color:#666; font-size:12px;">bağlanır ➔</span>
1542
+ <select class="fk-ref-tbl" onchange="updateFkRefCols(this)" style="width: 120px;" ${isNew ? '' : 'disabled'}>
1543
+ ${tablesHtml}
1544
+ </select>
1545
+ <select class="fk-ref-col" style="width: 120px;" ${isNew ? '' : 'disabled'}>
1546
+ <option value="${refCol}">${refCol || '-- Hedef Kolon --'}</option>
1547
+ </select>
1548
+ <div style="display:flex; flex-direction:column; gap:4px; margin-left:10px; font-size:11px;">
1549
+ <label>ON DELETE: <select class="fk-del" style="background:#333; padding:2px; border:none;" ${isNew ? '' : 'disabled'}><option ${onDelete==='RESTRICT'?'selected':''}>RESTRICT</option><option ${onDelete==='CASCADE'?'selected':''}>CASCADE</option><option ${onDelete==='SET NULL'?'selected':''}>SET NULL</option><option ${onDelete==='NO ACTION'?'selected':''}>NO ACTION</option></select></label>
1550
+ <label>ON UPDATE: <select class="fk-upd" style="background:#333; padding:2px; border:none;" ${isNew ? '' : 'disabled'}><option ${onUpdate==='RESTRICT'?'selected':''}>RESTRICT</option><option ${onUpdate==='CASCADE'?'selected':''}>CASCADE</option><option ${onUpdate==='SET NULL'?'selected':''}>SET NULL</option><option ${onUpdate==='NO ACTION'?'selected':''}>NO ACTION</option></select></label>
1551
+ </div>
1552
+ ${isNew ? `<button class="btn btn-danger" style="margin-left:auto;" onclick="this.parentElement.remove()">İptal</button>` : `<button class="btn btn-danger" style="margin-left:auto;" onclick="markDrop(this)">Sil (Drop)</button>`}
1553
+ `;
1554
+ div.dataset.oldName = name; div.dataset.isNew = isNew;
1555
+ if (isNew) setTimeout(() => updateFkRefCols(div.querySelector('.fk-ref-tbl')), 10);
1556
+ return div;
1557
+ }
1558
+
1559
+ function updateFkRefCols(selectEl) {
1560
+ const tblName = selectEl.value;
1561
+ const targetSelect = selectEl.parentElement.querySelector('.fk-ref-col');
1562
+ targetSelect.innerHTML = '<option value="">-- Hedef Kolon --</option>';
1563
+ if (!tblName) return;
1564
+ const t = currentTables.find(x => x.name === tblName);
1565
+ if (t) {
1566
+ t.columns.forEach(c => {
1567
+ const opt = document.createElement('option'); opt.value = c.name; opt.innerText = c.name;
1568
+ targetSelect.appendChild(opt);
1569
+ });
1570
+ }
1571
+ }
1572
+
1573
+ async function markDrop(btn) {
1574
+ if (!(await asyncConfirm('Bunu silmek üzere işaretliyorsunuz. Değişiklikleri kaydettiğinizde veritabanından kalıcı olarak silinecektir. Onaylıyor musunuz?'))) return;
1575
+ const row = btn.parentElement;
1576
+ row.style.display = 'none';
1577
+ row.dataset.drop = 'true';
1578
+ }
1579
+
1580
+ async function executeAlter(tableName) {
1581
+ let alterLines = [];
1582
+
1583
+ // 1. Columns
1584
+ document.getElementById('struct-list-'+tableName).querySelectorAll('.col-row').forEach(r => {
1585
+ const isNew = r.dataset.isNew === 'true'; const isDrop = r.dataset.drop === 'true';
1586
+ if (isDrop) { alterLines.push(`DROP COLUMN \`${r.dataset.oldName}\``); return; }
1587
+ const name = r.querySelector('.col-name').value.trim(); const baseType = r.querySelector('.col-type').value;
1588
+ let length = r.querySelector('.col-len').value.trim();
1589
+ const isPk = r.querySelector('.cb-pk').checked; const isAi = r.querySelector('.cb-ai').checked; const isNotNull = r.querySelector('.cb-nn').checked;
1590
+
1591
+ let finalType = baseType;
1592
+ if (length && !r.querySelector('.col-len').disabled) { finalType = `${baseType}(${length})`; }
1593
+ else if (baseType === 'VARCHAR' && !length) finalType = 'VARCHAR(255)';
1594
+ else if (baseType === 'BOOLEAN') finalType = 'TINYINT(1)';
1595
+
1596
+ if (isNew && name) {
1597
+ let def = `ADD COLUMN \`${name}\` ${finalType}`;
1598
+ if(isNotNull) def += ' NOT NULL';
1599
+ if(isPk) def += ' PRIMARY KEY';
1600
+ if(isAi) def += ' AUTO_INCREMENT';
1601
+ alterLines.push(def);
1602
+ }
1603
+ });
1604
+
1605
+ // 2. FKs Drop
1606
+ document.getElementById('fk-list-'+tableName).querySelectorAll('.fk-row').forEach(r => {
1607
+ const isDrop = r.dataset.drop === 'true';
1608
+ if (isDrop) { alterLines.push(`DROP FOREIGN KEY \`${r.dataset.oldName}\``); }
1609
+ });
1610
+
1611
+ // 3. Indexes Drop
1612
+ document.getElementById('idx-list-'+tableName).querySelectorAll('.idx-row').forEach(r => {
1613
+ const isDrop = r.dataset.drop === 'true';
1614
+ if (isDrop) { alterLines.push(`DROP INDEX \`${r.dataset.oldName}\``); }
1615
+ });
1616
+
1617
+ // 4. Indexes Add
1618
+ document.getElementById('idx-list-'+tableName).querySelectorAll('.idx-row').forEach(r => {
1619
+ const isNew = r.dataset.isNew === 'true'; const isDrop = r.dataset.drop === 'true';
1620
+ if (isNew && !isDrop) {
1621
+ let name = r.querySelector('.idx-name').value.trim();
1622
+ const type = r.querySelector('.idx-type').value;
1623
+ const selects = Array.from(r.querySelectorAll('.idx-col-select'));
1624
+ const colsList = selects.map(s => s.value).filter(v => v);
1625
+ if (colsList.length === 0) return;
1626
+ let col = colsList.join(',');
1627
+ if (!name && col) name = `idx_${col.replace(/[^a-zA-Z0-9_]/g, '')}`;
1628
+ if (name && col) {
1629
+ const kw = type === 'UNIQUE' ? 'UNIQUE INDEX' : 'INDEX';
1630
+ const colsMapped = col.split(',').map(c => `\`${c.trim()}\``).join(', ');
1631
+ alterLines.push(`ADD ${kw} \`${name}\` (${colsMapped})`);
1632
+ }
1633
+ }
1634
+ });
1635
+
1636
+ // 5. FKs Add
1637
+ document.getElementById('fk-list-'+tableName).querySelectorAll('.fk-row').forEach(r => {
1638
+ const isNew = r.dataset.isNew === 'true'; const isDrop = r.dataset.drop === 'true';
1639
+ if (isNew && !isDrop) {
1640
+ let name = r.querySelector('.fk-name').value.trim();
1641
+ const loc = r.querySelector('.fk-local').value;
1642
+ const refTbl = r.querySelector('.fk-ref-tbl').value;
1643
+ const refCol = r.querySelector('.fk-ref-col').value;
1644
+ const del = r.querySelector('.fk-del').value;
1645
+ const upd = r.querySelector('.fk-upd').value;
1646
+ if (!name && loc && refTbl) name = `fk_${tableName}_${loc}`;
1647
+ if (name && loc && refTbl && refCol) {
1648
+ alterLines.push(`ADD CONSTRAINT \`${name}\` FOREIGN KEY (\`${loc}\`) REFERENCES \`${refTbl}\` (\`${refCol}\`) ON DELETE ${del} ON UPDATE ${upd}`);
1649
+ }
1650
+ }
1651
+ });
1652
+
1653
+ if(alterLines.length === 0) { showStatus('Değişiklik yok', false); return; }
1654
+ const sql = `ALTER TABLE \`${tableName}\` \n` + alterLines.join(',\n') + ';';
1655
+ const res = await window.api.executeSql(sql);
1656
+ if(res.success) {
1657
+ showStatus('Tablo güncellendi!', true);
1658
+ await loadSchema();
1659
+ const tUpd = currentTables.find(x => x.name === tableName);
1660
+ if(tUpd) {
1661
+ populateStructure(tUpd);
1662
+ loadTableData(tableName);
1663
+ }
1664
+ } else {
1665
+ showStatus('Hata: ' + res.error, false);
1666
+ }
1667
+ }
1668
+
1669
+
1670
+
1671
+
1672
+ async function saveLiveLayout() {
1673
+ if (currentAppMode !== 'live') {
1674
+ showStatus("Önizleme modunda dizilim 'Dışa Aktar' işlemi sırasında otomatik kaydedilir.", false);
1675
+ return;
1676
+ }
1677
+ const layoutData = {};
1678
+ document.querySelectorAll('.table-card').forEach(card => {
1679
+ const tableName = card.dataset.tableName;
1680
+ if (tableName) {
1681
+ layoutData[tableName] = { left: card.style.left, top: card.style.top };
1682
+ }
1683
+ });
1684
+ const res = await window.api.saveLayout(layoutData);
1685
+ if (res && res.success) {
1686
+ showStatus("Dizilim projeye (.artiframe_layout.json) başarıyla kaydedildi!", true);
1687
+ } else {
1688
+ showStatus("Dizilim kaydedilemedi: " + (res?res.error:'Bilinmeyen hata'), false);
1689
+ }
1690
+ }
1691
+
1692
+ function showStatus(msg, isSuccess) {
1693
+ statusDiv.style.display = 'block'; statusDiv.className = isSuccess ? 'success' : 'error';
1694
+ statusDiv.innerText = msg; setTimeout(() => statusDiv.style.display = 'none', 5000);
1695
+ }
1696
+
1697
+ async function initApp() {
1698
+ if (window.api && window.api.getAppMode) {
1699
+ const initData = await window.api.getAppMode();
1700
+ if (initData) {
1701
+ currentAppMode = initData.mode;
1702
+ currentProjectRoot = initData.projectRoot;
1703
+ if (typeof applyAppModeUI === 'function') {
1704
+ applyAppModeUI();
1705
+ }
1706
+ }
1707
+ }
1708
+ await loadSchema();
1709
+ }
1710
+ initApp();
1711
+ </script>
1712
+
1713
+
1714
+ <div id="index-confirm-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); z-index:9999; justify-content:center; align-items:center;">
1715
+ <div style="background:#252526; padding:20px; border-radius:8px; border:1px solid #444; width:350px; text-align:center;">
1716
+ <h3 style="margin-top:0; color:#e74c3c; font-size:16px;">Onay Gerekli</h3>
1717
+ <p id="index-confirm-desc" style="font-size:13px; color:#ccc; margin-bottom:20px; white-space:pre-wrap;">Emin misiniz?</p>
1718
+ <div style="display:flex; gap:10px; justify-content:center;">
1719
+ <button class="btn" style="background:#555; flex:1;" onclick="document.getElementById('index-confirm-modal').style.display='none'">İptal</button>
1720
+ <button class="btn btn-danger" style="flex:1;" id="index-confirm-ok">Evet, Onaylıyorum</button>
1721
+ </div>
1722
+ </div>
1723
+ </div>
1724
+ <div id="index-prompt-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); z-index:9999; justify-content:center; align-items:center;">
1725
+ <div style="background:#252526; padding:20px; border-radius:8px; border:1px solid #444; width:300px; text-align:center;">
1726
+ <h3 id="index-prompt-title" style="margin-top:0; color:#d4d4d4; font-size:16px;">Başlık</h3>
1727
+ <p id="index-prompt-desc" style="font-size:12px; color:#888; margin-bottom:15px;">Açıklama</p>
1728
+ <input type="text" id="index-prompt-input" style="width:100%; padding:8px; background:#1e1e1e; border:1px solid #555; color:white; margin-bottom:15px; box-sizing:border-box; border-radius:4px;">
1729
+ <div style="display:flex; gap:10px; justify-content:center;">
1730
+ <button class="btn btn-danger" style="flex:1;" onclick="document.getElementById('index-prompt-modal').style.display='none'">İptal</button>
1731
+ <button class="btn" style="background:#007acc; flex:1;" id="index-prompt-ok">Tamam</button>
1732
+ </div>
1733
+ </div>
1734
+ </div>
1735
+
1736
+ <div id="index-prompt-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); z-index:9999; justify-content:center; align-items:center;">
1737
+ <div style="background:#252526; padding:20px; border-radius:8px; border:1px solid #444; width:350px; text-align:center;">
1738
+ <h3 style="margin-top:0; color:#d4d4d4; font-size:16px;">Giriş Gerekli</h3>
1739
+ <p id="index-prompt-desc" style="font-size:13px; color:#ccc; margin-bottom:15px;"></p>
1740
+ <input type="text" id="index-prompt-input" style="width:100%; padding:8px; background:#1e1e1e; border:1px solid #555; color:white; margin-bottom:15px; box-sizing:border-box; border-radius:4px;">
1741
+ <div style="display:flex; gap:10px; justify-content:center;">
1742
+ <button class="btn" style="background:#555; flex:1;" id="index-prompt-cancel">İptal</button>
1743
+ <button class="btn" style="background:#007acc; flex:1;" id="index-prompt-ok">Tamam</button>
1744
+ </div>
1745
+ </div>
1746
+ </div>
1747
+ <div id="index-alert-modal" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.7); z-index:9999; justify-content:center; align-items:center;">
1748
+ <div style="background:#252526; padding:20px; border-radius:8px; border:1px solid #444; width:450px; text-align:center;">
1749
+ <h3 style="margin-top:0; color:#f39c12; font-size:16px;">Bilgi</h3>
1750
+ <p id="index-alert-desc" style="font-size:13px; color:#ccc; margin-bottom:20px; white-space:pre-wrap; text-align:left; line-height:1.5;"></p>
1751
+ <button class="btn" style="background:#007acc; padding:8px 30px;" onclick="document.getElementById('index-alert-modal').style.display='none'">Tamam</button>
1752
+ </div>
1753
+ </div>
1754
+ </body>
1755
+ </html>