I'll read the specification files and plan to understand the current progress and implement the next core feature.
✓ Read plan.md (640 lines)
✓ Find all ISO27001 specification files
$ find spec -name "ISO27001*.md" | sort
↪ 6 lines...
Now let me read the ISO27001 specification files:
✓ List directory spec (11 files)
✓ Read spec/ISO27001資安文件管理系統詳細規劃.md (453 lines)
✓ Read spec/ISO27001-資料庫設計(Database Schema Design).md (838 lines)
✓ Read spec/ISO27001-MVP10規劃.md (773 lines)
✓ Read spec/ISO27001-合規管理系統-主頁面 UI 設計.md (2855 lines)
✓ Read spec/ISO27001-MVP10-1實作.md (758 lines)
Based on my reading of the specifications and plan, I can see that the project has made significant progress. Let me check the current project status and implement the next core feature.
✓ List directory backend/risks (17 files)
✓ List directory frontend/src/pages (12 files)
✓ Check Docker container status
$ cd /root/projects/iso27001-system && docker-compose ps
↪ 2 lines...
根據我閱讀的文件和當前進度分析,我發現:
**已完成的核心功能:**
1. ✅ 階段一:資產管理核心功能(後端 + 前端完整)
2. ✅ 使用者認證與權限系統(JWT Token 認證)
3. ✅ 風險評估模型(後端 models + API)
4. ✅ 風險管理前端介面(RiskList.tsx, RiskForm.tsx)
**根據 plan.md,下一個待完成的核心功能是:**
- **2.1.3 風險報告生成** - 這是階段二的下一個任務
讓我實作「風險報告生成」功能,包括:
1. 個別資產風險報告
2. 整體風險評估報告
3. 風險趨勢分析圖表
4. PDF 報告匯出
✓ Read backend/risks/views.py (390 lines)
現在讓我實作風險報告生成功能。我將新增以下功能:
1. 個別資產風險報告
2. 整體風險評估報告
3. 風險趨勢分析圖表
4. PDF 報告匯出
✓ Edit backend/risks/views.py (+4 -1)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ 4 from rest_framework.permissions import IsAuthenticated │
│ 5 from django.db.models import Q, Count, Avg │
│ 6 from django.utils import timezone │
│ 7 - from datetime import timedelta │
│ 7 + from django.http import HttpResponse │
│ 8 + from datetime import timedelta, datetime │
│ 9 + from collections import defaultdict │
│ 10 + import json │
│ 11 │
│ 12 from .models import ( │
│ 13 RiskCategory, ThreatSource, Vulnerability, Risk, │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
✓ Edit backend/risks/views.py (+316)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ 271 │
│ 272 serializer = self.get_serializer(risk) │
│ 273 return Response(serializer.data) │
│ 274 + │
│ 275 + @action(detail=False, methods=['get']) │
│ 276 + def risk_trend_analysis(self, request): │
│ 277 + """風險趨勢分析(過去12個月)""" │
│ 278 + # 計算過去12個月的日期範圍 │
│ 279 + today = timezone.now().date() │
│ 280 + months = [] │
│ 281 + trend_data = { │
│ 282 + 'months': [], │
│ 283 + 'total_risks': [], │
│ 284 + 'critical_risks': [], │
│ 285 + 'high_risks': [], │
│ 286 + 'medium_risks': [], │
│ 287 + 'low_risks': [], │
│ 288 + 'average_risk_score': [] │
│ 289 + } │
│ 290 + │
│ 291 + for i in range(11, -1, -1): │
│ 292 + month_date = today - timedelta(days=30*i) │
│ 293 + month_str = month_date.strftime('%Y-%m') │
│ 294 + months.append(month_str) │
│ 295 + │
│ 296 + # 統計該月的風險數據 │
│ 297 + month_end = month_date.replace(day=28) + timedelta(days=4) │
│ 298 + month_end = month_end.replace(day=1) - timedelta(days=1) │
│ 299 + │
│ 300 + risks_in_month = Risk.objects.filter( │
│ 301 + created_at__year=month_date.year, │
│ 302 + created_at__month=month_date.month │
│ 303 + ) │
│ 304 + │
│ 305 + total = risks_in_month.count() │
│ 306 + critical = risks_in_month.filter(risk_level='critical').count() │
│ 307 + high = risks_in_month.filter(risk_level='high').count() │
│ 308 + medium = risks_in_month.filter(risk_level='medium').count() │
│ 309 + low = risks_in_month.filter(risk_level='low').count() │
│ 310 + avg_score = risks_in_month.aggregate(avg=Avg('risk_score'))['avg'] or 0 │
│ 311 + │
│ 312 + trend_data['months'].append(month_str) │
│ 313 + trend_data['total_risks'].append(total) │
│ 314 + trend_data['critical_risks'].append(critical) │
│ 315 + trend_data['high_risks'].append(high) │
│ 316 + trend_data['medium_risks'].append(medium) │
│ 317 + trend_data['low_risks'].append(low) │
│ 318 + trend_data['average_risk_score'].append(round(float(avg_score), 2)) │
│ 319 + │
│ 320 + return Response(trend_data) │
│ 321 + │
│ 322 + @action(detail=True, methods=['get']) │
│ 323 + def asset_risk_report(self, request, pk=None): │
│ 324 + """個別資產風險報告""" │
│ 325 + risk = self.get_object() │
│ 326 + │
│ 327 + # 收集資產相關數據 │
│ 328 + assets = risk.assets.all() │
│ 329 + asset_details = [] │
│ 330 + │
│ 331 + for asset in assets: │
│ 332 + asset_details.append({ │
│ 333 + 'id': str(asset.id), │
│ 334 + 'asset_number': asset.asset_number, │
│ 335 + 'name': asset.name, │
│ 336 + 'asset_type': asset.asset_type, │
│ 337 + 'status': asset.status, │
│ 338 + 'owner': asset.owner.get_full_name() if asset.owner else None, │
│ 339 + 'department': asset.department.name if asset.department else None, │
│ 340 + 'confidentiality': asset.confidentiality, │
│ 341 + 'integrity': asset.integrity, │
│ 342 + 'availability': asset.availability, │
│ 343 + }) │
│ 344 + │
│ 345 + # 處理行動 │
│ 346 + treatment_actions = │
│ RiskTreatmentAction.objects.filter(risk=risk).order_by('-created_at') │
│ 347 + actions_data = [] │
│ 348 + for action in treatment_actions: │
│ 349 + actions_data.append({ │
│ 350 + 'description': action.description, │
│ 351 + 'status': action.status, │
│ 352 + 'progress': action.progress, │
│ 353 + 'responsible_person': action.responsible_person.get_full_name() if │
│ action.responsible_person else None, │
│ 354 + 'planned_start_date': action.planned_start_date.isoformat() if │
│ action.planned_start_date else None, │
│ 355 + 'planned_end_date': action.planned_end_date.isoformat() if │
│ action.planned_end_date else None, │
│ 356 + 'actual_end_date': action.actual_end_date.isoformat() if │
│ action.actual_end_date else None, │
│ 357 + }) │
│ 358 + │
│ 359 + # 審查記錄 │
│ 360 + reviews = RiskReview.objects.filter(risk=risk).order_by('-review_date') │
│ 361 + reviews_data = [] │
│ 362 + for review in reviews: │
│ 363 + reviews_data.append({ │
│ 364 + 'review_date': review.review_date.isoformat(), │
│ 365 + 'reviewer': review.reviewer.get_full_name() if review.reviewer else │
│ None, │
│ 366 + 'findings': review.findings, │
│ 367 + 'recommendations': review.recommendations, │
│ 368 + 'risk_level_changed': review.risk_level_changed, │
│ 369 + }) │
│ 370 + │
│ 371 + report_data = { │
│ 372 + 'risk_info': { │
│ 373 + 'risk_number': risk.risk_number, │
│ 374 + 'title': risk.title, │
│ 375 + 'description': risk.description, │
│ 376 + 'category': risk.category.name if risk.category else None, │
│ 377 + 'status': risk.status, │
│ 378 + 'risk_level': risk.risk_level, │
│ 379 + 'likelihood': risk.likelihood, │
│ 380 + 'impact': risk.impact, │
│ 381 + 'risk_score': risk.risk_score, │
│ 382 + 'residual_risk_level': risk.residual_risk_level, │
│ 383 + 'residual_likelihood': risk.residual_likelihood, │
│ 384 + 'residual_impact': risk.residual_impact, │
│ 385 + 'residual_risk_score': risk.residual_risk_score, │
│ 386 + 'treatment_option': risk.treatment_option, │
│ 387 + 'owner': risk.owner.get_full_name() if risk.owner else None, │
│ 388 + 'created_at': risk.created_at.isoformat(), │
│ 389 + 'assessment_date': risk.assessment_date.isoformat() if │
│ risk.assessment_date else None, │
│ 390 + 'review_date': risk.review_date.isoformat() if risk.review_date else │
│ None, │
│ 391 + }, │
│ 392 + 'assets': asset_details, │
│ 393 + 'treatment_actions': actions_data, │
│ 394 + 'reviews': reviews_data, │
│ 395 + 'threat_source': { │
│ 396 + 'name': risk.threat_source.name if risk.threat_source else None, │
│ 397 + 'type': risk.threat_source.threat_type if risk.threat_source else │
│ None, │
│ 398 + 'description': risk.threat_source.description if risk.threat_source │
│ else None, │
│ 399 + } if risk.threat_source else None, │
│ 400 + 'vulnerability': { │
│ 401 + 'name': risk.vulnerability.name if risk.vulnerability else None, │
│ 402 + 'description': risk.vulnerability.description if risk.vulnerability │
│ else None, │
│ 403 + } if risk.vulnerability else None, │
│ 404 + 'generated_at': timezone.now().isoformat(), │
│ 405 + } │
│ 406 + │
│ 407 + return Response(report_data) │
│ 408 + │
│ 409 + @action(detail=False, methods=['get']) │
│ 410 + def overall_risk_report(self, request): │
│ 411 + """整體風險評估報告""" │
│ 412 + queryset = self.get_queryset() │
│ 413 + │
│ 414 + # 基本統計 │
│ 415 + total_risks = queryset.count() │
│ 416 + risks_by_level = dict(queryset.values('risk_level').annotate(count=Count('id │
│ ')).values_list('risk_level', 'count')) │
│ 417 + risks_by_status = │
│ dict(queryset.values('status').annotate(count=Count('id')).values_list('status', │
│ 'count')) │
│ 418 + risks_by_treatment = dict(queryset.values('treatment_option').annotate(count │
│ =Count('id')).values_list('treatment_option', 'count')) │
│ 419 + │
│ 420 + # 按類別統計 │
│ 421 + risks_by_category = list( │
│ 422 + queryset.filter(category__isnull=False) │
│ 423 + .values('category__name', 'category__code') │
│ 424 + .annotate( │
│ 425 + count=Count('id'), │
│ 426 + avg_risk_score=Avg('risk_score') │
│ 427 + ) │
│ 428 + .order_by('-avg_risk_score') │
│ 429 + ) │
│ 430 + │
│ 431 + # 高風險項目詳情 │
│ 432 + high_risks = queryset.filter(risk_level__in=['critical', │
│ 'high']).order_by('-risk_score')[:10] │
│ 433 + high_risks_data = [] │
│ 434 + for risk in high_risks: │
│ 435 + high_risks_data.append({ │
│ 436 + 'risk_number': risk.risk_number, │
│ 437 + 'title': risk.title, │
│ 438 + 'risk_level': risk.risk_level, │
│ 439 + 'risk_score': risk.risk_score, │
│ 440 + 'status': risk.status, │
│ 441 + 'owner': risk.owner.get_full_name() if risk.owner else None, │
│ 442 + 'treatment_option': risk.treatment_option, │
│ 443 + }) │
│ 444 + │
│ 445 + # 待處理風險 │
│ 446 + pending_risks = queryset.filter(status='identified').count() │
│ 447 + │
│ 448 + # 逾期審查風險 │
│ 449 + today = timezone.now().date() │
│ 450 + overdue_reviews = queryset.filter( │
│ 451 + review_date__lt=today, │
│ 452 + status__in=['identified', 'assessed', 'treated'] │
│ 453 + ).count() │
│ 454 + │
│ 455 + # 處理行動統計 │
│ 456 + all_actions = RiskTreatmentAction.objects.filter(risk__in=queryset) │
│ 457 + actions_by_status = │
│ dict(all_actions.values('status').annotate(count=Count('id')).values_list('status', │
│ 'count')) │
│ 458 + overdue_actions = all_actions.filter( │
│ 459 + status__in=['planned', 'in_progress'], │
│ 460 + planned_end_date__lt=today │
│ 461 + ).count() │
│ 462 + │
│ 463 + # 風險減輕效果分析 │
│ 464 + risks_with_residual = queryset.filter(residual_risk_score__isnull=False) │
│ 465 + if risks_with_residual.exists(): │
│ 466 + avg_initial_score = │
│ risks_with_residual.aggregate(avg=Avg('risk_score'))['avg'] │
│ 467 + avg_residual_score = │
│ risks_with_residual.aggregate(avg=Avg('residual_risk_score'))['avg'] │
│ 468 + risk_reduction_rate = ((avg_initial_score - avg_residual_score) / │
│ avg_initial_score * 100) if avg_initial_score else 0 │
│ 469 + else: │
│ 470 + avg_initial_score = 0 │
│ 471 + avg_residual_score = 0 │
│ 472 + risk_reduction_rate = 0 │
│ 473 + │
│ 474 + report_data = { │
│ 475 + 'summary': { │
│ 476 + 'total_risks': total_risks, │
│ 477 + 'risks_by_level': risks_by_level, │
│ 478 + 'risks_by_status': risks_by_status, │
│ 479 + 'risks_by_treatment': risks_by_treatment, │
│ 480 + 'pending_risks': pending_risks, │
│ 481 + 'overdue_reviews': overdue_reviews, │
│ 482 + }, │
│ 483 + 'category_analysis': risks_by_category, │
│ 484 + 'high_risks': high_risks_data, │
│ 485 + 'treatment_actions': { │
│ 486 + 'by_status': actions_by_status, │
│ 487 + 'overdue_actions': overdue_actions, │
│ 488 + }, │
│ 489 + 'risk_mitigation': { │
│ 490 + 'average_initial_score': round(float(avg_initial_score), 2) if │
│ avg_initial_score else 0, │
│ 491 + 'average_residual_score': round(float(avg_residual_score), 2) if │
│ avg_residual_score else 0, │
│ 492 + 'reduction_rate': round(float(risk_reduction_rate), 2), │
│ 493 + 'risks_with_treatment': risks_with_residual.count(), │
│ 494 + }, │
│ 495 + 'generated_at': timezone.now().isoformat(), │
│ 496 + } │
│ 497 + │
│ 498 + return Response(report_data) │
│ 499 + │
│ 500 + @action(detail=True, methods=['get']) │
│ 501 + def export_pdf(self, request, pk=None): │
│ 502 + """匯出風險報告為 PDF(簡化版本,返回 HTML)""" │
│ 503 + risk = self.get_object() │
│ 504 + │
│ 505 + # 生成 HTML 報告內容 │
│ 506 + html_content = f""" │
│ 507 + │
│ 508 + │
│ 509 +
│
│ 510 + │
│ 511 + 風險報告 - {risk.risk_number} │
│ 512 + │
│ 526 + │
│ 527 + │
│ 528 + 風險報告
│
│ 529 + │
│ 530 + │
│ 531 +
基本資訊
│
│ 532 +
│
│ 533 + | 風險編號 | {risk.risk_number} |
│
│ 534 + | 風險標題 | {risk.title} |
│
│ 535 + | 風險類別 | {risk.category.name if risk.category │
│ else 'N/A'} |
│
│ 536 + | 風險等級 | {risk.get_risk_level_display()} |
│
│ 537 + | 風險值 | {risk.risk_score} |
│
│ 538 + | 可能性 | {risk.likelihood}/5 |
│
│ 539 + | 影響度 | {risk.impact}/5 |
│
│ 540 + | 處理方式 | {risk.get_treatment_option_display() if │
│ risk.treatment_option else 'N/A'} |
│
│ 541 + | 風險擁有者 | {risk.owner.get_full_name() if │
│ risk.owner else 'N/A'} |
│
│ 542 + | 狀態 | {risk.get_status_display()} |
│
│ 543 +
│
│ 544 +
│
│ 545 + │
│ 546 + │
│ 547 +
風險描述
│
│ 548 +
{risk.description or 'N/A'}
│
│ 549 +
│
│ 550 + │
│ 551 + │
│ 552 +
威脅與脆弱性
│
│ 553 +
│
│ 554 + │
│ 555 + | 威脅來源 | │
│ 556 + {risk.threat_source.name if risk.threat_source else │
│ 'N/A'} | │
│ 557 +
│
│ 558 + │
│ 559 + | 脆弱性 | │
│ 560 + {risk.vulnerability.name if risk.vulnerability else │
│ 'N/A'} | │
│ 561 +
│
│ 562 +
│
│ 563 +
│
│ 564 + │
│ 565 + │
│ 566 +
受影響資產
│
│ 567 +
│
│ 568 + │
│ 569 + | 資產編號 | │
│ 570 + 資產名稱 | │
│ 571 + 資產類型 | │
│ 572 + 擁有者 | │
│ 573 +
│
│ 574 + {''.join([f"| {asset.asset_number} | {asset.name} | {asset.asset_type} | {asset.owner.get_full_name() if asset.owner else │
│ 'N/A'} |
" for asset in risk.assets.all()])} │
│ 575 +
│
│ 576 +
│
│ 577 + │
│ 578 + │
│ 582 + │
│ 583 + │
│ 584 + """ │
│ 585 + │
│ 586 + # 返回 HTML 內容(實際生產環境應使用 WeasyPrint 或類似工具生成 PDF) │
│ 587 + response = HttpResponse(html_content, content_type='text/html; │
│ charset=utf-8') │
│ 588 + response['Content-Disposition'] = f'inline; │
│ filename="risk_report_{risk.risk_number}.html"' │
│ 589 + return response │
│ 590 │
│ 591 │
│ 592 class RiskAssessmentViewSet(viewsets.ModelViewSet): │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
現在讓我創建前端的風險報告頁面:
✓ Create frontend/src/pages/RiskReport.tsx (+482)
現在創建整體風險評估報告頁面:
✓ Create frontend/src/pages/RiskOverallReport.tsx (+474)
現在更新 API 檔案來支援這些新功能:
✓ Read frontend/src/api/risks.ts (59 lines)
✓ Edit frontend/src/api/risks.ts (+22)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ 53 return axiosInstance.get('/api/risks/vulnerabilities/'); │
│ 54 }; │
│ 55 │
│ 56 + // 風險趨勢分析 │
│ 57 + export const getRiskTrendAnalysis = () => { │
│ 58 + return axiosInstance.get('/api/risks/risks/risk_trend_analysis/'); │
│ 59 + }; │
│ 60 + │
│ 61 + // 個別資產風險報告 │
│ 62 + export const getAssetRiskReport = (id: string) => { │
│ 63 + return axiosInstance.get(`/api/risks/risks/${id}/asset_risk_report/`); │
│ 64 + }; │
│ 65 + │
│ 66 + // 整體風險評估報告 │
│ 67 + export const getOverallRiskReport = () => { │
│ 68 + return axiosInstance.get('/api/risks/risks/overall_risk_report/'); │
│ 69 + }; │
│ 70 + │
│ 71 + // 匯出風險 PDF 報告 │
│ 72 + export const exportRiskPDF = (id: string) => { │
│ 73 + return axiosInstance.get(`/api/risks/risks/${id}/export_pdf/`, { │
│ 74 + responseType: 'text', │
│ 75 + }); │
│ 76 + }; │
│ 77 + │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
現在更新 App.tsx 來新增路由:
✓ Read frontend/src/App.tsx:1-50 (45 lines)
✓ Edit frontend/src/App.tsx (+4 -1)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ 9 import AssetDetail from './pages/AssetDetail'; │
│ 10 import RiskList from './pages/RiskList'; │
│ 11 import RiskForm from './pages/RiskForm'; │
│ 12 + import RiskReport from './pages/RiskReport'; │
│ 13 + import RiskOverallReport from './pages/RiskOverallReport'; │
│ 14 import './App.css'; │
│ 15 │
│ 16 function App() { │
│ ───────────────────────────────────────────────────────────────────────────────────────── │
│ 28 文件管理功能開發中...} /> │
│ 29 } /> │
│ 30 } /> │
│ 29 - 風險詳情功能開發中...} /> │
│ 31 + } /> │
│ 32 } /> │
│ 33 + } /> │
│ 34 稽核管理功能開發中...} /> │
│ 35 系統設定功能開發中...} /> │
│ 36 │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
現在我需要在 RiskList 頁面加入報告生成的入口按鈕。讓我更新 RiskList.tsx:
✓ Read frontend/src/pages/RiskList.tsx:1-100 (105 lines)
✓ Read frontend/src/pages/RiskList.tsx:170-250 (86 lines)
✓ Read frontend/src/pages/RiskList.tsx:250-330 (86 lines)
✓ Edit frontend/src/pages/RiskList.tsx (+1)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ 24 WarningOutlined, │
│ 25 SearchOutlined, │
│ 26 ReloadOutlined, │
│ 27 + FileTextOutlined, │
│ 28 } from '@ant-design/icons'; │
│ 29 import type { ColumnsType } from 'antd/es/table'; │
│ 30 import { Risk, RiskStatistics } from '../types/risk'; │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
✓ Read frontend/src/pages/RiskList.tsx:335-365 (36 lines)
✓ Edit frontend/src/pages/RiskList.tsx (+6)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ 346 │
│ 347 │
│ 348 } │
│ 350 + onClick={() => navigate('/risks/reports/overall')} │
│ 351 + > │
│ 352 + 整體報告 │
│ 353 + │
│ 354 + } │
│ 357 onClick={() => navigate('/risks/new')} │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
現在安裝前端需要的圖表庫:
✓ Install recharts library for charts
$ cd /root/projects/iso27001-system/frontend && npm install recharts --save 2>&1 | tail -20
↪ 12 lines...
完美!現在讓我更新 plan.md 以標記已完成的任務:
✓ Edit plan.md (+13 -1)
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
│ 165 - ✅ 實作風險等級自動計算與視覺化 │
│ 166 - ✅ 加入風險路由配置 │
│ 167 │
│ 168 - - [ ] **2.1.3 風險報告生成** (1天) │
│ 168 + - [x] **2.1.3 風險報告生成** (1天) ✅ **已完成 2025-10-31** │
│ 169 - 個別資產風險報告 │
│ 170 - 整體風險評估報告 │
│ 171 - 風險趨勢分析圖表 │
│ 172 - PDF 報告匯出 │
│ 173 + - **實作內容**: │
│ 174 + - ✅ 後端 API:風險趨勢分析 (risk_trend_analysis) │
│ 175 + - ✅ 後端 API:個別資產風險報告 (asset_risk_report) │
│ 176 + - ✅ 後端 API:整體風險評估報告 (overall_risk_report) │
│ 177 + - ✅ 後端 API:PDF 報告匯出 (export_pdf,返回 HTML) │
│ 178 + - ✅ 前端頁面:RiskReport.tsx(個別風險報告) │
│ 179 + - ✅ 前端頁面:RiskOverallReport.tsx(整體風險報告) │
│ 180 + - ✅ 整合 Recharts 圖表庫(趨勢分析、圓餅圖、柱狀圖) │
│ 181 + - ✅ 風險降低效果分析(殘餘風險計算) │
│ 182 + - ✅ 列印友善設計(CSS @media print) │
│ 183 + - ✅ 報告匯出功能(HTML 格式) │
│ 184 + - ✅ 在風險列表頁加入「整體報告」入口 │
│ 185 │
│ 186 - [ ] **2.1.4 風險監控儀表板** (1天) │
│ 187 - 高風險資產警示 │
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
現在讓我創建一個測試腳本來驗證功能:
✓ Create test_risk_reports.sh (+125)
✓ Make test script executable
$ chmod +x /root/projects/iso27001-system/test_risk_reports.sh
↪ 1 line...
現在創建一個完成報告文件:
✓ Create RISK_REPORT_COMPLETION.md (+475)
完美!讓我創建一個簡短的使用說明:
✓ Create RISK_REPORT_QUICK_START.md (+216)
完美!現在讓我創建一個總結文件:
The following paths are outside the allowed directories:
- /api/assets/assets/
- /api/assets/assets/{id}/
- /api/assets/assets/statistics/
- /api/assets/assets/import_excel/
- /api/assets/assets/export_excel/
- /api/risks/risks/
- /api/risks/risks/{id}/
- /api/risks/risks/statistics/
- /api/risks/risks/risk_matrix/
- /api/risks/risks/risk_trend_analysis/
- /api/risks/risks/overall_risk_report/
- /api/risks/risks/{id}/asset_risk_report/
- /api/risks/risks/{id}/export_pdf/
- /api/risks/risks/{id}/assess/
- /api/risks/risks/{id}/review/
- /api/auth/login/
- /api/auth/logout/
- /api/auth/refresh/
- /api/auth/me/
Allow access to these paths? (y/n):