fdb2 1.0.0

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 (125) hide show
  1. package/.dockerignore +21 -0
  2. package/.editorconfig +11 -0
  3. package/.eslintrc.cjs +14 -0
  4. package/.eslintrc.json +7 -0
  5. package/.prettierrc.js +3 -0
  6. package/.tpl.env +22 -0
  7. package/README.md +260 -0
  8. package/bin/build.sh +28 -0
  9. package/bin/deploy.sh +8 -0
  10. package/bin/dev.sh +10 -0
  11. package/bin/docker/.env +4 -0
  12. package/bin/docker/dev-docker-compose.yml +43 -0
  13. package/bin/docker/dev.Dockerfile +24 -0
  14. package/bin/docker/prod-docker-compose.yml +17 -0
  15. package/bin/docker/prod.Dockerfile +29 -0
  16. package/bin/fdb2.js +142 -0
  17. package/data/connections.demo.json +32 -0
  18. package/env.d.ts +1 -0
  19. package/nw-build.js +120 -0
  20. package/nw-dev.js +65 -0
  21. package/package.json +114 -0
  22. package/public/favicon.ico +0 -0
  23. package/public/index.html +9 -0
  24. package/public/modules/header.tpl +14 -0
  25. package/public/modules/initial_state.tpl +55 -0
  26. package/server/index.ts +677 -0
  27. package/server/model/connection.entity.ts +66 -0
  28. package/server/model/database.entity.ts +246 -0
  29. package/server/service/connection.service.ts +334 -0
  30. package/server/service/database/base.service.ts +363 -0
  31. package/server/service/database/database.service.ts +510 -0
  32. package/server/service/database/index.ts +7 -0
  33. package/server/service/database/mssql.service.ts +723 -0
  34. package/server/service/database/mysql.service.ts +761 -0
  35. package/server/service/database/oracle.service.ts +839 -0
  36. package/server/service/database/postgres.service.ts +744 -0
  37. package/server/service/database/sqlite.service.ts +559 -0
  38. package/server/service/session.service.ts +158 -0
  39. package/server.js +128 -0
  40. package/src/adapter/ajax.ts +135 -0
  41. package/src/assets/base.css +1 -0
  42. package/src/assets/database.css +950 -0
  43. package/src/assets/images/collapse.png +0 -0
  44. package/src/assets/images/no-login.png +0 -0
  45. package/src/assets/images/svg/illustrations/illustration-1.svg +1 -0
  46. package/src/assets/images/svg/illustrations/illustration-2.svg +2 -0
  47. package/src/assets/images/svg/illustrations/illustration-3.svg +50 -0
  48. package/src/assets/images/svg/illustrations/illustration-4.svg +1 -0
  49. package/src/assets/images/svg/illustrations/illustration-5.svg +73 -0
  50. package/src/assets/images/svg/illustrations/illustration-6.svg +89 -0
  51. package/src/assets/images/svg/illustrations/illustration-7.svg +39 -0
  52. package/src/assets/images/svg/illustrations/illustration-8.svg +1 -0
  53. package/src/assets/images/svg/separators/curve-2.svg +3 -0
  54. package/src/assets/images/svg/separators/curve.svg +3 -0
  55. package/src/assets/images/svg/separators/line.svg +3 -0
  56. package/src/assets/images/theme/light/screen-1-1000x800.jpg +0 -0
  57. package/src/assets/images/theme/light/screen-2-1000x800.jpg +0 -0
  58. package/src/assets/login/bg.jpg +0 -0
  59. package/src/assets/login/bg.png +0 -0
  60. package/src/assets/login/left.jpg +0 -0
  61. package/src/assets/logo.svg +73 -0
  62. package/src/assets/logo.webp +0 -0
  63. package/src/assets/main.css +1 -0
  64. package/src/base/config.ts +20 -0
  65. package/src/base/detect.ts +134 -0
  66. package/src/base/entity.ts +92 -0
  67. package/src/base/eventBus.ts +37 -0
  68. package/src/base//345/237/272/347/241/200/345/261/202.md +7 -0
  69. package/src/components/connection-editor/index.vue +590 -0
  70. package/src/components/dataGrid/index.vue +105 -0
  71. package/src/components/dataGrid/pagination.vue +106 -0
  72. package/src/components/loading/index.vue +43 -0
  73. package/src/components/modal/index.ts +181 -0
  74. package/src/components/modal/index.vue +560 -0
  75. package/src/components/toast/index.ts +44 -0
  76. package/src/components/toast/toast.vue +58 -0
  77. package/src/components/user/name.vue +104 -0
  78. package/src/components/user/selector.vue +416 -0
  79. package/src/domain/SysConfig.ts +74 -0
  80. package/src/platform/App.vue +8 -0
  81. package/src/platform/database/components/connection-detail.vue +1154 -0
  82. package/src/platform/database/components/data-editor.vue +478 -0
  83. package/src/platform/database/components/data-import-export.vue +1602 -0
  84. package/src/platform/database/components/database-detail.vue +1173 -0
  85. package/src/platform/database/components/database-monitor.vue +1086 -0
  86. package/src/platform/database/components/db-tools.vue +577 -0
  87. package/src/platform/database/components/query-history.vue +1349 -0
  88. package/src/platform/database/components/sql-executor.vue +738 -0
  89. package/src/platform/database/components/sql-query-editor.vue +1046 -0
  90. package/src/platform/database/components/table-detail.vue +1376 -0
  91. package/src/platform/database/components/table-editor.vue +690 -0
  92. package/src/platform/database/explorer.vue +1840 -0
  93. package/src/platform/database/index.vue +1193 -0
  94. package/src/platform/database/layout.vue +367 -0
  95. package/src/platform/database/router.ts +37 -0
  96. package/src/platform/database/styles/common.scss +602 -0
  97. package/src/platform/database/types/common.ts +445 -0
  98. package/src/platform/database/utils/export.ts +232 -0
  99. package/src/platform/database/utils/helpers.ts +437 -0
  100. package/src/platform/index.ts +33 -0
  101. package/src/platform/router.ts +41 -0
  102. package/src/service/base.ts +128 -0
  103. package/src/service/database.ts +500 -0
  104. package/src/service/login.ts +121 -0
  105. package/src/shims-vue.d.ts +7 -0
  106. package/src/stores/connection.ts +266 -0
  107. package/src/stores/session.ts +87 -0
  108. package/src/typings/database-types.ts +413 -0
  109. package/src/typings/database.ts +364 -0
  110. package/src/typings/global.d.ts +58 -0
  111. package/src/typings/pinia.d.ts +8 -0
  112. package/src/utils/clipboard.ts +30 -0
  113. package/src/utils/database-types.ts +243 -0
  114. package/src/utils/modal.ts +124 -0
  115. package/src/utils/request.ts +55 -0
  116. package/src/utils/sleep.ts +4 -0
  117. package/src/utils/toast.ts +73 -0
  118. package/src/utils/util.ts +171 -0
  119. package/src/utils/xlsx.ts +228 -0
  120. package/tsconfig.json +33 -0
  121. package/tsconfig.server.json +19 -0
  122. package/view/index.html +9 -0
  123. package/view/modules/header.tpl +14 -0
  124. package/view/modules/initial_state.tpl +20 -0
  125. package/vite.config.ts +384 -0
@@ -0,0 +1,577 @@
1
+ <template>
2
+ <div class="db-tools">
3
+ <div class="tools-header">
4
+ <h5 class="tools-title">
5
+ <i class="bi bi-tools"></i>
6
+ 数据库管理工具
7
+ </h5>
8
+ </div>
9
+
10
+ <div class="tools-content">
11
+ <!-- 数据备份 -->
12
+ <div class="tool-section">
13
+ <h6 class="section-title">
14
+ <i class="bi bi-shield-check"></i>
15
+ 数据备份
16
+ </h6>
17
+ <div class="tool-actions">
18
+ <button class="btn btn-outline-primary btn-sm" @click="backupDatabase">
19
+ <i class="bi bi-download"></i> 备份数据库
20
+ </button>
21
+ <button class="btn btn-outline-secondary btn-sm" @click="showRestoreModal">
22
+ <i class="bi bi-upload"></i> 恢复数据库
23
+ </button>
24
+ <button class="btn btn-outline-info btn-sm" @click="showScheduleModal">
25
+ <i class="bi bi-clock"></i> 定时备份
26
+ </button>
27
+ </div>
28
+ </div>
29
+
30
+ <!-- 用户管理 -->
31
+ <div class="tool-section">
32
+ <h6 class="section-title">
33
+ <i class="bi bi-people"></i>
34
+ 用户管理
35
+ </h6>
36
+ <div class="tool-actions">
37
+ <button class="btn btn-outline-success btn-sm" @click="showUsersList">
38
+ <i class="bi bi-person-lines-fill"></i> 用户列表
39
+ </button>
40
+ <button class="btn btn-outline-primary btn-sm" @click="showCreateUserModal">
41
+ <i class="bi bi-person-plus"></i> 创建用户
42
+ </button>
43
+ <button class="btn btn-outline-warning btn-sm" @click="showPermissionsModal">
44
+ <i class="bi bi-key"></i> 权限管理
45
+ </button>
46
+ </div>
47
+ </div>
48
+
49
+ <!-- 性能监控 -->
50
+ <div class="tool-section">
51
+ <h6 class="section-title">
52
+ <i class="bi bi-speedometer2"></i>
53
+ 性能监控
54
+ </h6>
55
+ <div class="tool-actions">
56
+ <button class="btn btn-outline-info btn-sm" @click="showProcessList">
57
+ <i class="bi bi-activity"></i> 进程列表
58
+ </button>
59
+ <button class="btn btn-outline-warning btn-sm" @click="showSlowQueries">
60
+ <i class="bi bi-hourglass-split"></i> 慢查询
61
+ </button>
62
+ <button class="btn btn-outline-danger btn-sm" @click="showConnectionsList">
63
+ <i class="bi bi-diagram-3"></i> 连接数
64
+ </button>
65
+ </div>
66
+ </div>
67
+
68
+ <!-- 数据库优化 -->
69
+ <div class="tool-section">
70
+ <h6 class="section-title">
71
+ <i class="bi bi-gear-wide-connected"></i>
72
+ 数据库优化
73
+ </h6>
74
+ <div class="tool-actions">
75
+ <button class="btn btn-outline-success btn-sm" @click="optimizeDatabase">
76
+ <i class="bi bi-lightning-charge"></i> 优化数据库
77
+ </button>
78
+ <button class="btn btn-outline-primary btn-sm" @click="analyzeTables">
79
+ <i class="bi bi-search"></i> 分析表
80
+ </button>
81
+ <button class="btn btn-outline-secondary btn-sm" @click="repairTables">
82
+ <i class="bi bi-tools"></i> 修复表
83
+ </button>
84
+ <button class="btn btn-outline-info btn-sm" @click="clearLogs">
85
+ <i class="bi bi-trash"></i> 清理日志
86
+ </button>
87
+ </div>
88
+ </div>
89
+
90
+ <!-- 数据迁移 -->
91
+ <div class="tool-section">
92
+ <h6 class="section-title">
93
+ <i class="bi bi-arrow-left-right"></i>
94
+ 数据迁移
95
+ </h6>
96
+ <div class="tool-actions">
97
+ <button class="btn btn-outline-primary btn-sm" @click="showExportModal">
98
+ <i class="bi bi-box-arrow-up-right"></i> 导出结构
99
+ </button>
100
+ <button class="btn btn-outline-success btn-sm" @click="showImportModal">
101
+ <i class="bi bi-box-arrow-in-down"></i> 导入数据
102
+ </button>
103
+ <button class="btn btn-outline-warning btn-sm" @click="showSyncModal">
104
+ <i class="bi bi-arrow-repeat"></i> 数据同步
105
+ </button>
106
+ </div>
107
+ </div>
108
+
109
+ <!-- 健康检查 -->
110
+ <div class="tool-section">
111
+ <h6 class="section-title">
112
+ <i class="bi bi-heart-pulse"></i>
113
+ 健康检查
114
+ </h6>
115
+ <div class="tool-actions">
116
+ <button class="btn btn-outline-info btn-sm" @click="runHealthCheck">
117
+ <i class="bi bi-clipboard-check"></i> 健康检查
118
+ </button>
119
+ <button class="btn btn-outline-secondary btn-sm" @click="showStatistics">
120
+ <i class="bi bi-bar-chart"></i> 数据统计
121
+ </button>
122
+ <button class="btn btn-outline-warning btn-sm" @click="showAuditLog">
123
+ <i class="bi bi-journal-text"></i> 审计日志
124
+ </button>
125
+ </div>
126
+ </div>
127
+ </div>
128
+
129
+ <!-- 数据恢复模态框 -->
130
+ <div class="modal fade" :class="{ show: restoreModalVisible }" :style="{ display: restoreModalVisible ? 'block' : 'none', zIndex: 1055 }">
131
+ <div class="modal-dialog">
132
+ <div class="modal-content">
133
+ <div class="modal-header">
134
+ <h5 class="modal-title">恢复数据库</h5>
135
+ <button type="button" class="btn-close" @click="closeRestoreModal"></button>
136
+ </div>
137
+ <div class="modal-body">
138
+ <p>请选择要恢复的备份文件:</p>
139
+ <div class="mb-3">
140
+ <input type="file" class="form-control" @change="handleFileChange" accept=".sql,.bak">
141
+ </div>
142
+ <div v-if="selectedFile" class="alert alert-info">
143
+ 已选择文件:{{ selectedFile.name }}
144
+ </div>
145
+ <div class="mb-3 form-check">
146
+ <input type="checkbox" class="form-check-input" v-model="restoreOptions.dropExisting" id="dropExisting">
147
+ <label class="form-check-label" for="dropExisting">删除现有表</label>
148
+ </div>
149
+ </div>
150
+ <div class="modal-footer">
151
+ <button type="button" class="btn btn-secondary" @click="closeRestoreModal">取消</button>
152
+ <button type="button" class="btn btn-primary" @click="performRestore" :disabled="!selectedFile">
153
+ <span v-if="restoring" class="spinner-border spinner-border-sm me-2"></span>
154
+ 恢复
155
+ </button>
156
+ </div>
157
+ </div>
158
+ </div>
159
+ </div>
160
+
161
+ <!-- 健康检查结果模态框 -->
162
+ <div class="modal fade" :class="{ show: healthModalVisible }" :style="{ display: healthModalVisible ? 'block' : 'none', zIndex: 1055 }">
163
+ <div class="modal-dialog modal-lg">
164
+ <div class="modal-content">
165
+ <div class="modal-header">
166
+ <h5 class="modal-title">数据库健康检查</h5>
167
+ <button type="button" class="btn-close" @click="closeHealthModal"></button>
168
+ </div>
169
+ <div class="modal-body">
170
+ <div v-if="healthChecking" class="text-center py-4">
171
+ <div class="spinner-border text-primary" role="status"></div>
172
+ <div class="mt-3">正在检查数据库健康状况...</div>
173
+ </div>
174
+ <div v-else>
175
+ <div class="health-results">
176
+ <div v-for="check in healthResults" :key="check.name" class="health-item">
177
+ <div class="health-status">
178
+ <i :class="check.status === 'healthy' ? 'bi bi-check-circle-fill text-success' :
179
+ check.status === 'warning' ? 'bi bi-exclamation-triangle-fill text-warning' :
180
+ 'bi bi-x-circle-fill text-danger'"></i>
181
+ {{ check.name }}
182
+ </div>
183
+ <div class="health-message text-wrap">{{ check.message }}</div>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ </div>
188
+ <div class="modal-footer">
189
+ <button type="button" class="btn btn-secondary" @click="closeHealthModal">关闭</button>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ </div>
194
+ </div>
195
+ </template>
196
+
197
+ <script lang="ts" setup>
198
+ import { ref } from 'vue';
199
+ import { DatabaseService } from '@/service/database';
200
+ import { modal } from '@/utils/modal';
201
+
202
+ const props = defineProps<{
203
+ connection: any;
204
+ database: string;
205
+ }>();
206
+
207
+ const emit = defineEmits<{
208
+ 'execute-sql': [sql: string];
209
+ }>();
210
+
211
+ const databaseService = new DatabaseService();
212
+
213
+ // 状态管理
214
+ const restoreModalVisible = ref(false);
215
+ const healthModalVisible = ref(false);
216
+ const selectedFile = ref<File | null>(null);
217
+ const restoring = ref(false);
218
+ const healthChecking = ref(false);
219
+ const healthResults = ref<any[]>([]);
220
+
221
+ const restoreOptions = ref({
222
+ dropExisting: false
223
+ });
224
+
225
+ // 数据备份
226
+ async function backupDatabase() {
227
+ try {
228
+ const res = await databaseService.backupDatabase(props.connection?.id || '', props.database);
229
+ if(res.ret === 0) await modal.success('数据库备份成功');
230
+ else {
231
+ modal.error(res.msg || '备份失败', {
232
+ operation: 'BACKUP',
233
+ database: props.database,
234
+ });
235
+ }
236
+ } catch (error) {
237
+ console.error('备份失败:', error);
238
+
239
+ modal.error(error.msg || error.message || '备份失败', {
240
+ operation: 'BACKUP',
241
+ database: props.database,
242
+ //options: backupOptions.value,
243
+ stack: error.stack
244
+ });
245
+ }
246
+ }
247
+
248
+ // 用户管理
249
+ function showUsersList() {
250
+ modal.info('用户列表功能开发中...');
251
+ }
252
+
253
+ function showCreateUserModal() {
254
+ modal.info('创建用户功能开发中...');
255
+ }
256
+
257
+ function showPermissionsModal() {
258
+ modal.info('权限管理功能开发中...');
259
+ }
260
+
261
+ // 性能监控
262
+ function showProcessList() {
263
+ const sql = 'SHOW PROCESSLIST';
264
+ emit('execute-sql', sql);
265
+ }
266
+
267
+ function showSlowQueries() {
268
+ const sql = 'SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10';
269
+ emit('execute-sql', sql);
270
+ }
271
+
272
+ function showConnectionsList() {
273
+ const sql = 'SHOW STATUS LIKE "Threads_connected"';
274
+ emit('execute-sql', sql);
275
+ }
276
+
277
+ // 数据库优化
278
+ async function optimizeDatabase() {
279
+ try {
280
+ await databaseService.optimizeDatabase(props.connection?.id || '', props.database);
281
+ await modal.success('数据库优化完成');
282
+ } catch (error) {
283
+ console.error('优化失败:', error);
284
+
285
+ modal.error(error.msg || error.message || '优化失败', {
286
+ operation: 'OPTIMIZE',
287
+ database: props.database,
288
+ //options: optimizationOptions.value,
289
+ stack: error.stack
290
+ });
291
+ }
292
+ }
293
+
294
+ async function analyzeTables() {
295
+ try {
296
+ await databaseService.analyzeTables(props.connection?.id || '', props.database);
297
+ await modal.success('表分析完成');
298
+ } catch (error) {
299
+ console.error('分析失败:', error);
300
+
301
+ modal.error(error.msg || error.message || '分析失败', {
302
+ operation: 'ANALYZE',
303
+ database: props.database,
304
+ //options: analysisOptions.value,
305
+ stack: error.stack
306
+ });
307
+ }
308
+ }
309
+
310
+ async function repairTables() {
311
+ try {
312
+ await databaseService.repairTables(props.connection?.id || '', props.database);
313
+ await modal.success('表修复完成');
314
+ } catch (error) {
315
+ console.error('修复失败:', error);
316
+
317
+ modal.error(error.msg || error.message || '修复失败', {
318
+ operation: 'REPAIR',
319
+ database: props.database,
320
+ //options: repairOptions.value,
321
+ stack: error.stack
322
+ });
323
+ }
324
+ }
325
+
326
+ async function clearLogs() {
327
+ const logs = [
328
+ 'TRUNCATE TABLE mysql.slow_log',
329
+ 'TRUNCATE TABLE mysql.general_log',
330
+ 'FLUSH LOGS'
331
+ ];
332
+
333
+ logs.forEach(sql => emit('execute-sql', sql));
334
+ await modal.success('日志清理完成');
335
+ }
336
+
337
+ // 数据迁移
338
+ function showExportModal() {
339
+ modal.info('导出结构功能开发中...');
340
+ }
341
+
342
+ function showImportModal() {
343
+ modal.info('导入数据功能开发中...');
344
+ }
345
+
346
+ function showSyncModal() {
347
+ modal.info('数据同步功能开发中...');
348
+ }
349
+
350
+ // 健康检查
351
+ async function runHealthCheck() {
352
+ healthChecking.value = true;
353
+ healthResults.value = [];
354
+
355
+ try {
356
+ const checks = [
357
+ { name: '连接状态', sql: 'SELECT 1 as status' },
358
+ { name: '表完整性', sql: 'SELECT COUNT(*) as status FROM information_schema.tables WHERE table_schema = DATABASE() AND table_type = "BASE TABLE"' },
359
+ { name: '索引状态', sql: 'SELECT COUNT(*) as status FROM information_schema.statistics WHERE table_schema = DATABASE()' },
360
+ { name: '磁盘空间', sql: 'SELECT SUM(data_length + index_length) as status FROM information_schema.tables WHERE table_schema = DATABASE()' }
361
+ ];
362
+
363
+ for (const check of checks) {
364
+ try {
365
+ // 这里应该调用实际的数据库查询
366
+ healthResults.value.push({
367
+ name: check.name,
368
+ status: 'healthy',
369
+ message: '正常'
370
+ });
371
+ } catch (error) {
372
+ healthResults.value.push({
373
+ name: check.name,
374
+ status: 'error',
375
+ message: error.message
376
+ });
377
+ }
378
+ }
379
+
380
+ healthModalVisible.value = true;
381
+ } finally {
382
+ healthChecking.value = false;
383
+ }
384
+ }
385
+
386
+ function showStatistics() {
387
+ const sql = `
388
+ SELECT
389
+ table_name as '表名',
390
+ table_rows as '记录数',
391
+ ROUND(((data_length + index_length) / 1024 / 1024), 2) as '大小(MB)'
392
+ FROM information_schema.tables
393
+ WHERE table_schema = DATABASE()
394
+ ORDER BY (data_length + index_length) DESC
395
+ `;
396
+ emit('execute-sql', sql);
397
+ }
398
+
399
+ function showAuditLog() {
400
+ const sql = 'SELECT * FROM mysql.general_log ORDER BY event_time DESC LIMIT 100';
401
+ emit('execute-sql', sql);
402
+ }
403
+
404
+ // 恢复功能
405
+ function showRestoreModal() {
406
+ restoreModalVisible.value = true;
407
+ }
408
+
409
+ function closeRestoreModal() {
410
+ restoreModalVisible.value = false;
411
+ selectedFile.value = null;
412
+ }
413
+
414
+ function handleFileSelect(event: Event) {
415
+ const target = event.target as HTMLInputElement;
416
+ if (target.files && target.files.length > 0) {
417
+ selectedFile.value = target.files[0];
418
+ }
419
+ }
420
+
421
+ async function performRestore() {
422
+ if (!selectedFile.value) return;
423
+
424
+ try {
425
+ restoring.value = true;
426
+ // 这里简化处理,实际项目中可能需要先上传文件
427
+ const filePath = selectedFile.value.name;
428
+
429
+ await databaseService.restoreDatabase(
430
+ props.connection?.id || '',
431
+ props.database,
432
+ filePath,
433
+ { dropExisting: restoreOptions.value.dropExisting }
434
+ );
435
+
436
+ await modal.success('数据库恢复成功');
437
+ closeRestoreModal();
438
+ } catch (error) {
439
+ console.error('恢复失败:', error);
440
+
441
+ modal.error(error.msg || error.message || '恢复失败', {
442
+ operation: 'RESTORE',
443
+ database: props.database,
444
+ //file: restoreFile.value,
445
+ stack: error.stack
446
+ });
447
+ } finally {
448
+ restoring.value = false;
449
+ }
450
+ }
451
+
452
+ // 健康检查模态框
453
+ function closeHealthModal() {
454
+ healthModalVisible.value = false;
455
+ healthResults.value = [];
456
+ }
457
+
458
+ // 辅助函数
459
+ async function fetchTableList(): Promise<string[]> {
460
+ // 这里应该调用API获取表列表
461
+ return [];
462
+ }
463
+
464
+ function showScheduleModal() {
465
+ modal.info('定时备份功能开发中...');
466
+ }
467
+ </script>
468
+
469
+ <style scoped>
470
+ .db-tools {
471
+ background: white;
472
+ border-radius: 12px;
473
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
474
+ overflow: hidden;
475
+ }
476
+
477
+ .tools-header {
478
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
479
+ color: white;
480
+ padding: 1rem 1.5rem;
481
+ border-bottom: 1px solid #e2e8f0;
482
+ }
483
+
484
+ .tools-title {
485
+ margin: 0;
486
+ font-size: 1.1rem;
487
+ font-weight: 600;
488
+ display: flex;
489
+ align-items: center;
490
+ gap: 0.5rem;
491
+ }
492
+
493
+ .tools-content {
494
+ padding: 1.5rem;
495
+ max-height: 500px;
496
+ overflow-y: auto;
497
+ }
498
+
499
+ .tool-section {
500
+ margin-bottom: 2rem;
501
+ }
502
+
503
+ .section-title {
504
+ font-size: 0.9rem;
505
+ font-weight: 600;
506
+ color: #374151;
507
+ margin-bottom: 1rem;
508
+ display: flex;
509
+ align-items: center;
510
+ gap: 0.5rem;
511
+ }
512
+
513
+ .tool-actions {
514
+ display: flex;
515
+ flex-wrap: wrap;
516
+ gap: 0.5rem;
517
+ }
518
+
519
+ .tool-actions .btn {
520
+ min-width: 120px;
521
+ display: flex;
522
+ align-items: center;
523
+ gap: 0.5rem;
524
+ }
525
+
526
+ .health-results {
527
+ max-height: 400px;
528
+ overflow-y: auto;
529
+ }
530
+
531
+ .health-item {
532
+ display: flex;
533
+ align-items: center;
534
+ padding: 0.75rem;
535
+ border-bottom: 1px solid #e2e8f0;
536
+ }
537
+
538
+ .health-status {
539
+ min-width: 150px;
540
+ font-weight: 600;
541
+ display: flex;
542
+ align-items: center;
543
+ gap: 0.5rem;
544
+ }
545
+
546
+ .health-message {
547
+ flex: 1;
548
+ color: #6b7280;
549
+ }
550
+
551
+ .modal {
552
+ background-color: rgba(0, 0, 0, 0.5);
553
+ }
554
+
555
+ .modal-dialog {
556
+ max-width: 600px;
557
+ }
558
+
559
+ .modal-lg .modal-dialog {
560
+ max-width: 800px;
561
+ }
562
+
563
+ .modal-header {
564
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
565
+ border-bottom: 1px solid #e2e8f0;
566
+ }
567
+
568
+ .modal-title {
569
+ color: #1e293b;
570
+ font-weight: 600;
571
+ }
572
+
573
+ .modal-footer {
574
+ background: #f8fafc;
575
+ border-top: 1px solid #e2e8f0;
576
+ }
577
+ </style>