create-appraise 0.1.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.
- package/README.md +52 -0
- package/package.json +63 -0
- package/templates/default/.env.example +2 -0
- package/templates/default/README.md +51 -0
- package/templates/default/appraise.config.json +4 -0
- package/templates/default/components.json +24 -0
- package/templates/default/eslint.config.mjs +15 -0
- package/templates/default/next-env.d.ts +6 -0
- package/templates/default/next.config.ts +7 -0
- package/templates/default/package-lock.json +14321 -0
- package/templates/default/package.json +124 -0
- package/templates/default/postcss.config.mjs +8 -0
- package/templates/default/prisma/migrations/20251026202316_migrate_back_to_sqlite/migration.sql +257 -0
- package/templates/default/prisma/migrations/20251104113456_add_type_for_template_step_groups/migration.sql +16 -0
- package/templates/default/prisma/migrations/20251104170946_add_tags_to_test_suite_and_test_case/migration.sql +27 -0
- package/templates/default/prisma/migrations/20251112190024_add_cascade_delete_to_test_run_test_case/migration.sql +17 -0
- package/templates/default/prisma/migrations/20251113181100_add_test_run_log/migration.sql +12 -0
- package/templates/default/prisma/migrations/20251119191838_add_tag_type/migration.sql +28 -0
- package/templates/default/prisma/migrations/20251121164059_add_conflict_resolution/migration.sql +12 -0
- package/templates/default/prisma/migrations/20251130190737_add_trace_path_to_test_run_test_case/migration.sql +2 -0
- package/templates/default/prisma/migrations/20251213074835_add_log_path_to_test_run/migration.sql +2 -0
- package/templates/default/prisma/migrations/20251213183952_add_name_property_for_the_test_run_entities/migration.sql +30 -0
- package/templates/default/prisma/migrations/20251223183400_add_report_model_to_db_schema/migration.sql +10 -0
- package/templates/default/prisma/migrations/20251223183637_add_report_test_case_entity_for_storing_test_results_for_individual_test_cases/migration.sql +10 -0
- package/templates/default/prisma/migrations/20251224083549_add_comprehensive_report_storage/migration.sql +108 -0
- package/templates/default/prisma/migrations/20251229194422_migrate_duration_to_string/migration.sql +55 -0
- package/templates/default/prisma/migrations/20251230124637_add_unique_constraint_to_test_run_name/migration.sql +27 -0
- package/templates/default/prisma/migrations/20260115094436_add_dashboard_metrics/migration.sql +59 -0
- package/templates/default/prisma/migrations/20260127172022_add_cascade_delete_to_step_parameters/migration.sql +34 -0
- package/templates/default/prisma/migrations/migration_lock.toml +3 -0
- package/templates/default/prisma/schema.prisma +554 -0
- package/templates/default/public/favicon.ico +0 -0
- package/templates/default/public/file.svg +1 -0
- package/templates/default/public/globe.svg +1 -0
- package/templates/default/public/next.svg +1 -0
- package/templates/default/public/vercel.svg +1 -0
- package/templates/default/public/window.svg +1 -0
- package/templates/default/scripts/regenerate-features.ts +94 -0
- package/templates/default/scripts/setup-env.ts +19 -0
- package/templates/default/scripts/sync-all.ts +341 -0
- package/templates/default/scripts/sync-environments.ts +323 -0
- package/templates/default/scripts/sync-locator-groups.ts +413 -0
- package/templates/default/scripts/sync-locators.ts +402 -0
- package/templates/default/scripts/sync-modules.ts +349 -0
- package/templates/default/scripts/sync-tags.ts +292 -0
- package/templates/default/scripts/sync-template-step-groups.ts +399 -0
- package/templates/default/scripts/sync-template-steps.ts +806 -0
- package/templates/default/scripts/sync-test-cases.ts +905 -0
- package/templates/default/scripts/sync-test-suites.ts +411 -0
- package/templates/default/src/actions/conflict/conflict.action.ts +33 -0
- package/templates/default/src/actions/dashboard/dashboard-actions.ts +241 -0
- package/templates/default/src/actions/environments/environment-actions.ts +205 -0
- package/templates/default/src/actions/locator/locator-actions.ts +547 -0
- package/templates/default/src/actions/locator-groups/locator-group-actions.ts +344 -0
- package/templates/default/src/actions/modules/module-actions.ts +133 -0
- package/templates/default/src/actions/reports/report-actions.ts +614 -0
- package/templates/default/src/actions/review/review-actions.ts +147 -0
- package/templates/default/src/actions/tags/tag-actions.ts +104 -0
- package/templates/default/src/actions/template-step/template-step-actions.ts +332 -0
- package/templates/default/src/actions/template-step-group/template-step-group-actions.ts +278 -0
- package/templates/default/src/actions/template-test-case/template-test-case-actions.ts +238 -0
- package/templates/default/src/actions/test-case/test-case-actions.ts +419 -0
- package/templates/default/src/actions/test-run/test-run-actions.ts +1185 -0
- package/templates/default/src/actions/test-suite/test-suite-actions.ts +253 -0
- package/templates/default/src/actions/user/user-actions.ts +13 -0
- package/templates/default/src/app/(base)/environments/create/page.tsx +28 -0
- package/templates/default/src/app/(base)/environments/environment-form.tsx +219 -0
- package/templates/default/src/app/(base)/environments/environment-table-columns.tsx +96 -0
- package/templates/default/src/app/(base)/environments/environment-table.tsx +24 -0
- package/templates/default/src/app/(base)/environments/modify/[id]/page.tsx +46 -0
- package/templates/default/src/app/(base)/environments/page.tsx +59 -0
- package/templates/default/src/app/(base)/layout.tsx +10 -0
- package/templates/default/src/app/(base)/locator-groups/create/page.tsx +44 -0
- package/templates/default/src/app/(base)/locator-groups/locator-group-form.tsx +215 -0
- package/templates/default/src/app/(base)/locator-groups/locator-group-table-columns.tsx +77 -0
- package/templates/default/src/app/(base)/locator-groups/locator-group-table.tsx +28 -0
- package/templates/default/src/app/(base)/locator-groups/modify/[id]/page.tsx +46 -0
- package/templates/default/src/app/(base)/locator-groups/page.tsx +61 -0
- package/templates/default/src/app/(base)/locators/create/page.tsx +38 -0
- package/templates/default/src/app/(base)/locators/locator-form.tsx +163 -0
- package/templates/default/src/app/(base)/locators/locator-table-columns.tsx +90 -0
- package/templates/default/src/app/(base)/locators/locator-table.tsx +28 -0
- package/templates/default/src/app/(base)/locators/modify/[id]/page.tsx +45 -0
- package/templates/default/src/app/(base)/locators/page.tsx +65 -0
- package/templates/default/src/app/(base)/locators/sync-locators-button.tsx +66 -0
- package/templates/default/src/app/(base)/modules/create/page.tsx +34 -0
- package/templates/default/src/app/(base)/modules/modify/[id]/page.tsx +46 -0
- package/templates/default/src/app/(base)/modules/module-form.tsx +126 -0
- package/templates/default/src/app/(base)/modules/module-table-columns.tsx +85 -0
- package/templates/default/src/app/(base)/modules/module-table.tsx +24 -0
- package/templates/default/src/app/(base)/modules/page.tsx +59 -0
- package/templates/default/src/app/(base)/reports/[id]/page.tsx +517 -0
- package/templates/default/src/app/(base)/reports/duration-chart.tsx +33 -0
- package/templates/default/src/app/(base)/reports/feature-chart.tsx +78 -0
- package/templates/default/src/app/(base)/reports/overview-chart.tsx +46 -0
- package/templates/default/src/app/(base)/reports/page.tsx +98 -0
- package/templates/default/src/app/(base)/reports/report-metric-card.tsx +16 -0
- package/templates/default/src/app/(base)/reports/report-table-columns.tsx +189 -0
- package/templates/default/src/app/(base)/reports/report-table.tsx +72 -0
- package/templates/default/src/app/(base)/reports/report-view-table-columns.tsx +131 -0
- package/templates/default/src/app/(base)/reports/report-view-table.tsx +82 -0
- package/templates/default/src/app/(base)/reports/test-cases/page.tsx +42 -0
- package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table-columns.tsx +115 -0
- package/templates/default/src/app/(base)/reports/test-cases/test-cases-metric-table.tsx +27 -0
- package/templates/default/src/app/(base)/reports/test-suites/page.tsx +42 -0
- package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table-columns.tsx +79 -0
- package/templates/default/src/app/(base)/reports/test-suites/test-suites-metric-table.tsx +27 -0
- package/templates/default/src/app/(base)/reports/view-logs-button.tsx +60 -0
- package/templates/default/src/app/(base)/reviews/create/page.tsx +26 -0
- package/templates/default/src/app/(base)/reviews/created-reviews-table.tsx +15 -0
- package/templates/default/src/app/(base)/reviews/modify/[id]/page.tsx +26 -0
- package/templates/default/src/app/(base)/reviews/page.tsx +26 -0
- package/templates/default/src/app/(base)/reviews/review/[id]/page.tsx +26 -0
- package/templates/default/src/app/(base)/reviews/review-form.tsx +11 -0
- package/templates/default/src/app/(base)/reviews/review-table-by-creator-columns.tsx +9 -0
- package/templates/default/src/app/(base)/reviews/review-table-by-reviewer-columns.tsx +9 -0
- package/templates/default/src/app/(base)/reviews/reviewer-reviews-table.tsx +15 -0
- package/templates/default/src/app/(base)/tags/create/page.tsx +39 -0
- package/templates/default/src/app/(base)/tags/modify/[id]/page.tsx +50 -0
- package/templates/default/src/app/(base)/tags/page.tsx +58 -0
- package/templates/default/src/app/(base)/tags/tag-form.tsx +147 -0
- package/templates/default/src/app/(base)/tags/tag-table-columns.tsx +63 -0
- package/templates/default/src/app/(base)/tags/tag-table.tsx +29 -0
- package/templates/default/src/app/(base)/template-step-groups/create/page.tsx +28 -0
- package/templates/default/src/app/(base)/template-step-groups/modify/[id]/page.tsx +45 -0
- package/templates/default/src/app/(base)/template-step-groups/page.tsx +60 -0
- package/templates/default/src/app/(base)/template-step-groups/template-step-group-form.tsx +167 -0
- package/templates/default/src/app/(base)/template-step-groups/template-step-group-table-columns.tsx +89 -0
- package/templates/default/src/app/(base)/template-step-groups/template-step-group-table.tsx +32 -0
- package/templates/default/src/app/(base)/template-steps/create/page.tsx +37 -0
- package/templates/default/src/app/(base)/template-steps/modify/[id]/page.tsx +49 -0
- package/templates/default/src/app/(base)/template-steps/page.tsx +59 -0
- package/templates/default/src/app/(base)/template-steps/paramChip.tsx +213 -0
- package/templates/default/src/app/(base)/template-steps/template-step-form.tsx +384 -0
- package/templates/default/src/app/(base)/template-steps/template-step-table-columns.tsx +158 -0
- package/templates/default/src/app/(base)/template-steps/template-step-table.tsx +24 -0
- package/templates/default/src/app/(base)/template-test-cases/create/page.tsx +56 -0
- package/templates/default/src/app/(base)/template-test-cases/modify/[id]/page.tsx +89 -0
- package/templates/default/src/app/(base)/template-test-cases/page.tsx +58 -0
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-flow.tsx +84 -0
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-form.tsx +262 -0
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-table-columns.tsx +76 -0
- package/templates/default/src/app/(base)/template-test-cases/template-test-case-table.tsx +32 -0
- package/templates/default/src/app/(base)/test-cases/create/page.tsx +76 -0
- package/templates/default/src/app/(base)/test-cases/create-from-template/generate/[id]/page.tsx +96 -0
- package/templates/default/src/app/(base)/test-cases/create-from-template/page.tsx +38 -0
- package/templates/default/src/app/(base)/test-cases/create-from-template/template-selection-form.tsx +73 -0
- package/templates/default/src/app/(base)/test-cases/modify/[id]/page.tsx +106 -0
- package/templates/default/src/app/(base)/test-cases/page.tsx +60 -0
- package/templates/default/src/app/(base)/test-cases/test-case-flow.tsx +82 -0
- package/templates/default/src/app/(base)/test-cases/test-case-form.tsx +395 -0
- package/templates/default/src/app/(base)/test-cases/test-case-table-columns.tsx +90 -0
- package/templates/default/src/app/(base)/test-cases/test-case-table.tsx +35 -0
- package/templates/default/src/app/(base)/test-runs/[id]/page.tsx +56 -0
- package/templates/default/src/app/(base)/test-runs/create/page.tsx +47 -0
- package/templates/default/src/app/(base)/test-runs/page.tsx +60 -0
- package/templates/default/src/app/(base)/test-runs/test-run-form.tsx +512 -0
- package/templates/default/src/app/(base)/test-runs/test-run-table-columns.tsx +229 -0
- package/templates/default/src/app/(base)/test-runs/test-run-table.tsx +127 -0
- package/templates/default/src/app/(base)/test-suites/create/page.tsx +45 -0
- package/templates/default/src/app/(base)/test-suites/modify/[id]/page.tsx +55 -0
- package/templates/default/src/app/(base)/test-suites/page.tsx +82 -0
- package/templates/default/src/app/(base)/test-suites/test-suite-form.tsx +269 -0
- package/templates/default/src/app/(base)/test-suites/test-suite-table-columns.tsx +97 -0
- package/templates/default/src/app/(base)/test-suites/test-suite-table.tsx +29 -0
- package/templates/default/src/app/(dashboard-components)/app-drawer.tsx +187 -0
- package/templates/default/src/app/(dashboard-components)/data-card-grid.tsx +13 -0
- package/templates/default/src/app/(dashboard-components)/data-card.tsx +27 -0
- package/templates/default/src/app/(dashboard-components)/execution-health-panel.tsx +57 -0
- package/templates/default/src/app/(dashboard-components)/ongoing-test-runs-card.tsx +87 -0
- package/templates/default/src/app/(dashboard-components)/quick-actions-drawer.tsx +45 -0
- package/templates/default/src/app/api/test-runs/[runId]/download/route.ts +133 -0
- package/templates/default/src/app/api/test-runs/[runId]/logs/route.ts +420 -0
- package/templates/default/src/app/api/test-runs/[runId]/trace/[testCaseId]/route.ts +146 -0
- package/templates/default/src/app/favicon.ico +0 -0
- package/templates/default/src/app/globals.css +147 -0
- package/templates/default/src/app/layout.tsx +171 -0
- package/templates/default/src/app/page.tsx +64 -0
- package/templates/default/src/assets/icons/empty-tube.tsx +23 -0
- package/templates/default/src/assets/icons/tube-plus.tsx +29 -0
- package/templates/default/src/components/base-node.tsx +21 -0
- package/templates/default/src/components/chart/pie-chart.tsx +73 -0
- package/templates/default/src/components/data-extraction/locator-inspector.tsx +460 -0
- package/templates/default/src/components/data-state/empty-state.tsx +40 -0
- package/templates/default/src/components/data-visualization/info-card.tsx +70 -0
- package/templates/default/src/components/data-visualization/info-grid.tsx +22 -0
- package/templates/default/src/components/devtools/providers.tsx +13 -0
- package/templates/default/src/components/diagram/button-edge.tsx +54 -0
- package/templates/default/src/components/diagram/dynamic-parameters.tsx +438 -0
- package/templates/default/src/components/diagram/edit-header-option.tsx +36 -0
- package/templates/default/src/components/diagram/flow-diagram.tsx +470 -0
- package/templates/default/src/components/diagram/node-form.tsx +262 -0
- package/templates/default/src/components/diagram/options-header-node.tsx +57 -0
- package/templates/default/src/components/diagram/template-step-combobox.tsx +155 -0
- package/templates/default/src/components/form/error-message.tsx +7 -0
- package/templates/default/src/components/kokonutui/smooth-tab.tsx +453 -0
- package/templates/default/src/components/loading-skeleton/data-table/data-table-skeleton.tsx +30 -0
- package/templates/default/src/components/loading-skeleton/form/button-skeleton.tsx +8 -0
- package/templates/default/src/components/loading-skeleton/form/icon-button-skeleton.tsx +8 -0
- package/templates/default/src/components/loading-skeleton/form/text-input-skeleton.tsx +8 -0
- package/templates/default/src/components/loading-skeleton/visualization/table-skeleton.tsx +14 -0
- package/templates/default/src/components/logo.tsx +15 -0
- package/templates/default/src/components/navigation/command-badge.tsx +34 -0
- package/templates/default/src/components/navigation/command-chain-input.tsx +51 -0
- package/templates/default/src/components/navigation/entity-search-command.tsx +116 -0
- package/templates/default/src/components/navigation/nav-card.tsx +31 -0
- package/templates/default/src/components/navigation/nav-command.tsx +508 -0
- package/templates/default/src/components/navigation/nav-link.tsx +60 -0
- package/templates/default/src/components/navigation/nav-menu-card-deck.tsx +112 -0
- package/templates/default/src/components/node-header.tsx +159 -0
- package/templates/default/src/components/reports/test-case-logs-modal.tsx +253 -0
- package/templates/default/src/components/table/table-actions.tsx +172 -0
- package/templates/default/src/components/test-run/download-logs-button.tsx +99 -0
- package/templates/default/src/components/test-run/log-viewer.tsx +445 -0
- package/templates/default/src/components/test-run/test-run-details.tsx +611 -0
- package/templates/default/src/components/test-run/test-run-header.tsx +149 -0
- package/templates/default/src/components/test-run/view-report-button.tsx +102 -0
- package/templates/default/src/components/theme/mode-toggle.tsx +54 -0
- package/templates/default/src/components/theme/theme-provider.tsx +8 -0
- package/templates/default/src/components/typography/page-header-subtitle.tsx +7 -0
- package/templates/default/src/components/typography/page-header.tsx +7 -0
- package/templates/default/src/components/ui/alert-dialog.tsx +106 -0
- package/templates/default/src/components/ui/alert.tsx +43 -0
- package/templates/default/src/components/ui/avatar.tsx +40 -0
- package/templates/default/src/components/ui/badge.tsx +29 -0
- package/templates/default/src/components/ui/button.tsx +47 -0
- package/templates/default/src/components/ui/calendar.tsx +158 -0
- package/templates/default/src/components/ui/card.tsx +43 -0
- package/templates/default/src/components/ui/chart.tsx +369 -0
- package/templates/default/src/components/ui/checkbox.tsx +28 -0
- package/templates/default/src/components/ui/command.tsx +135 -0
- package/templates/default/src/components/ui/data-table-column-header.tsx +61 -0
- package/templates/default/src/components/ui/data-table-pagination.tsx +87 -0
- package/templates/default/src/components/ui/data-table-view-options.tsx +50 -0
- package/templates/default/src/components/ui/data-table.tsx +267 -0
- package/templates/default/src/components/ui/dialog.tsx +97 -0
- package/templates/default/src/components/ui/dropdown-menu.tsx +182 -0
- package/templates/default/src/components/ui/empty.tsx +104 -0
- package/templates/default/src/components/ui/input.tsx +22 -0
- package/templates/default/src/components/ui/kbd.tsx +28 -0
- package/templates/default/src/components/ui/label.tsx +19 -0
- package/templates/default/src/components/ui/loading.tsx +12 -0
- package/templates/default/src/components/ui/multi-select-with-preview.tsx +116 -0
- package/templates/default/src/components/ui/multi-select.tsx +142 -0
- package/templates/default/src/components/ui/navigation-menu.tsx +120 -0
- package/templates/default/src/components/ui/popover.tsx +33 -0
- package/templates/default/src/components/ui/progress.tsx +25 -0
- package/templates/default/src/components/ui/radio-group.tsx +44 -0
- package/templates/default/src/components/ui/scroll-area.tsx +40 -0
- package/templates/default/src/components/ui/select.tsx +144 -0
- package/templates/default/src/components/ui/separator.tsx +22 -0
- package/templates/default/src/components/ui/skeleton.tsx +7 -0
- package/templates/default/src/components/ui/table.tsx +76 -0
- package/templates/default/src/components/ui/tabs.tsx +55 -0
- package/templates/default/src/components/ui/textarea.tsx +21 -0
- package/templates/default/src/components/ui/toast.tsx +113 -0
- package/templates/default/src/components/ui/toaster.tsx +26 -0
- package/templates/default/src/components/ui/tooltip.tsx +32 -0
- package/templates/default/src/components/user-prompt/delete-prompt.tsx +87 -0
- package/templates/default/src/config/db-config.ts +10 -0
- package/templates/default/src/constants/form-opts/diagram/node-form.ts +30 -0
- package/templates/default/src/constants/form-opts/environment-form-opts.ts +24 -0
- package/templates/default/src/constants/form-opts/locator-form-opts.ts +20 -0
- package/templates/default/src/constants/form-opts/locator-group-form-opts.ts +28 -0
- package/templates/default/src/constants/form-opts/module-form-opts.ts +21 -0
- package/templates/default/src/constants/form-opts/review-form-opts.ts +23 -0
- package/templates/default/src/constants/form-opts/tag-form-opts.ts +42 -0
- package/templates/default/src/constants/form-opts/template-selection-form-opts.ts +16 -0
- package/templates/default/src/constants/form-opts/template-step-group-form-opts.ts +24 -0
- package/templates/default/src/constants/form-opts/template-test-case-form-opts.ts +39 -0
- package/templates/default/src/constants/form-opts/template-test-step-form-opts.ts +36 -0
- package/templates/default/src/constants/form-opts/test-case-form-opts.ts +43 -0
- package/templates/default/src/constants/form-opts/test-run-form-opts.ts +31 -0
- package/templates/default/src/constants/form-opts/test-suite-form-opts.ts +24 -0
- package/templates/default/src/hooks/use-toast.ts +187 -0
- package/templates/default/src/lib/bidirectional-sync.ts +432 -0
- package/templates/default/src/lib/database-sync.ts +531 -0
- package/templates/default/src/lib/environment-file-utils.ts +221 -0
- package/templates/default/src/lib/feature-file-generator.ts +411 -0
- package/templates/default/src/lib/gherkin-parser.ts +259 -0
- package/templates/default/src/lib/locator-group-file-utils.ts +370 -0
- package/templates/default/src/lib/metrics/metric-calculator.ts +613 -0
- package/templates/default/src/lib/module-hierarchy-builder.ts +205 -0
- package/templates/default/src/lib/path-helpers/module-path.ts +71 -0
- package/templates/default/src/lib/test-case-utils.ts +6 -0
- package/templates/default/src/lib/test-run/log-formatter.ts +83 -0
- package/templates/default/src/lib/test-run/process-manager.ts +191 -0
- package/templates/default/src/lib/test-run/report-parser.ts +316 -0
- package/templates/default/src/lib/test-run/test-run-executor.ts +144 -0
- package/templates/default/src/lib/test-run/winston-logger.ts +95 -0
- package/templates/default/src/lib/transformers/gherkin-converter.ts +42 -0
- package/templates/default/src/lib/transformers/key-to-icon-transformer.tsx +95 -0
- package/templates/default/src/lib/transformers/template-test-case-converter.ts +160 -0
- package/templates/default/src/lib/utils/node-param-validation.ts +81 -0
- package/templates/default/src/lib/utils/template-step-file-generator.ts +167 -0
- package/templates/default/src/lib/utils/template-step-file-manager-intelligent.ts +723 -0
- package/templates/default/src/lib/utils/template-step-file-manager.ts +166 -0
- package/templates/default/src/lib/utils.ts +31 -0
- package/templates/default/src/tests/config/environments/environments.json +14 -0
- package/templates/default/src/tests/config/executor/world.ts +41 -0
- package/templates/default/src/tests/executor.ts +80 -0
- package/templates/default/src/tests/hooks/hooks.ts +99 -0
- package/templates/default/src/tests/mapping/locator-map.json +1 -0
- package/templates/default/src/tests/steps/actions/click.step.ts +62 -0
- package/templates/default/src/tests/steps/actions/hover.step.ts +31 -0
- package/templates/default/src/tests/steps/actions/input.step.ts +149 -0
- package/templates/default/src/tests/steps/actions/navigation.step.ts +72 -0
- package/templates/default/src/tests/steps/actions/random_data.step.ts +146 -0
- package/templates/default/src/tests/steps/actions/store.step.ts +90 -0
- package/templates/default/src/tests/steps/actions/wait.step.ts +107 -0
- package/templates/default/src/tests/steps/validations/active_state_assertion.step.ts +34 -0
- package/templates/default/src/tests/steps/validations/navigation_assertion.step.ts +23 -0
- package/templates/default/src/tests/steps/validations/text_assertion.step.ts +111 -0
- package/templates/default/src/tests/steps/validations/visibility_assertion.step.ts +30 -0
- package/templates/default/src/tests/support/parameter-types.ts +12 -0
- package/templates/default/src/tests/utils/cache.util.ts +260 -0
- package/templates/default/src/tests/utils/cli.util.ts +177 -0
- package/templates/default/src/tests/utils/environment.util.ts +65 -0
- package/templates/default/src/tests/utils/locator.util.ts +248 -0
- package/templates/default/src/tests/utils/random-data.util.ts +45 -0
- package/templates/default/src/tests/utils/spawner.util.ts +617 -0
- package/templates/default/src/types/diagram/diagram.ts +34 -0
- package/templates/default/src/types/diagram/template-step.ts +11 -0
- package/templates/default/src/types/executor/browser.type.ts +1 -0
- package/templates/default/src/types/form/actionHandler.ts +6 -0
- package/templates/default/src/types/locator/locator.type.ts +11 -0
- package/templates/default/src/types/step/step.type.ts +1 -0
- package/templates/default/src/types/table/data-table.ts +6 -0
- package/templates/default/tailwind.config.ts +62 -0
- package/templates/default/tsconfig.json +28 -0
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef, useState } from 'react'
|
|
4
|
+
import { ScrollArea } from '@/components/ui/scroll-area'
|
|
5
|
+
import { Badge } from '@/components/ui/badge'
|
|
6
|
+
import { Alert, AlertDescription } from '@/components/ui/alert'
|
|
7
|
+
import { LoaderCircle, Wifi, WifiOff, CheckCircle, XCircle, Logs } from 'lucide-react'
|
|
8
|
+
import { cn } from '@/lib/utils'
|
|
9
|
+
import { getTestRunLogsAction, updateTestRunTestCaseStatusAction } from '@/actions/test-run/test-run-actions'
|
|
10
|
+
import { TestRunStatus } from '@prisma/client'
|
|
11
|
+
import { DownloadLogsButton } from './download-logs-button'
|
|
12
|
+
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'
|
|
13
|
+
|
|
14
|
+
interface LogMessage {
|
|
15
|
+
type: 'stdout' | 'stderr' | 'status'
|
|
16
|
+
message: string
|
|
17
|
+
timestamp: Date
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface LogViewerProps {
|
|
21
|
+
testRunId: string
|
|
22
|
+
status?: TestRunStatus
|
|
23
|
+
className?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
type ConnectionStatus = 'connecting' | 'connected' | 'disconnected' | 'error' | 'completed' | 'loading'
|
|
27
|
+
|
|
28
|
+
export function LogViewer({ testRunId, status, className }: LogViewerProps) {
|
|
29
|
+
const [logs, setLogs] = useState<LogMessage[]>([])
|
|
30
|
+
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('connecting')
|
|
31
|
+
const [error, setError] = useState<string | null>(null)
|
|
32
|
+
const scrollAreaRef = useRef<HTMLDivElement>(null)
|
|
33
|
+
const eventSourceRef = useRef<EventSource | null>(null)
|
|
34
|
+
const autoScrollRef = useRef(true)
|
|
35
|
+
const wasConnectedRef = useRef(false) // Track if we ever successfully connected
|
|
36
|
+
const shouldStopReconnectingRef = useRef(false) // Track if we should stop auto-reconnecting
|
|
37
|
+
|
|
38
|
+
// Load logs from database if test run is completed
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (status === TestRunStatus.COMPLETED || status === TestRunStatus.CANCELLED) {
|
|
41
|
+
queueMicrotask(() => setConnectionStatus('loading'))
|
|
42
|
+
getTestRunLogsAction(testRunId)
|
|
43
|
+
.then(response => {
|
|
44
|
+
if (response.error) {
|
|
45
|
+
setError(response.error)
|
|
46
|
+
setConnectionStatus('error')
|
|
47
|
+
} else {
|
|
48
|
+
const loadedLogs = (response.data as LogMessage[]) || []
|
|
49
|
+
// Convert timestamp strings to Date objects if needed
|
|
50
|
+
const parsedLogs = loadedLogs.map(log => ({
|
|
51
|
+
...log,
|
|
52
|
+
timestamp: log.timestamp instanceof Date ? log.timestamp : new Date(log.timestamp),
|
|
53
|
+
}))
|
|
54
|
+
setLogs(parsedLogs)
|
|
55
|
+
setConnectionStatus('completed')
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
.catch(err => {
|
|
59
|
+
console.error('[LogViewer] Error loading logs from database:', err)
|
|
60
|
+
setError('Failed to load logs from database')
|
|
61
|
+
setConnectionStatus('error')
|
|
62
|
+
})
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
}, [testRunId, status])
|
|
66
|
+
|
|
67
|
+
// Use SSE for running/queued test runs
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
// Skip SSE if test run is completed
|
|
70
|
+
if (status === TestRunStatus.COMPLETED || status === TestRunStatus.CANCELLED) {
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(`[LogViewer] Connecting to SSE endpoint for testRunId: ${testRunId}`)
|
|
75
|
+
// Create EventSource connection to SSE endpoint
|
|
76
|
+
const eventSource = new EventSource(`/api/test-runs/${testRunId}/logs`)
|
|
77
|
+
eventSourceRef.current = eventSource
|
|
78
|
+
|
|
79
|
+
// Handle connection open
|
|
80
|
+
eventSource.onopen = () => {
|
|
81
|
+
console.log(`[LogViewer] SSE connection opened for testRunId: ${testRunId}`)
|
|
82
|
+
wasConnectedRef.current = true
|
|
83
|
+
setConnectionStatus('connected')
|
|
84
|
+
setError(null)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle connection error
|
|
88
|
+
eventSource.onerror = err => {
|
|
89
|
+
const readyState = eventSource.readyState
|
|
90
|
+
|
|
91
|
+
if (readyState === EventSource.CONNECTING) {
|
|
92
|
+
// Still connecting, don't log error or set error state yet
|
|
93
|
+
// This is normal during initial connection attempts
|
|
94
|
+
// Allow EventSource to continue trying to connect
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (readyState === EventSource.CLOSED) {
|
|
99
|
+
console.log(`[LogViewer] SSE connection closed for testRunId: ${testRunId}`)
|
|
100
|
+
|
|
101
|
+
// If we never connected, it's likely a fatal error (404, 500, etc.)
|
|
102
|
+
// Close EventSource to prevent auto-reconnection
|
|
103
|
+
if (!wasConnectedRef.current) {
|
|
104
|
+
console.log(`[LogViewer] Fatal error: never connected, closing EventSource to prevent reconnection`)
|
|
105
|
+
shouldStopReconnectingRef.current = true
|
|
106
|
+
eventSource.close()
|
|
107
|
+
setConnectionStatus('error')
|
|
108
|
+
setError(
|
|
109
|
+
'Failed to connect to log stream. The test run may not be running or the process has ended. Please check the server logs for more details.',
|
|
110
|
+
)
|
|
111
|
+
} else {
|
|
112
|
+
// We were connected before, so this is a normal disconnection
|
|
113
|
+
setConnectionStatus('disconnected')
|
|
114
|
+
// Don't close EventSource here - let it try to reconnect if needed
|
|
115
|
+
// But if we've been told to stop reconnecting, close it
|
|
116
|
+
if (shouldStopReconnectingRef.current) {
|
|
117
|
+
eventSource.close()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
// readyState === EventSource.OPEN (unlikely to be an error) or unknown state
|
|
122
|
+
console.error(`[LogViewer] SSE error for testRunId: ${testRunId}`, err, 'readyState:', readyState)
|
|
123
|
+
setConnectionStatus('error')
|
|
124
|
+
setError(`Failed to connect to log stream (readyState: ${readyState}). Check server logs for details.`)
|
|
125
|
+
// For OPEN state errors, close to prevent reconnection attempts
|
|
126
|
+
shouldStopReconnectingRef.current = true
|
|
127
|
+
eventSource.close()
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Handle 'connected' event
|
|
132
|
+
eventSource.addEventListener('connected', (event: MessageEvent) => {
|
|
133
|
+
try {
|
|
134
|
+
if (!event.data) {
|
|
135
|
+
console.warn('[LogViewer] Received connected event with no data')
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
const data = JSON.parse(event.data)
|
|
139
|
+
console.log(`[LogViewer] Received connected event:`, data)
|
|
140
|
+
setLogs(prev => [
|
|
141
|
+
...prev,
|
|
142
|
+
{
|
|
143
|
+
type: 'status',
|
|
144
|
+
message: data.message || 'Connected to log stream',
|
|
145
|
+
timestamp: new Date(),
|
|
146
|
+
},
|
|
147
|
+
])
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('[LogViewer] Error parsing connected event:', error, 'event.data:', event.data)
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// Handle 'log' event (stdout/stderr)
|
|
154
|
+
eventSource.addEventListener('log', (event: MessageEvent) => {
|
|
155
|
+
try {
|
|
156
|
+
if (!event.data) {
|
|
157
|
+
console.warn('[LogViewer] Received log event with no data')
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
const data = JSON.parse(event.data)
|
|
161
|
+
// Only log first few events to avoid spam
|
|
162
|
+
if (logs.length < 5) {
|
|
163
|
+
console.log(`[LogViewer] Received log event:`, data)
|
|
164
|
+
}
|
|
165
|
+
setLogs(prev => [
|
|
166
|
+
...prev,
|
|
167
|
+
{
|
|
168
|
+
type: data.type || 'stdout',
|
|
169
|
+
message: data.message || '',
|
|
170
|
+
timestamp: new Date(),
|
|
171
|
+
},
|
|
172
|
+
])
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error('[LogViewer] Error parsing log event:', error, 'event.data:', event.data)
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
// Handle 'exit' event
|
|
179
|
+
eventSource.addEventListener('exit', (event: MessageEvent) => {
|
|
180
|
+
try {
|
|
181
|
+
if (!event.data) {
|
|
182
|
+
console.warn('[LogViewer] Received exit event with no data')
|
|
183
|
+
setConnectionStatus('completed')
|
|
184
|
+
eventSource.close()
|
|
185
|
+
// Dispatch custom event for TestRunHeader to listen to
|
|
186
|
+
window.dispatchEvent(
|
|
187
|
+
new CustomEvent('testrun:exit', {
|
|
188
|
+
detail: { testRunId },
|
|
189
|
+
}),
|
|
190
|
+
)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
const data = JSON.parse(event.data)
|
|
194
|
+
const exitCode = data.code
|
|
195
|
+
setLogs(prev => [
|
|
196
|
+
...prev,
|
|
197
|
+
{
|
|
198
|
+
type: 'status',
|
|
199
|
+
message: `Process exited with code ${exitCode}`,
|
|
200
|
+
timestamp: new Date(),
|
|
201
|
+
},
|
|
202
|
+
])
|
|
203
|
+
setConnectionStatus('completed')
|
|
204
|
+
eventSource.close()
|
|
205
|
+
// Dispatch custom event for TestRunHeader to listen to
|
|
206
|
+
window.dispatchEvent(
|
|
207
|
+
new CustomEvent('testrun:exit', {
|
|
208
|
+
detail: { testRunId },
|
|
209
|
+
}),
|
|
210
|
+
)
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error('[LogViewer] Error parsing exit event:', error, 'event.data:', event.data)
|
|
213
|
+
setConnectionStatus('completed')
|
|
214
|
+
eventSource.close()
|
|
215
|
+
// Dispatch custom event even on error so TestRunHeader can refresh
|
|
216
|
+
window.dispatchEvent(
|
|
217
|
+
new CustomEvent('testrun:exit', {
|
|
218
|
+
detail: { testRunId },
|
|
219
|
+
}),
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
|
|
224
|
+
// Handle 'error' event (from SSE stream, not the onerror handler)
|
|
225
|
+
eventSource.addEventListener('error', (event: MessageEvent) => {
|
|
226
|
+
try {
|
|
227
|
+
if (!event.data) {
|
|
228
|
+
console.warn('[LogViewer] Received error event with no data')
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
const data = JSON.parse(event.data)
|
|
232
|
+
const errorMessage = data.error || data.message || 'Unknown error'
|
|
233
|
+
|
|
234
|
+
setLogs(prev => [
|
|
235
|
+
...prev,
|
|
236
|
+
{
|
|
237
|
+
type: 'stderr',
|
|
238
|
+
message: `Error: ${errorMessage}`,
|
|
239
|
+
timestamp: new Date(),
|
|
240
|
+
},
|
|
241
|
+
])
|
|
242
|
+
setConnectionStatus('error')
|
|
243
|
+
|
|
244
|
+
// Check if this is a fatal error that should stop reconnection
|
|
245
|
+
// Fatal errors include: "Test run not found", "Test run has completed", "Internal server error"
|
|
246
|
+
const fatalErrorPatterns = [
|
|
247
|
+
'Test run not found',
|
|
248
|
+
'Test run has completed',
|
|
249
|
+
'Internal server error',
|
|
250
|
+
'Test run process not found',
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
const isFatalError = fatalErrorPatterns.some(pattern =>
|
|
254
|
+
errorMessage.toLowerCase().includes(pattern.toLowerCase())
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
if (isFatalError) {
|
|
258
|
+
console.log(`[LogViewer] Fatal error received, closing EventSource to prevent reconnection: ${errorMessage}`)
|
|
259
|
+
shouldStopReconnectingRef.current = true
|
|
260
|
+
eventSource.close()
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error('[LogViewer] Error parsing error event:', error, 'event.data:', event.data)
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
// Handle 'scenario::end' event - update test case status
|
|
268
|
+
eventSource.addEventListener('scenario::end', async (event: MessageEvent) => {
|
|
269
|
+
try {
|
|
270
|
+
if (!event.data) {
|
|
271
|
+
console.warn('[LogViewer] Received scenario::end event with no data')
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
const data = JSON.parse(event.data)
|
|
275
|
+
const { scenarioName, status, tracePath } = data
|
|
276
|
+
|
|
277
|
+
console.log(
|
|
278
|
+
`[LogViewer] Scenario ended: ${scenarioName} with status: ${status}${tracePath ? `, tracePath: ${tracePath}` : ''}`,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
// Update test case status in database
|
|
282
|
+
// This will gracefully handle test runs filtered by tags (no test cases)
|
|
283
|
+
const response = await updateTestRunTestCaseStatusAction(testRunId, scenarioName, status, tracePath)
|
|
284
|
+
if (response.error && response.status !== 200) {
|
|
285
|
+
// Only log as error if it's not a 200 status (which means it was skipped gracefully)
|
|
286
|
+
console.error('[LogViewer] Error updating test case status:', response.error)
|
|
287
|
+
} else if (response.status === 200) {
|
|
288
|
+
// Log success or graceful skip (200 status means it was handled correctly)
|
|
289
|
+
console.log(
|
|
290
|
+
`[LogViewer] ${response.message || `Successfully updated test case status for scenario: ${scenarioName}`}`,
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Also add to logs for visibility
|
|
295
|
+
setLogs(prev => [
|
|
296
|
+
...prev,
|
|
297
|
+
{
|
|
298
|
+
type: 'status',
|
|
299
|
+
message: `Scenario completed: ${scenarioName} - ${status}${tracePath ? ` (trace available)` : ''}`,
|
|
300
|
+
timestamp: new Date(),
|
|
301
|
+
},
|
|
302
|
+
])
|
|
303
|
+
} catch (error) {
|
|
304
|
+
console.error('[LogViewer] Error handling scenario::end event:', error, 'event.data:', event.data)
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
|
|
308
|
+
// Cleanup on unmount
|
|
309
|
+
return () => {
|
|
310
|
+
wasConnectedRef.current = false
|
|
311
|
+
shouldStopReconnectingRef.current = false
|
|
312
|
+
eventSource.close()
|
|
313
|
+
eventSourceRef.current = null
|
|
314
|
+
}
|
|
315
|
+
}, [testRunId, status]) // eslint-disable-line react-hooks/exhaustive-deps -- SSE must not depend on logs
|
|
316
|
+
|
|
317
|
+
// Auto-scroll to bottom when new logs arrive
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
if (autoScrollRef.current && scrollAreaRef.current) {
|
|
320
|
+
const scrollContainer = scrollAreaRef.current.querySelector('[data-radix-scroll-area-viewport]')
|
|
321
|
+
if (scrollContainer) {
|
|
322
|
+
scrollContainer.scrollTop = scrollContainer.scrollHeight
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}, [logs, logs.length])
|
|
326
|
+
|
|
327
|
+
// Handle manual scroll to detect user scrolling up
|
|
328
|
+
const handleScroll = () => {
|
|
329
|
+
if (scrollAreaRef.current) {
|
|
330
|
+
const scrollContainer = scrollAreaRef.current.querySelector('[data-radix-scroll-area-viewport]')
|
|
331
|
+
if (scrollContainer) {
|
|
332
|
+
const { scrollTop, scrollHeight, clientHeight } = scrollContainer
|
|
333
|
+
// If user scrolled up, disable auto-scroll
|
|
334
|
+
autoScrollRef.current = scrollTop + clientHeight >= scrollHeight - 10
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const getStatusIcon = () => {
|
|
340
|
+
switch (connectionStatus) {
|
|
341
|
+
case 'connecting':
|
|
342
|
+
return <LoaderCircle className="h-4 w-4 animate-spin" />
|
|
343
|
+
case 'connected':
|
|
344
|
+
return <Wifi className="h-4 w-4 text-green-500" />
|
|
345
|
+
case 'disconnected':
|
|
346
|
+
return <WifiOff className="h-4 w-4 text-gray-500" />
|
|
347
|
+
case 'error':
|
|
348
|
+
return <XCircle className="h-4 w-4 text-red-500" />
|
|
349
|
+
case 'completed':
|
|
350
|
+
return <CheckCircle className="h-4 w-4 text-blue-500" />
|
|
351
|
+
default:
|
|
352
|
+
return null
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const getStatusText = () => {
|
|
357
|
+
switch (connectionStatus) {
|
|
358
|
+
case 'connecting':
|
|
359
|
+
return 'Connecting...'
|
|
360
|
+
case 'connected':
|
|
361
|
+
return 'Connected'
|
|
362
|
+
case 'disconnected':
|
|
363
|
+
return 'Disconnected'
|
|
364
|
+
case 'error':
|
|
365
|
+
return 'Error'
|
|
366
|
+
case 'completed':
|
|
367
|
+
return 'Completed'
|
|
368
|
+
default:
|
|
369
|
+
return 'Unknown'
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return (
|
|
374
|
+
<Card>
|
|
375
|
+
<CardHeader>
|
|
376
|
+
<CardTitle className="flex items-center justify-between">
|
|
377
|
+
<span className="flex items-center gap-2">
|
|
378
|
+
<Logs className="h-6 w-6" />
|
|
379
|
+
Live Logs
|
|
380
|
+
</span>
|
|
381
|
+
{connectionStatus === 'completed' || connectionStatus === 'disconnected' ? (
|
|
382
|
+
<DownloadLogsButton testRunId={testRunId} />
|
|
383
|
+
) : null}
|
|
384
|
+
</CardTitle>
|
|
385
|
+
</CardHeader>
|
|
386
|
+
<CardContent>
|
|
387
|
+
<div className={cn('flex flex-col gap-2', className)}>
|
|
388
|
+
{/* Connection Status */}
|
|
389
|
+
<div className="flex items-center justify-between">
|
|
390
|
+
<Badge variant="outline" className="flex items-center gap-2">
|
|
391
|
+
{getStatusIcon()}
|
|
392
|
+
<span>{getStatusText()}</span>
|
|
393
|
+
</Badge>
|
|
394
|
+
<div className="flex items-center gap-2">
|
|
395
|
+
{logs.length > 0 && (
|
|
396
|
+
<Badge variant="outline" className="font-mono text-xs">
|
|
397
|
+
{logs.length} log{logs.length !== 1 ? 's' : ''}
|
|
398
|
+
</Badge>
|
|
399
|
+
)}
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
|
|
403
|
+
{/* Error Alert */}
|
|
404
|
+
{error && (
|
|
405
|
+
<Alert variant="destructive">
|
|
406
|
+
<AlertDescription>{error}</AlertDescription>
|
|
407
|
+
</Alert>
|
|
408
|
+
)}
|
|
409
|
+
|
|
410
|
+
{/* Log Display */}
|
|
411
|
+
<ScrollArea
|
|
412
|
+
ref={scrollAreaRef}
|
|
413
|
+
className="bg-muted/50 h-[600px] w-full rounded-md border p-4 font-mono text-sm"
|
|
414
|
+
onScroll={handleScroll}
|
|
415
|
+
>
|
|
416
|
+
{logs.length === 0 && (connectionStatus === 'connecting' || connectionStatus === 'loading') && (
|
|
417
|
+
<div className="flex items-center justify-center py-8 text-muted-foreground">
|
|
418
|
+
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
|
|
419
|
+
{connectionStatus === 'loading' ? 'Loading logs...' : 'Connecting to log stream...'}
|
|
420
|
+
</div>
|
|
421
|
+
)}
|
|
422
|
+
{logs.length === 0 && connectionStatus !== 'connecting' && connectionStatus !== 'loading' && (
|
|
423
|
+
<div className="flex items-center justify-center py-8 text-muted-foreground">No logs available</div>
|
|
424
|
+
)}
|
|
425
|
+
{logs.map((log, index) => (
|
|
426
|
+
<div
|
|
427
|
+
key={index}
|
|
428
|
+
className={cn(
|
|
429
|
+
'mb-1 flex items-start gap-2 whitespace-pre-wrap break-words',
|
|
430
|
+
log.type === 'stderr' && 'text-red-400',
|
|
431
|
+
log.type === 'stdout' && 'text-foreground',
|
|
432
|
+
log.type === 'status' && 'font-semibold text-blue-400',
|
|
433
|
+
)}
|
|
434
|
+
>
|
|
435
|
+
<span className="shrink-0 text-xs text-muted-foreground">{log.timestamp.toLocaleTimeString()}</span>
|
|
436
|
+
<span className="w-16 shrink-0 text-xs text-muted-foreground">[{log.type.toUpperCase()}]</span>
|
|
437
|
+
<span className="flex-1">{log.message}</span>
|
|
438
|
+
</div>
|
|
439
|
+
))}
|
|
440
|
+
</ScrollArea>
|
|
441
|
+
</div>
|
|
442
|
+
</CardContent>
|
|
443
|
+
</Card>
|
|
444
|
+
)
|
|
445
|
+
}
|