From 61403ceb9b7fe74e34ecc419f35711b89560b5b1 Mon Sep 17 00:00:00 2001 From: Mohan K Date: Wed, 23 Sep 2026 11:05:25 +0530 Subject: [PATCH] Update API Keys and fix frontend bugs --- app.json | 1 + app2.json | 1 + app3.json | 1 + app4.json | 1 + backend/controllers/analysisController.js | 5 +- backend/scripts/seedKeys.js | 11 +- backend/scripts/testApiKey.js | 18 + backend/scripts/testApiKeyTask.js | 44 + exec.json | 3 + frontend/src/AnalysisResultPage.jsx | 2076 ++++++++++----------- frontend/src/DashboardPage.jsx | 678 +++---- patch_backend.json | 10 + patch_compose.json | 3 + patch_frontend.json | 10 + patch_frontend_dns.json | 3 + patch_git.json | 3 + patch_loc.json | 3 + payload_backend_contabo.json | 13 + payload_db.json | 6 + payload_db_contabo.json | 6 + payload_envs.json | 4 + payload_envs_contabo.json | 4 + payload_envs_port.json | 4 + payload_frontend_contabo.json | 14 + payload_mongo.json | 10 + payload_v3.json | 11 + payload_v4.json | 12 + scratch/check_apps.js | 18 + 28 files changed, 1594 insertions(+), 1379 deletions(-) create mode 100644 app.json create mode 100644 app2.json create mode 100644 app3.json create mode 100644 app4.json create mode 100644 backend/scripts/testApiKey.js create mode 100644 backend/scripts/testApiKeyTask.js create mode 100644 exec.json create mode 100644 patch_backend.json create mode 100644 patch_compose.json create mode 100644 patch_frontend.json create mode 100644 patch_frontend_dns.json create mode 100644 patch_git.json create mode 100644 patch_loc.json create mode 100644 payload_backend_contabo.json create mode 100644 payload_db.json create mode 100644 payload_db_contabo.json create mode 100644 payload_envs.json create mode 100644 payload_envs_contabo.json create mode 100644 payload_envs_port.json create mode 100644 payload_frontend_contabo.json create mode 100644 payload_mongo.json create mode 100644 payload_v3.json create mode 100644 payload_v4.json create mode 100644 scratch/check_apps.js diff --git a/app.json b/app.json new file mode 100644 index 0000000..bdee2d6 --- /dev/null +++ b/app.json @@ -0,0 +1 @@ +{"uuid":"4uq3ri0vmsh82v0xb4zc28hr","name":"limelite-faceanalyzer","additional_networks_count":0,"additional_servers":[],"additional_servers_count":0,"base_directory":"\/","build_command":null,"build_pack":"dockercompose","compose_parsing_version":"5","config_hash":"9b4499e4d7561ba4e29d082c26859f01","container_present":false,"custom_docker_run_options":null,"custom_healthcheck_found":false,"custom_labels":"traefik.enable=true\ntraefik.http.middlewares.gzip.compress=true\ntraefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https\ntraefik.http.routers.http-0-4uq3ri0vmsh82v0xb4zc28hr.entryPoints=http\ntraefik.http.routers.http-0-4uq3ri0vmsh82v0xb4zc28hr.middlewares=gzip\ntraefik.http.routers.http-0-4uq3ri0vmsh82v0xb4zc28hr.rule=Host(`4uq3ri0vmsh82v0xb4zc28hr.91.203.135.27.sslip.io`) && PathPrefix(`\/`)\ntraefik.http.routers.http-0-4uq3ri0vmsh82v0xb4zc28hr.service=http-0-4uq3ri0vmsh82v0xb4zc28hr\ntraefik.http.services.http-0-4uq3ri0vmsh82v0xb4zc28hr.loadbalancer.server.port=80\ncaddy_0.encode=zstd gzip\ncaddy_0.handle_path.0_reverse_proxy={{upstreams 80}}\ncaddy_0.handle_path=\/*\ncaddy_0.header=-Server\ncaddy_0.try_files={path} \/index.html \/index.php\ncaddy_0=http:\/\/4uq3ri0vmsh82v0xb4zc28hr.91.203.135.27.sslip.io\ncaddy_ingress_network=coolify","custom_network_aliases":null,"custom_nginx_configuration":null,"deleted_at":null,"description":null,"destination":{"id":1,"name":"coolify","uuid":"aqyq2o4oubmgulktfusatwqm","network":"coolify","server_id":1,"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-08-19T05:37:20.000000Z","server":{"id":1,"uuid":"jrcjtobvncyba40v3vi6pzqe","name":"DigifoxServer-Prod","description":"","ip":"91.203.135.27","port":22,"user":"coolify-user","team_id":0,"private_key_id":0,"proxy":{"redirect_enabled":true,"last_saved_proxy_configuration":"name: coolify-proxy\nnetworks:\n coolify:\n external: true\nservices:\n traefik:\n container_name: coolify-proxy\n image: 'traefik:v3.7'\n restart: unless-stopped\n extra_hosts:\n - 'host.docker.internal:host-gateway'\n networks:\n - coolify\n ports:\n - '80:80'\n - '443:443'\n - '443:443\/udp'\n - '8080:8080'\n healthcheck:\n test: 'wget -qO- http:\/\/localhost:80\/ping || exit 1'\n interval: 4s\n timeout: 2s\n retries: 5\n volumes:\n - '\/var\/run\/docker.sock:\/var\/run\/docker.sock:ro'\n - '\/data\/coolify\/proxy\/:\/traefik'\n command:\n - '--ping=true'\n - '--ping.entrypoint=http'\n - '--api.dashboard=true'\n - '--entrypoints.http.address=:80'\n - '--entrypoints.https.address=:443'\n - '--entrypoints.http.http.encodequerysemicolons=true'\n - '--entryPoints.http.http2.maxConcurrentStreams=250'\n - '--entrypoints.https.http.encodequerysemicolons=true'\n - '--entryPoints.https.http2.maxConcurrentStreams=250'\n - '--entrypoints.https.http3'\n - '--providers.file.directory=\/traefik\/dynamic\/'\n - '--providers.file.watch=true'\n - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'\n - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http'\n - '--certificatesresolvers.letsencrypt.acme.storage=\/traefik\/acme.json'\n - '--api.insecure=false'\n - '--providers.docker=true'\n - '--providers.docker.exposedbydefault=false'\n labels:\n - traefik.enable=true\n - traefik.http.routers.traefik.entrypoints=http\n - traefik.http.routers.traefik.service=api@internal\n - traefik.http.services.traefik.loadbalancer.server.port=8080\n - coolify.managed=true\n - coolify.proxy=true","type":"TRAEFIK","status":"created","last_saved_settings":"3b2fb10ec80cfa819e51625efa249fe1","last_applied_settings":"3b2fb10ec80cfa819e51625efa249fe1"},"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-09-22T11:27:09.000000Z","unreachable_notification_sent":false,"unreachable_count":0,"high_disk_usage_notification_sent":false,"log_drain_notification_sent":false,"swarm_cluster":null,"validation_logs":null,"sentinel_updated_at":"2026-09-18 07:27:07","deleted_at":null,"ip_previous":null,"hetzner_server_id":null,"cloud_provider_token_id":null,"hetzner_server_status":null,"is_validating":false,"detected_traefik_version":null,"traefik_outdated_info":null,"server_metadata":{"os":"Ubuntu 22.04.5 LTS","arch":"x86_64","kernel":"5.15.0-187-generic","cpus":4,"memory_bytes":12239319040,"uptime_since":"2026-08-20 15:20:39","collected_at":"2026-08-20T13:37:08+00:00"},"vultr_instance_id":null,"vultr_instance_status":null,"digitalocean_droplet_id":null,"digitalocean_droplet_status":null,"sentinel_waiting_since":"2026-09-22T11:27:09.000000Z","is_coolify_host":false,"settings":{"id":2,"is_swarm_manager":false,"is_jump_server":false,"is_build_server":false,"is_reachable":true,"is_usable":true,"server_id":1,"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-09-22T11:27:04.000000Z","wildcard_domain":null,"is_cloudflare_tunnel":false,"is_logdrain_newrelic_enabled":false,"logdrain_newrelic_license_key":null,"logdrain_newrelic_base_uri":null,"is_logdrain_highlight_enabled":false,"logdrain_highlight_project_id":null,"is_logdrain_axiom_enabled":false,"logdrain_axiom_dataset_name":null,"logdrain_axiom_api_key":null,"is_swarm_worker":false,"is_logdrain_custom_enabled":false,"logdrain_custom_config":null,"logdrain_custom_config_parser":null,"concurrent_builds":2,"dynamic_timeout":3600,"force_disabled":false,"is_metrics_enabled":false,"generate_exact_labels":false,"force_docker_cleanup":true,"docker_cleanup_frequency":"0 0 * * *","docker_cleanup_threshold":80,"server_timezone":"UTC","delete_unused_volumes":false,"delete_unused_networks":false,"is_sentinel_enabled":true,"sentinel_token":"eyJpdiI6InhiRVYvTjcrY2grN293a0tWZkV1WFE9PSIsInZhbHVlIjoiaVFMZW5KTzY4QlZWYW82WmFnZmdBL0VwN3pJeEVRcjQzZ082T1NzT2xoMzVBVk9LQXJ4cXJkRUhZN0p2ZzcwQ3RFclg4WFlLOHVuTW0yU2pwcXR3aGc9PSIsIm1hYyI6IjdiMzFiOWU2MDhhN2RlNjhlZDJlYjRkMGQ3ZGU2YjlkMzI4MDY1ODk3YTY2NTgwZTdmOWM3Y2E0YjM3MTg3YzIiLCJ0YWciOiIifQ==","sentinel_metrics_refresh_rate_seconds":10,"sentinel_metrics_history_days":7,"sentinel_push_interval_seconds":60,"sentinel_custom_url":"https:\/\/deploy.digifox.live","server_disk_usage_notification_threshold":80,"is_sentinel_debug_enabled":false,"server_disk_usage_check_frequency":"0 23 * * *","is_terminal_enabled":true,"deployment_queue_limit":25,"disable_application_image_retention":false,"connection_timeout":10,"docker_version":"29.8.1","docker_version_checked_at":"2026-09-22T11:27:03.000000Z","compose_version":"5.5.1","compose_version_checked_at":"2026-09-22T11:27:04.000000Z","backup_compression_cpu_percentage":25}}},"destination_id":1,"destination_type":"App\\Models\\StandaloneDocker","docker_compose":null,"docker_compose_custom_build_command":null,"docker_compose_custom_start_command":null,"docker_compose_domains":null,"docker_compose_location":"\/docker-compose.yml","docker_compose_raw":null,"docker_registry_image_name":null,"docker_registry_image_tag":null,"dockerfile":null,"dockerfile_location":null,"dockerfile_target_build":null,"environment":{"id":23,"name":"production","project_id":17,"created_at":"2026-08-12T05:23:25.000000Z","updated_at":"2026-08-12T05:23:25.000000Z","description":null,"uuid":"ijga3o6m6arup69yohj2ecyw","project":{"id":17,"uuid":"kozt3dmpa63penhin84sb8r0","name":"limelite-smartmirror","description":"","team_id":0,"created_at":"2026-08-12T05:23:25.000000Z","updated_at":"2026-08-18T11:28:25.000000Z","icon_path":null,"icon_storage_type":null,"icon_s3_storage_id":null,"team":{"id":0,"name":"Root Team","description":"The root team","personal_team":true,"created_at":"2026-06-05T23:01:55.000000Z","updated_at":"2026-06-05T23:11:01.000000Z","show_boarding":false,"custom_server_limit":null,"is_mcp_server_enabled":true}}},"environment_id":23,"fqdn":"http:\/\/4uq3ri0vmsh82v0xb4zc28hr.91.203.135.27.sslip.io","git_branch":"master","git_commit_sha":"HEAD","git_full_url":null,"git_repository":"https:\/\/mohanki:48d38a221949649fdf1b470bbd037de9a5e6c564@git.digifox.live\/mohanki\/limelite_faceanalyzer.git","health_check_command":null,"health_check_enabled":false,"health_check_host":"localhost","health_check_interval":5,"health_check_method":"GET","health_check_path":"\/","health_check_port":null,"health_check_response_text":null,"health_check_retries":10,"health_check_return_code":200,"health_check_scheme":"http","health_check_start_period":5,"health_check_timeout":5,"health_check_type":"http","http_basic_auth_password":null,"http_basic_auth_username":null,"install_command":null,"is_http_basic_auth_enabled":false,"last_online_at":"2026-06-05 23:01:53","last_restart_at":null,"last_restart_type":null,"limits_cpu_shares":1024,"limits_cpus":"0","limits_cpuset":null,"limits_memory":"0","limits_memory_reservation":"0","limits_memory_swap":"0","limits_memory_swappiness":60,"manual_webhook_secret_bitbucket":"yvbVnb9T2y7Zru3Jskw1UODFEermE2E6VhQ2M6QM","manual_webhook_secret_gitea":"tO5bHwa6rGYE5l8zByJzYXxionKHLRPkLgDbCTFj","manual_webhook_secret_github":"wGAJtfx6YmuDZF2jv1KaVdbT7S79uMogT8njpo7B","manual_webhook_secret_gitlab":"YmGawM1NqN2hHiMbFm8QYTadhZkqtYZF0HqBByOf","max_restart_count":0,"noindex_domains":null,"ports_exposes":"80","ports_mappings":null,"post_deployment_command":null,"post_deployment_command_container":null,"pre_deployment_command":null,"pre_deployment_command_container":null,"preview_url_template":"{{pr_id}}.{{domain}}","private_key_id":null,"publish_directory":null,"redirect":"both","repository_project_id":null,"restart_count":0,"restart_limit_reached":false,"server_status":true,"settings":{"is_static":false,"is_git_submodules_enabled":true,"is_git_lfs_enabled":true,"is_auto_deploy_enabled":true,"is_force_https_enabled":true,"is_debug_enabled":false,"is_preview_deployments_enabled":false,"is_log_drain_enabled":false,"is_gpu_enabled":false,"gpu_driver":"nvidia","gpu_count":null,"gpu_device_ids":null,"gpu_options":null,"is_include_timestamps":false,"is_swarm_only_worker_nodes":true,"is_raw_compose_deployment_enabled":false,"is_build_server_enabled":false,"is_consistent_container_name_enabled":false,"is_gzip_enabled":true,"is_stripprefix_enabled":true,"connect_to_docker_network":false,"custom_internal_name":null,"is_container_label_escape_enabled":true,"is_env_sorting_enabled":false,"is_container_label_readonly_enabled":true,"is_preserve_repository_enabled":false,"disable_build_cache":false,"is_spa":false,"is_git_shallow_clone_enabled":true,"is_pr_deployments_public_enabled":false,"use_build_secrets":false,"stop_grace_period":null,"inject_build_args_to_dockerfile":true,"include_source_commit_in_build":false,"docker_images_to_keep":2},"source_id":null,"source_type":null,"start_command":null,"static_image":"nginx:alpine","status":"exited:unhealthy","swarm_placement_constraints":null,"swarm_replicas":1,"watch_paths":null,"created_at":"2026-09-22T11:20:53.000000Z","updated_at":"2026-09-22T11:25:19.000000Z"} \ No newline at end of file diff --git a/app2.json b/app2.json new file mode 100644 index 0000000..6f4b2b0 --- /dev/null +++ b/app2.json @@ -0,0 +1 @@ +{"uuid":"4uq3ri0vmsh82v0xb4zc28hr","name":"limelite-faceanalyzer","additional_networks_count":0,"additional_servers":[],"additional_servers_count":0,"base_directory":"\/","build_command":null,"build_pack":"dockercompose","compose_parsing_version":"5","config_hash":"9b4499e4d7561ba4e29d082c26859f01","container_present":false,"custom_docker_run_options":null,"custom_healthcheck_found":false,"custom_labels":"traefik.enable=true\ntraefik.http.middlewares.gzip.compress=true\ntraefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https\ntraefik.http.routers.http-0-4uq3ri0vmsh82v0xb4zc28hr.entryPoints=http\ntraefik.http.routers.http-0-4uq3ri0vmsh82v0xb4zc28hr.middlewares=gzip\ntraefik.http.routers.http-0-4uq3ri0vmsh82v0xb4zc28hr.rule=Host(`4uq3ri0vmsh82v0xb4zc28hr.91.203.135.27.sslip.io`) && PathPrefix(`\/`)\ntraefik.http.routers.http-0-4uq3ri0vmsh82v0xb4zc28hr.service=http-0-4uq3ri0vmsh82v0xb4zc28hr\ntraefik.http.services.http-0-4uq3ri0vmsh82v0xb4zc28hr.loadbalancer.server.port=80\ncaddy_0.encode=zstd gzip\ncaddy_0.handle_path.0_reverse_proxy={{upstreams 80}}\ncaddy_0.handle_path=\/*\ncaddy_0.header=-Server\ncaddy_0.try_files={path} \/index.html \/index.php\ncaddy_0=http:\/\/4uq3ri0vmsh82v0xb4zc28hr.91.203.135.27.sslip.io\ncaddy_ingress_network=coolify","custom_network_aliases":null,"custom_nginx_configuration":null,"deleted_at":null,"description":null,"destination":{"id":1,"name":"coolify","uuid":"aqyq2o4oubmgulktfusatwqm","network":"coolify","server_id":1,"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-08-19T05:37:20.000000Z","server":{"id":1,"uuid":"jrcjtobvncyba40v3vi6pzqe","name":"DigifoxServer-Prod","description":"","ip":"91.203.135.27","port":22,"user":"coolify-user","team_id":0,"private_key_id":0,"proxy":{"redirect_enabled":true,"last_saved_proxy_configuration":"name: coolify-proxy\nnetworks:\n coolify:\n external: true\nservices:\n traefik:\n container_name: coolify-proxy\n image: 'traefik:v3.7'\n restart: unless-stopped\n extra_hosts:\n - 'host.docker.internal:host-gateway'\n networks:\n - coolify\n ports:\n - '80:80'\n - '443:443'\n - '443:443\/udp'\n - '8080:8080'\n healthcheck:\n test: 'wget -qO- http:\/\/localhost:80\/ping || exit 1'\n interval: 4s\n timeout: 2s\n retries: 5\n volumes:\n - '\/var\/run\/docker.sock:\/var\/run\/docker.sock:ro'\n - '\/data\/coolify\/proxy\/:\/traefik'\n command:\n - '--ping=true'\n - '--ping.entrypoint=http'\n - '--api.dashboard=true'\n - '--entrypoints.http.address=:80'\n - '--entrypoints.https.address=:443'\n - '--entrypoints.http.http.encodequerysemicolons=true'\n - '--entryPoints.http.http2.maxConcurrentStreams=250'\n - '--entrypoints.https.http.encodequerysemicolons=true'\n - '--entryPoints.https.http2.maxConcurrentStreams=250'\n - '--entrypoints.https.http3'\n - '--providers.file.directory=\/traefik\/dynamic\/'\n - '--providers.file.watch=true'\n - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'\n - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http'\n - '--certificatesresolvers.letsencrypt.acme.storage=\/traefik\/acme.json'\n - '--api.insecure=false'\n - '--providers.docker=true'\n - '--providers.docker.exposedbydefault=false'\n labels:\n - traefik.enable=true\n - traefik.http.routers.traefik.entrypoints=http\n - traefik.http.routers.traefik.service=api@internal\n - traefik.http.services.traefik.loadbalancer.server.port=8080\n - coolify.managed=true\n - coolify.proxy=true","type":"TRAEFIK","status":"starting","last_saved_settings":"3b2fb10ec80cfa819e51625efa249fe1","last_applied_settings":"3b2fb10ec80cfa819e51625efa249fe1"},"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-09-22T11:29:13.000000Z","unreachable_notification_sent":false,"unreachable_count":0,"high_disk_usage_notification_sent":false,"log_drain_notification_sent":false,"swarm_cluster":null,"validation_logs":null,"sentinel_updated_at":"2026-09-18 07:29:10","deleted_at":null,"ip_previous":null,"hetzner_server_id":null,"cloud_provider_token_id":null,"hetzner_server_status":null,"is_validating":false,"detected_traefik_version":null,"traefik_outdated_info":null,"server_metadata":{"os":"Ubuntu 22.04.5 LTS","arch":"x86_64","kernel":"5.15.0-187-generic","cpus":4,"memory_bytes":12239319040,"uptime_since":"2026-08-20 15:20:39","collected_at":"2026-08-20T13:37:08+00:00"},"vultr_instance_id":null,"vultr_instance_status":null,"digitalocean_droplet_id":null,"digitalocean_droplet_status":null,"sentinel_waiting_since":"2026-09-22T11:29:11.000000Z","is_coolify_host":false,"settings":{"id":2,"is_swarm_manager":false,"is_jump_server":false,"is_build_server":false,"is_reachable":true,"is_usable":true,"server_id":1,"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-09-22T11:29:20.000000Z","wildcard_domain":null,"is_cloudflare_tunnel":false,"is_logdrain_newrelic_enabled":false,"logdrain_newrelic_license_key":null,"logdrain_newrelic_base_uri":null,"is_logdrain_highlight_enabled":false,"logdrain_highlight_project_id":null,"is_logdrain_axiom_enabled":false,"logdrain_axiom_dataset_name":null,"logdrain_axiom_api_key":null,"is_swarm_worker":false,"is_logdrain_custom_enabled":false,"logdrain_custom_config":null,"logdrain_custom_config_parser":null,"concurrent_builds":2,"dynamic_timeout":3600,"force_disabled":false,"is_metrics_enabled":false,"generate_exact_labels":false,"force_docker_cleanup":true,"docker_cleanup_frequency":"0 0 * * *","docker_cleanup_threshold":80,"server_timezone":"UTC","delete_unused_volumes":false,"delete_unused_networks":false,"is_sentinel_enabled":true,"sentinel_token":"eyJpdiI6InhiRVYvTjcrY2grN293a0tWZkV1WFE9PSIsInZhbHVlIjoiaVFMZW5KTzY4QlZWYW82WmFnZmdBL0VwN3pJeEVRcjQzZ082T1NzT2xoMzVBVk9LQXJ4cXJkRUhZN0p2ZzcwQ3RFclg4WFlLOHVuTW0yU2pwcXR3aGc9PSIsIm1hYyI6IjdiMzFiOWU2MDhhN2RlNjhlZDJlYjRkMGQ3ZGU2YjlkMzI4MDY1ODk3YTY2NTgwZTdmOWM3Y2E0YjM3MTg3YzIiLCJ0YWciOiIifQ==","sentinel_metrics_refresh_rate_seconds":10,"sentinel_metrics_history_days":7,"sentinel_push_interval_seconds":60,"sentinel_custom_url":"https:\/\/deploy.digifox.live","server_disk_usage_notification_threshold":80,"is_sentinel_debug_enabled":false,"server_disk_usage_check_frequency":"0 23 * * *","is_terminal_enabled":true,"deployment_queue_limit":25,"disable_application_image_retention":false,"connection_timeout":10,"docker_version":"29.8.1","docker_version_checked_at":"2026-09-22T11:29:20.000000Z","compose_version":"5.5.1","compose_version_checked_at":"2026-09-22T11:29:05.000000Z","backup_compression_cpu_percentage":25}}},"destination_id":1,"destination_type":"App\\Models\\StandaloneDocker","docker_compose":null,"docker_compose_custom_build_command":null,"docker_compose_custom_start_command":null,"docker_compose_domains":null,"docker_compose_location":"\/docker-compose.yaml","docker_compose_raw":null,"docker_registry_image_name":null,"docker_registry_image_tag":null,"dockerfile":null,"dockerfile_location":null,"dockerfile_target_build":null,"environment":{"id":23,"name":"production","project_id":17,"created_at":"2026-08-12T05:23:25.000000Z","updated_at":"2026-08-12T05:23:25.000000Z","description":null,"uuid":"ijga3o6m6arup69yohj2ecyw","project":{"id":17,"uuid":"kozt3dmpa63penhin84sb8r0","name":"limelite-smartmirror","description":"","team_id":0,"created_at":"2026-08-12T05:23:25.000000Z","updated_at":"2026-08-18T11:28:25.000000Z","icon_path":null,"icon_storage_type":null,"icon_s3_storage_id":null,"team":{"id":0,"name":"Root Team","description":"The root team","personal_team":true,"created_at":"2026-06-05T23:01:55.000000Z","updated_at":"2026-06-05T23:11:01.000000Z","show_boarding":false,"custom_server_limit":null,"is_mcp_server_enabled":true}}},"environment_id":23,"fqdn":"http:\/\/4uq3ri0vmsh82v0xb4zc28hr.91.203.135.27.sslip.io","git_branch":"master","git_commit_sha":"HEAD","git_full_url":null,"git_repository":"https:\/\/mohanki:48d38a221949649fdf1b470bbd037de9a5e6c564@git.digifox.live\/mohanki\/limelite_faceanalyzer.git","health_check_command":null,"health_check_enabled":false,"health_check_host":"localhost","health_check_interval":5,"health_check_method":"GET","health_check_path":"\/","health_check_port":null,"health_check_response_text":null,"health_check_retries":10,"health_check_return_code":200,"health_check_scheme":"http","health_check_start_period":5,"health_check_timeout":5,"health_check_type":"http","http_basic_auth_password":null,"http_basic_auth_username":null,"install_command":null,"is_http_basic_auth_enabled":false,"last_online_at":"2026-06-05 23:01:53","last_restart_at":null,"last_restart_type":null,"limits_cpu_shares":1024,"limits_cpus":"0","limits_cpuset":null,"limits_memory":"0","limits_memory_reservation":"0","limits_memory_swap":"0","limits_memory_swappiness":60,"manual_webhook_secret_bitbucket":"yvbVnb9T2y7Zru3Jskw1UODFEermE2E6VhQ2M6QM","manual_webhook_secret_gitea":"tO5bHwa6rGYE5l8zByJzYXxionKHLRPkLgDbCTFj","manual_webhook_secret_github":"wGAJtfx6YmuDZF2jv1KaVdbT7S79uMogT8njpo7B","manual_webhook_secret_gitlab":"YmGawM1NqN2hHiMbFm8QYTadhZkqtYZF0HqBByOf","max_restart_count":0,"noindex_domains":null,"ports_exposes":"80","ports_mappings":null,"post_deployment_command":null,"post_deployment_command_container":null,"pre_deployment_command":null,"pre_deployment_command_container":null,"preview_url_template":"{{pr_id}}.{{domain}}","private_key_id":null,"publish_directory":null,"redirect":"both","repository_project_id":null,"restart_count":0,"restart_limit_reached":false,"server_status":true,"settings":{"is_static":false,"is_git_submodules_enabled":true,"is_git_lfs_enabled":true,"is_auto_deploy_enabled":true,"is_force_https_enabled":true,"is_debug_enabled":false,"is_preview_deployments_enabled":false,"is_log_drain_enabled":false,"is_gpu_enabled":false,"gpu_driver":"nvidia","gpu_count":null,"gpu_device_ids":null,"gpu_options":null,"is_include_timestamps":false,"is_swarm_only_worker_nodes":true,"is_raw_compose_deployment_enabled":false,"is_build_server_enabled":false,"is_consistent_container_name_enabled":false,"is_gzip_enabled":true,"is_stripprefix_enabled":true,"connect_to_docker_network":false,"custom_internal_name":null,"is_container_label_escape_enabled":true,"is_env_sorting_enabled":false,"is_container_label_readonly_enabled":true,"is_preserve_repository_enabled":false,"disable_build_cache":false,"is_spa":false,"is_git_shallow_clone_enabled":true,"is_pr_deployments_public_enabled":false,"use_build_secrets":false,"stop_grace_period":null,"inject_build_args_to_dockerfile":true,"include_source_commit_in_build":false,"docker_images_to_keep":2},"source_id":null,"source_type":null,"start_command":null,"static_image":"nginx:alpine","status":"exited:unhealthy","swarm_placement_constraints":null,"swarm_replicas":1,"watch_paths":null,"created_at":"2026-09-22T11:20:53.000000Z","updated_at":"2026-09-22T11:29:16.000000Z"} \ No newline at end of file diff --git a/app3.json b/app3.json new file mode 100644 index 0000000..700f21c --- /dev/null +++ b/app3.json @@ -0,0 +1 @@ +{"uuid":"4uq3ri0vmsh82v0xb4zc28hr","name":"limelite-faceanalyzer","additional_networks_count":0,"additional_servers":[],"additional_servers_count":0,"base_directory":"\/","build_command":null,"build_pack":"dockercompose","compose_parsing_version":"5","config_hash":"9b4499e4d7561ba4e29d082c26859f01","container_present":false,"custom_docker_run_options":null,"custom_healthcheck_found":false,"custom_labels":"dHJhZWZpay5lbmFibGU9dHJ1ZQp0cmFlZmlrLmh0dHAubWlkZGxld2FyZXMuZ3ppcC5jb21wcmVzcz10cnVlCnRyYWVmaWsuaHR0cC5taWRkbGV3YXJlcy5yZWRpcmVjdC10by1odHRwcy5yZWRpcmVjdHNjaGVtZS5zY2hlbWU9aHR0cHMKdHJhZWZpay5odHRwLnJvdXRlcnMuaHR0cC0wLTR1cTNyaTB2bXNoODJ2MHhiNHpjMjhoci5lbnRyeVBvaW50cz1odHRwCnRyYWVmaWsuaHR0cC5yb3V0ZXJzLmh0dHAtMC00dXEzcmkwdm1zaDgydjB4YjR6YzI4aHIubWlkZGxld2FyZXM9Z3ppcAp0cmFlZmlrLmh0dHAucm91dGVycy5odHRwLTAtNHVxM3JpMHZtc2g4MnYweGI0emMyOGhyLnJ1bGU9SG9zdChgNHVxM3JpMHZtc2g4MnYweGI0emMyOGhyLjkxLjIwMy4xMzUuMjcuc3NsaXAuaW9gKSAmJiBQYXRoUHJlZml4KGAvYCkKdHJhZWZpay5odHRwLnJvdXRlcnMuaHR0cC0wLTR1cTNyaTB2bXNoODJ2MHhiNHpjMjhoci5zZXJ2aWNlPWh0dHAtMC00dXEzcmkwdm1zaDgydjB4YjR6YzI4aHIKdHJhZWZpay5odHRwLnNlcnZpY2VzLmh0dHAtMC00dXEzcmkwdm1zaDgydjB4YjR6YzI4aHIubG9hZGJhbGFuY2VyLnNlcnZlci5wb3J0PTgwCmNhZGR5XzAuZW5jb2RlPXpzdGQgZ3ppcApjYWRkeV8wLmhhbmRsZV9wYXRoLjBfcmV2ZXJzZV9wcm94eT17e3Vwc3RyZWFtcyA4MH19CmNhZGR5XzAuaGFuZGxlX3BhdGg9LyoKY2FkZHlfMC5oZWFkZXI9LVNlcnZlcgpjYWRkeV8wLnRyeV9maWxlcz17cGF0aH0gL2luZGV4Lmh0bWwgL2luZGV4LnBocApjYWRkeV8wPWh0dHA6Ly80dXEzcmkwdm1zaDgydjB4YjR6YzI4aHIuOTEuMjAzLjEzNS4yNy5zc2xpcC5pbwpjYWRkeV9pbmdyZXNzX25ldHdvcms9Y29vbGlmeQ==","custom_network_aliases":null,"custom_nginx_configuration":null,"deleted_at":null,"description":null,"destination":{"id":1,"name":"coolify","uuid":"aqyq2o4oubmgulktfusatwqm","network":"coolify","server_id":1,"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-08-19T05:37:20.000000Z","server":{"id":1,"uuid":"jrcjtobvncyba40v3vi6pzqe","name":"DigifoxServer-Prod","description":"","ip":"91.203.135.27","port":22,"user":"coolify-user","team_id":0,"private_key_id":0,"proxy":{"redirect_enabled":true,"last_saved_proxy_configuration":"name: coolify-proxy\nnetworks:\n coolify:\n external: true\nservices:\n traefik:\n container_name: coolify-proxy\n image: 'traefik:v3.7'\n restart: unless-stopped\n extra_hosts:\n - 'host.docker.internal:host-gateway'\n networks:\n - coolify\n ports:\n - '80:80'\n - '443:443'\n - '443:443\/udp'\n - '8080:8080'\n healthcheck:\n test: 'wget -qO- http:\/\/localhost:80\/ping || exit 1'\n interval: 4s\n timeout: 2s\n retries: 5\n volumes:\n - '\/var\/run\/docker.sock:\/var\/run\/docker.sock:ro'\n - '\/data\/coolify\/proxy\/:\/traefik'\n command:\n - '--ping=true'\n - '--ping.entrypoint=http'\n - '--api.dashboard=true'\n - '--entrypoints.http.address=:80'\n - '--entrypoints.https.address=:443'\n - '--entrypoints.http.http.encodequerysemicolons=true'\n - '--entryPoints.http.http2.maxConcurrentStreams=250'\n - '--entrypoints.https.http.encodequerysemicolons=true'\n - '--entryPoints.https.http2.maxConcurrentStreams=250'\n - '--entrypoints.https.http3'\n - '--providers.file.directory=\/traefik\/dynamic\/'\n - '--providers.file.watch=true'\n - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'\n - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http'\n - '--certificatesresolvers.letsencrypt.acme.storage=\/traefik\/acme.json'\n - '--api.insecure=false'\n - '--providers.docker=true'\n - '--providers.docker.exposedbydefault=false'\n labels:\n - traefik.enable=true\n - traefik.http.routers.traefik.entrypoints=http\n - traefik.http.routers.traefik.service=api@internal\n - traefik.http.services.traefik.loadbalancer.server.port=8080\n - coolify.managed=true\n - coolify.proxy=true","type":"TRAEFIK","status":"created","last_saved_settings":"3b2fb10ec80cfa819e51625efa249fe1","last_applied_settings":"3b2fb10ec80cfa819e51625efa249fe1"},"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-09-22T11:36:08.000000Z","unreachable_notification_sent":false,"unreachable_count":0,"high_disk_usage_notification_sent":false,"log_drain_notification_sent":false,"swarm_cluster":null,"validation_logs":null,"sentinel_updated_at":"2026-09-18 07:36:07","deleted_at":null,"ip_previous":null,"hetzner_server_id":null,"cloud_provider_token_id":null,"hetzner_server_status":null,"is_validating":false,"detected_traefik_version":null,"traefik_outdated_info":null,"server_metadata":{"os":"Ubuntu 22.04.5 LTS","arch":"x86_64","kernel":"5.15.0-187-generic","cpus":4,"memory_bytes":12239319040,"uptime_since":"2026-08-20 15:20:39","collected_at":"2026-08-20T13:37:08+00:00"},"vultr_instance_id":null,"vultr_instance_status":null,"digitalocean_droplet_id":null,"digitalocean_droplet_status":null,"sentinel_waiting_since":"2026-09-22T11:36:08.000000Z","is_coolify_host":false,"settings":{"id":2,"is_swarm_manager":false,"is_jump_server":false,"is_build_server":false,"is_reachable":true,"is_usable":true,"server_id":1,"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-09-22T11:36:05.000000Z","wildcard_domain":null,"is_cloudflare_tunnel":false,"is_logdrain_newrelic_enabled":false,"logdrain_newrelic_license_key":null,"logdrain_newrelic_base_uri":null,"is_logdrain_highlight_enabled":false,"logdrain_highlight_project_id":null,"is_logdrain_axiom_enabled":false,"logdrain_axiom_dataset_name":null,"logdrain_axiom_api_key":null,"is_swarm_worker":false,"is_logdrain_custom_enabled":false,"logdrain_custom_config":null,"logdrain_custom_config_parser":null,"concurrent_builds":2,"dynamic_timeout":3600,"force_disabled":false,"is_metrics_enabled":false,"generate_exact_labels":false,"force_docker_cleanup":true,"docker_cleanup_frequency":"0 0 * * *","docker_cleanup_threshold":80,"server_timezone":"UTC","delete_unused_volumes":false,"delete_unused_networks":false,"is_sentinel_enabled":true,"sentinel_token":"eyJpdiI6InhiRVYvTjcrY2grN293a0tWZkV1WFE9PSIsInZhbHVlIjoiaVFMZW5KTzY4QlZWYW82WmFnZmdBL0VwN3pJeEVRcjQzZ082T1NzT2xoMzVBVk9LQXJ4cXJkRUhZN0p2ZzcwQ3RFclg4WFlLOHVuTW0yU2pwcXR3aGc9PSIsIm1hYyI6IjdiMzFiOWU2MDhhN2RlNjhlZDJlYjRkMGQ3ZGU2YjlkMzI4MDY1ODk3YTY2NTgwZTdmOWM3Y2E0YjM3MTg3YzIiLCJ0YWciOiIifQ==","sentinel_metrics_refresh_rate_seconds":10,"sentinel_metrics_history_days":7,"sentinel_push_interval_seconds":60,"sentinel_custom_url":"https:\/\/deploy.digifox.live","server_disk_usage_notification_threshold":80,"is_sentinel_debug_enabled":false,"server_disk_usage_check_frequency":"0 23 * * *","is_terminal_enabled":true,"deployment_queue_limit":25,"disable_application_image_retention":false,"connection_timeout":10,"docker_version":"29.8.1","docker_version_checked_at":"2026-09-22T11:36:04.000000Z","compose_version":"5.5.1","compose_version_checked_at":"2026-09-22T11:36:05.000000Z","backup_compression_cpu_percentage":25}}},"destination_id":1,"destination_type":"App\\Models\\StandaloneDocker","docker_compose":null,"docker_compose_custom_build_command":null,"docker_compose_custom_start_command":null,"docker_compose_domains":null,"docker_compose_location":"\/docker-compose.yaml","docker_compose_raw":null,"docker_registry_image_name":null,"docker_registry_image_tag":null,"dockerfile":null,"dockerfile_location":null,"dockerfile_target_build":null,"environment":{"id":23,"name":"production","project_id":17,"created_at":"2026-08-12T05:23:25.000000Z","updated_at":"2026-08-12T05:23:25.000000Z","description":null,"uuid":"ijga3o6m6arup69yohj2ecyw","project":{"id":17,"uuid":"kozt3dmpa63penhin84sb8r0","name":"limelite-smartmirror","description":"","team_id":0,"created_at":"2026-08-12T05:23:25.000000Z","updated_at":"2026-08-18T11:28:25.000000Z","icon_path":null,"icon_storage_type":null,"icon_s3_storage_id":null,"team":{"id":0,"name":"Root Team","description":"The root team","personal_team":true,"created_at":"2026-06-05T23:01:55.000000Z","updated_at":"2026-06-05T23:11:01.000000Z","show_boarding":false,"custom_server_limit":null,"is_mcp_server_enabled":true}}},"environment_id":23,"fqdn":null,"git_branch":"master","git_commit_sha":"HEAD","git_full_url":null,"git_repository":"https:\/\/mohanki:48d38a221949649fdf1b470bbd037de9a5e6c564@git.digifox.live\/mohanki\/limelite_faceanalyzer.git","health_check_command":null,"health_check_enabled":false,"health_check_host":"localhost","health_check_interval":5,"health_check_method":"GET","health_check_path":"\/","health_check_port":null,"health_check_response_text":null,"health_check_retries":10,"health_check_return_code":200,"health_check_scheme":"http","health_check_start_period":5,"health_check_timeout":5,"health_check_type":"http","http_basic_auth_password":null,"http_basic_auth_username":null,"install_command":null,"is_http_basic_auth_enabled":false,"last_online_at":"2026-06-05 23:01:53","last_restart_at":null,"last_restart_type":null,"limits_cpu_shares":1024,"limits_cpus":"0","limits_cpuset":null,"limits_memory":"0","limits_memory_reservation":"0","limits_memory_swap":"0","limits_memory_swappiness":60,"manual_webhook_secret_bitbucket":"yvbVnb9T2y7Zru3Jskw1UODFEermE2E6VhQ2M6QM","manual_webhook_secret_gitea":"tO5bHwa6rGYE5l8zByJzYXxionKHLRPkLgDbCTFj","manual_webhook_secret_github":"wGAJtfx6YmuDZF2jv1KaVdbT7S79uMogT8njpo7B","manual_webhook_secret_gitlab":"YmGawM1NqN2hHiMbFm8QYTadhZkqtYZF0HqBByOf","max_restart_count":0,"noindex_domains":null,"ports_exposes":"80","ports_mappings":null,"post_deployment_command":null,"post_deployment_command_container":null,"pre_deployment_command":null,"pre_deployment_command_container":null,"preview_url_template":"{{pr_id}}.{{domain}}","private_key_id":null,"publish_directory":null,"redirect":"both","repository_project_id":null,"restart_count":0,"restart_limit_reached":false,"server_status":true,"settings":{"is_static":false,"is_git_submodules_enabled":true,"is_git_lfs_enabled":true,"is_auto_deploy_enabled":true,"is_force_https_enabled":true,"is_debug_enabled":false,"is_preview_deployments_enabled":false,"is_log_drain_enabled":false,"is_gpu_enabled":false,"gpu_driver":"nvidia","gpu_count":null,"gpu_device_ids":null,"gpu_options":null,"is_include_timestamps":false,"is_swarm_only_worker_nodes":true,"is_raw_compose_deployment_enabled":false,"is_build_server_enabled":false,"is_consistent_container_name_enabled":false,"is_gzip_enabled":true,"is_stripprefix_enabled":true,"connect_to_docker_network":false,"custom_internal_name":null,"is_container_label_escape_enabled":true,"is_env_sorting_enabled":false,"is_container_label_readonly_enabled":true,"is_preserve_repository_enabled":false,"disable_build_cache":false,"is_spa":false,"is_git_shallow_clone_enabled":true,"is_pr_deployments_public_enabled":false,"use_build_secrets":false,"stop_grace_period":null,"inject_build_args_to_dockerfile":true,"include_source_commit_in_build":false,"docker_images_to_keep":2},"source_id":null,"source_type":null,"start_command":null,"static_image":"nginx:alpine","status":"exited:unhealthy","swarm_placement_constraints":null,"swarm_replicas":1,"watch_paths":null,"created_at":"2026-09-22T11:20:53.000000Z","updated_at":"2026-09-22T11:35:14.000000Z"} \ No newline at end of file diff --git a/app4.json b/app4.json new file mode 100644 index 0000000..7207870 --- /dev/null +++ b/app4.json @@ -0,0 +1 @@ +{"uuid":"vn0t5yt9sbzirnd0qjlmte4j","name":"limelite-faceanalyzer-v2","additional_networks_count":0,"additional_servers":[],"additional_servers_count":0,"base_directory":"\/","build_command":null,"build_pack":"dockercompose","compose_parsing_version":"5","config_hash":"a72f07ac4f3e56c3cf98290f152a8e06","container_present":null,"custom_docker_run_options":null,"custom_healthcheck_found":false,"custom_labels":"traefik.enable=true\ntraefik.http.middlewares.gzip.compress=true\ntraefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https\ntraefik.http.routers.http-0-vn0t5yt9sbzirnd0qjlmte4j.entryPoints=http\ntraefik.http.routers.http-0-vn0t5yt9sbzirnd0qjlmte4j.middlewares=gzip\ntraefik.http.routers.http-0-vn0t5yt9sbzirnd0qjlmte4j.rule=Host(`vn0t5yt9sbzirnd0qjlmte4j.91.203.135.27.sslip.io`) && PathPrefix(`\/`)\ntraefik.http.routers.http-0-vn0t5yt9sbzirnd0qjlmte4j.service=http-0-vn0t5yt9sbzirnd0qjlmte4j\ntraefik.http.services.http-0-vn0t5yt9sbzirnd0qjlmte4j.loadbalancer.server.port=80\ncaddy_0.encode=zstd gzip\ncaddy_0.handle_path.0_reverse_proxy={{upstreams 80}}\ncaddy_0.handle_path=\/*\ncaddy_0.header=-Server\ncaddy_0.try_files={path} \/index.html \/index.php\ncaddy_0=http:\/\/vn0t5yt9sbzirnd0qjlmte4j.91.203.135.27.sslip.io\ncaddy_ingress_network=coolify","custom_network_aliases":null,"custom_nginx_configuration":null,"deleted_at":null,"description":null,"destination":{"id":1,"name":"coolify","uuid":"aqyq2o4oubmgulktfusatwqm","network":"coolify","server_id":1,"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-08-19T05:37:20.000000Z","server":{"id":1,"uuid":"jrcjtobvncyba40v3vi6pzqe","name":"DigifoxServer-Prod","description":"","ip":"91.203.135.27","port":22,"user":"coolify-user","team_id":0,"private_key_id":0,"proxy":{"redirect_enabled":true,"last_saved_proxy_configuration":"name: coolify-proxy\nnetworks:\n coolify:\n external: true\nservices:\n traefik:\n container_name: coolify-proxy\n image: 'traefik:v3.7'\n restart: unless-stopped\n extra_hosts:\n - 'host.docker.internal:host-gateway'\n networks:\n - coolify\n ports:\n - '80:80'\n - '443:443'\n - '443:443\/udp'\n - '8080:8080'\n healthcheck:\n test: 'wget -qO- http:\/\/localhost:80\/ping || exit 1'\n interval: 4s\n timeout: 2s\n retries: 5\n volumes:\n - '\/var\/run\/docker.sock:\/var\/run\/docker.sock:ro'\n - '\/data\/coolify\/proxy\/:\/traefik'\n command:\n - '--ping=true'\n - '--ping.entrypoint=http'\n - '--api.dashboard=true'\n - '--entrypoints.http.address=:80'\n - '--entrypoints.https.address=:443'\n - '--entrypoints.http.http.encodequerysemicolons=true'\n - '--entryPoints.http.http2.maxConcurrentStreams=250'\n - '--entrypoints.https.http.encodequerysemicolons=true'\n - '--entryPoints.https.http2.maxConcurrentStreams=250'\n - '--entrypoints.https.http3'\n - '--providers.file.directory=\/traefik\/dynamic\/'\n - '--providers.file.watch=true'\n - '--certificatesresolvers.letsencrypt.acme.httpchallenge=true'\n - '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http'\n - '--certificatesresolvers.letsencrypt.acme.storage=\/traefik\/acme.json'\n - '--api.insecure=false'\n - '--providers.docker=true'\n - '--providers.docker.exposedbydefault=false'\n labels:\n - traefik.enable=true\n - traefik.http.routers.traefik.entrypoints=http\n - traefik.http.routers.traefik.service=api@internal\n - traefik.http.services.traefik.loadbalancer.server.port=8080\n - coolify.managed=true\n - coolify.proxy=true","type":"TRAEFIK","status":"created","last_saved_settings":"3b2fb10ec80cfa819e51625efa249fe1","last_applied_settings":"3b2fb10ec80cfa819e51625efa249fe1"},"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-09-22T11:42:08.000000Z","unreachable_notification_sent":false,"unreachable_count":0,"high_disk_usage_notification_sent":false,"log_drain_notification_sent":false,"swarm_cluster":null,"validation_logs":null,"sentinel_updated_at":"2026-09-18 07:42:07","deleted_at":null,"ip_previous":null,"hetzner_server_id":null,"cloud_provider_token_id":null,"hetzner_server_status":null,"is_validating":false,"detected_traefik_version":null,"traefik_outdated_info":null,"server_metadata":{"os":"Ubuntu 22.04.5 LTS","arch":"x86_64","kernel":"5.15.0-187-generic","cpus":4,"memory_bytes":12239319040,"uptime_since":"2026-08-20 15:20:39","collected_at":"2026-08-20T13:37:08+00:00"},"vultr_instance_id":null,"vultr_instance_status":null,"digitalocean_droplet_id":null,"digitalocean_droplet_status":null,"sentinel_waiting_since":"2026-09-22T11:42:08.000000Z","is_coolify_host":false,"settings":{"id":2,"is_swarm_manager":false,"is_jump_server":false,"is_build_server":false,"is_reachable":true,"is_usable":true,"server_id":1,"created_at":"2026-08-19T05:37:20.000000Z","updated_at":"2026-09-22T11:42:05.000000Z","wildcard_domain":null,"is_cloudflare_tunnel":false,"is_logdrain_newrelic_enabled":false,"logdrain_newrelic_license_key":null,"logdrain_newrelic_base_uri":null,"is_logdrain_highlight_enabled":false,"logdrain_highlight_project_id":null,"is_logdrain_axiom_enabled":false,"logdrain_axiom_dataset_name":null,"logdrain_axiom_api_key":null,"is_swarm_worker":false,"is_logdrain_custom_enabled":false,"logdrain_custom_config":null,"logdrain_custom_config_parser":null,"concurrent_builds":2,"dynamic_timeout":3600,"force_disabled":false,"is_metrics_enabled":false,"generate_exact_labels":false,"force_docker_cleanup":true,"docker_cleanup_frequency":"0 0 * * *","docker_cleanup_threshold":80,"server_timezone":"UTC","delete_unused_volumes":false,"delete_unused_networks":false,"is_sentinel_enabled":true,"sentinel_token":"eyJpdiI6InhiRVYvTjcrY2grN293a0tWZkV1WFE9PSIsInZhbHVlIjoiaVFMZW5KTzY4QlZWYW82WmFnZmdBL0VwN3pJeEVRcjQzZ082T1NzT2xoMzVBVk9LQXJ4cXJkRUhZN0p2ZzcwQ3RFclg4WFlLOHVuTW0yU2pwcXR3aGc9PSIsIm1hYyI6IjdiMzFiOWU2MDhhN2RlNjhlZDJlYjRkMGQ3ZGU2YjlkMzI4MDY1ODk3YTY2NTgwZTdmOWM3Y2E0YjM3MTg3YzIiLCJ0YWciOiIifQ==","sentinel_metrics_refresh_rate_seconds":10,"sentinel_metrics_history_days":7,"sentinel_push_interval_seconds":60,"sentinel_custom_url":"https:\/\/deploy.digifox.live","server_disk_usage_notification_threshold":80,"is_sentinel_debug_enabled":false,"server_disk_usage_check_frequency":"0 23 * * *","is_terminal_enabled":true,"deployment_queue_limit":25,"disable_application_image_retention":false,"connection_timeout":10,"docker_version":"29.8.1","docker_version_checked_at":"2026-09-22T11:42:05.000000Z","compose_version":"5.5.1","compose_version_checked_at":"2026-09-22T11:42:05.000000Z","backup_compression_cpu_percentage":25}}},"destination_id":1,"destination_type":"App\\Models\\StandaloneDocker","docker_compose":null,"docker_compose_custom_build_command":null,"docker_compose_custom_start_command":null,"docker_compose_domains":null,"docker_compose_location":"\/docker-compose.yaml","docker_compose_raw":null,"docker_registry_image_name":null,"docker_registry_image_tag":null,"dockerfile":null,"dockerfile_location":null,"dockerfile_target_build":null,"environment":{"id":23,"name":"production","project_id":17,"created_at":"2026-08-12T05:23:25.000000Z","updated_at":"2026-08-12T05:23:25.000000Z","description":null,"uuid":"ijga3o6m6arup69yohj2ecyw","project":{"id":17,"uuid":"kozt3dmpa63penhin84sb8r0","name":"limelite-smartmirror","description":"","team_id":0,"created_at":"2026-08-12T05:23:25.000000Z","updated_at":"2026-08-18T11:28:25.000000Z","icon_path":null,"icon_storage_type":null,"icon_s3_storage_id":null,"team":{"id":0,"name":"Root Team","description":"The root team","personal_team":true,"created_at":"2026-06-05T23:01:55.000000Z","updated_at":"2026-06-05T23:11:01.000000Z","show_boarding":false,"custom_server_limit":null,"is_mcp_server_enabled":true}}},"environment_id":23,"fqdn":"http:\/\/vn0t5yt9sbzirnd0qjlmte4j.91.203.135.27.sslip.io","git_branch":"master","git_commit_sha":"HEAD","git_full_url":null,"git_repository":"https:\/\/mohanki:48d38a221949649fdf1b470bbd037de9a5e6c564@git.digifox.live\/mohanki\/limelite_faceanalyzer.git","health_check_command":null,"health_check_enabled":false,"health_check_host":"localhost","health_check_interval":5,"health_check_method":"GET","health_check_path":"\/","health_check_port":null,"health_check_response_text":null,"health_check_retries":10,"health_check_return_code":200,"health_check_scheme":"http","health_check_start_period":5,"health_check_timeout":5,"health_check_type":"http","http_basic_auth_password":null,"http_basic_auth_username":null,"install_command":null,"is_http_basic_auth_enabled":false,"last_online_at":"2026-06-05 23:01:53","last_restart_at":null,"last_restart_type":null,"limits_cpu_shares":1024,"limits_cpus":"0","limits_cpuset":null,"limits_memory":"0","limits_memory_reservation":"0","limits_memory_swap":"0","limits_memory_swappiness":60,"manual_webhook_secret_bitbucket":"fOAnt5ryuTAKiNFAt3nEfxREceCyUENbfaSRUD9P","manual_webhook_secret_gitea":"4wQTj8a4mUfSdna7o8CQdKSCawPWf39DPhvqeztR","manual_webhook_secret_github":"aJKfuyscFa2krCyO1kOYmNYzFS1qKckywv0pOd2O","manual_webhook_secret_gitlab":"ovN0g1yBvUwYR2rnWsAQHzQnDlzh5GPTIi2GLc38","max_restart_count":0,"noindex_domains":null,"ports_exposes":"80","ports_mappings":null,"post_deployment_command":null,"post_deployment_command_container":null,"pre_deployment_command":null,"pre_deployment_command_container":null,"preview_url_template":"{{pr_id}}.{{domain}}","private_key_id":null,"publish_directory":null,"redirect":"both","repository_project_id":null,"restart_count":0,"restart_limit_reached":false,"server_status":true,"settings":{"is_static":false,"is_git_submodules_enabled":true,"is_git_lfs_enabled":true,"is_auto_deploy_enabled":true,"is_force_https_enabled":true,"is_debug_enabled":false,"is_preview_deployments_enabled":false,"is_log_drain_enabled":false,"is_gpu_enabled":false,"gpu_driver":"nvidia","gpu_count":null,"gpu_device_ids":null,"gpu_options":null,"is_include_timestamps":false,"is_swarm_only_worker_nodes":true,"is_raw_compose_deployment_enabled":false,"is_build_server_enabled":false,"is_consistent_container_name_enabled":false,"is_gzip_enabled":true,"is_stripprefix_enabled":true,"connect_to_docker_network":false,"custom_internal_name":null,"is_container_label_escape_enabled":true,"is_env_sorting_enabled":false,"is_container_label_readonly_enabled":true,"is_preserve_repository_enabled":false,"disable_build_cache":false,"is_spa":false,"is_git_shallow_clone_enabled":true,"is_pr_deployments_public_enabled":false,"use_build_secrets":false,"stop_grace_period":null,"inject_build_args_to_dockerfile":true,"include_source_commit_in_build":false,"docker_images_to_keep":2},"source_id":null,"source_type":null,"start_command":null,"static_image":"nginx:alpine","status":"exited:unhealthy","swarm_placement_constraints":null,"swarm_replicas":1,"watch_paths":null,"created_at":"2026-09-22T11:42:15.000000Z","updated_at":"2026-09-22T11:42:15.000000Z"} \ No newline at end of file diff --git a/backend/controllers/analysisController.js b/backend/controllers/analysisController.js index fbd2bb7..e3c347f 100644 --- a/backend/controllers/analysisController.js +++ b/backend/controllers/analysisController.js @@ -132,6 +132,9 @@ exports.analyzeImage = async (req, res) => { } catch (error) { console.error('Error analyzing image:', error.response ? JSON.stringify(error.response.data) : error.message); - res.status(500).json({ error: error.message || 'Failed to analyze image' }); + res.status(500).json({ + error: error.message || 'Failed to analyze image', + details: error.response ? error.response.data : null + }); } }; diff --git a/backend/scripts/seedKeys.js b/backend/scripts/seedKeys.js index 667b4c9..60b64c2 100644 --- a/backend/scripts/seedKeys.js +++ b/backend/scripts/seedKeys.js @@ -7,8 +7,8 @@ dotenv.config({ path: path.join(__dirname, '../.env') }); const Config = require('../models/Config'); const { encrypt } = require('../utils/crypto'); -const PERFECT_CORP_API_KEY = 'sk-tHtPU8rst9cifrM6RhkjCKy9EpNfXBdcoCbzP0QDRhIkHr_InBYPS6g_6XGSumbm'; -const PERFECT_CORP_SECRET_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCKexD/nyLrGq7jXCxYi/Tjwr297cZS5EUFQbCV9NO2XQU/sJitsT2WBHHS8AYAQrzM56hPgnwpt/B+EuSuLfVbM/ympBUvSrULUNlrJF5lW96JMd1KF8RWC9fICZJuCE2iGpGAd0l9pOCUmYM4JZ/n26HxZxSSramkoOYxmhFYhQIDAQAB'; +const PERFECT_CORP_API_KEY = 'sk-cKf0TV79X6Fp3CZwKfsnHv1Rq9GOK6nFCAXyDG9CvnIFKGYiTtSdOnjyadBaYc5y'; +const PERFECT_CORP_SECRET_KEY = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCAiLqa5kyq7FR9WmK++HCPNjGLsljLfvJQq+JZzCx7pfLlVD5KzRb17iglHQJkFkSHxfqxtYNwosA54oT9uD1kA96TJy+uOLRwxry7BMPTFQyDdIoloJCI/Bl+SEnSxjDoYpU8zA3S4GDkmLH3A7vHkxhMydgB8B1EOxJahQsF7QIDAQAB'; async function seedKeys() { try { @@ -38,3 +38,10 @@ async function seedKeys() { } module.exports = seedKeys; + +if (require.main === module) { + seedKeys().then(() => { + console.log('Seeding complete. Exiting.'); + process.exit(0); + }); +} diff --git a/backend/scripts/testApiKey.js b/backend/scripts/testApiKey.js new file mode 100644 index 0000000..08edd58 --- /dev/null +++ b/backend/scripts/testApiKey.js @@ -0,0 +1,18 @@ +const axios = require('axios'); +const API_KEY = 'sk-cKf0TV79X6Fp3CZwKfsnHv1Rq9GOK6nFCAXyDG9CvnIFKGYiTtSdOnjyadBaYc5y'; +const baseUrl = 'https://yce-api-01.makeupar.com/s2s/v2.0'; +const initPayload = { + files: [ + { content_type: 'image/jpeg', file_name: 'test.jpg', file_size: 100000 } + ] +}; +axios.post(`${baseUrl}/file`, initPayload, { + headers: { + 'Authorization': `Bearer ${API_KEY}`, + 'Content-Type': 'application/json' + } +}).then(res => console.log('Init success:', JSON.stringify(res.data))) + .catch(err => { + console.error('Init failed:', err.response ? err.response.status : err.message); + if(err.response) console.error(JSON.stringify(err.response.data)); + }); diff --git a/backend/scripts/testApiKeyTask.js b/backend/scripts/testApiKeyTask.js new file mode 100644 index 0000000..f32a29d --- /dev/null +++ b/backend/scripts/testApiKeyTask.js @@ -0,0 +1,44 @@ +const axios = require('axios'); +const API_KEY = 'sk-cKf0TV79X6Fp3CZwKfsnHv1Rq9GOK6nFCAXyDG9CvnIFKGYiTtSdOnjyadBaYc5y'; +const baseUrl = 'https://yce-api-01.makeupar.com/s2s/v2.0'; + +async function test() { + try { + const initPayload = { + files: [ + { content_type: 'image/jpeg', file_name: 'test.jpg', file_size: 100000 } + ] + }; + + console.log('Init...'); + const res = await axios.post(`${baseUrl}/file`, initPayload, { + headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' } + }); + + const fileId = res.data.data.files[0].file_id; + console.log('File ID:', fileId); + + console.log('Creating task...'); + const taskPayload = { + src_file_id: fileId, + dst_actions: [ + "wrinkle", "pore", "texture", "acne", "moisture", "eye_bag", + "firmness", "age_spot", "radiance", "redness", "oiliness" + ], + miniserver_args: { + enable_mask_overlay: false + }, + format: "json" + }; + + const taskRes = await axios.post(`${baseUrl}/task/skin-analysis`, taskPayload, { + headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' } + }); + + console.log('Task success:', taskRes.data); + } catch (err) { + console.error('Failed:', err.response ? err.response.status : err.message); + if(err.response) console.error(JSON.stringify(err.response.data, null, 2)); + } +} +test(); diff --git a/exec.json b/exec.json new file mode 100644 index 0000000..bf2647c --- /dev/null +++ b/exec.json @@ -0,0 +1,3 @@ +{ + "command": "uptime" +} diff --git a/frontend/src/AnalysisResultPage.jsx b/frontend/src/AnalysisResultPage.jsx index 28af8f5..184b7ff 100644 --- a/frontend/src/AnalysisResultPage.jsx +++ b/frontend/src/AnalysisResultPage.jsx @@ -1,1039 +1,1039 @@ -// src/pages/AnalysisResultPage.jsx -import React, { useState, useEffect, useRef } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { loadFromDB, saveToDB } from './db'; -import jsPDF from 'jspdf'; -import html2canvas from 'html2canvas'; -import API_BASE_URL from './config'; - -const AnalysisResultPage = () => { - const navigate = useNavigate(); - - const errorDescriptions = { - 'error_below_min_image_size': 'Input image resolution is too small', - 'error_exceed_max_image_size': 'Input image resolution is too large', - 'error_src_skin_too_small': 'The skin area in the uploaded image is too small. The width of the skin needs to be greater than 60% of the width of the image.', - 'error_src_skin_out_of_bound': 'The skin area in the uploaded image is out of bound', - 'error_lighting_dark': 'The lighting in the uploaded image is too dark' - }; - - const [analysisData, setAnalysisData] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [selectedMetric, setSelectedMetric] = useState(null); - const [activeMask, setActiveMask] = useState(null); - const [showSignupModal, setShowSignupModal] = useState(false); - const [showHelpModal, setShowHelpModal] = useState(false); - const [isVerified, setIsVerified] = useState(false); - - // Form State - const [formData, setFormData] = useState({ name: '', phone: '' }); - const [formErrors, setFormErrors] = useState({ name: '', phone: '' }); - - // Report Ref - const reportRef = useRef(null); - - // OTP State - const [showOtpModal, setShowOtpModal] = useState(false); - const [otp, setOtp] = useState(new Array(6).fill('')); - const [otpError, setOtpError] = useState(''); - const [isVerifying, setIsVerifying] = useState(false); - const [isSendingReport, setIsSendingReport] = useState(false); - const [isSubmittingSignup, setIsSubmittingSignup] = useState(false); - const [toast, setToast] = useState({ show: false, message: '' }); - - // WhatsApp API configuration has been moved to backend - - // Track the generated OTP for verification (frontend-only simulation) - const [generatedOtp, setGeneratedOtp] = useState(''); - - // Validation Regex - const nameRegex = /^[a-zA-Z\s]*$/; - const phoneRegex = /^\d{0,10}$/; - - // Ref for PDF capture - const pdfRef = useRef(null); - - useEffect(() => { - const loadAnalysisData = async () => { - try { - const urlParams = new URLSearchParams(window.location.search); - const phone = urlParams.get('phone'); - const reportId = urlParams.get('report'); - - if (phone && reportId) { - // We are coming from a verification link - try { - const res = await fetch(`${API_BASE_URL}/api/reports/${reportId}`); - if (res.ok) { - const savedReport = await res.json(); - setAnalysisData(savedReport.data); - setFormData(prev => ({ ...prev, phone: phone, name: savedReport.userName || '' })); - // Skip OTP when clicking the direct report link or from admin panel - setIsVerified(true); - setIsLoading(false); - return; - } - } catch (e) { - console.error("Failed to load report", e); - } - } - - const storedData = await loadFromDB('analysisData'); - if (!storedData) { - throw new Error('No analysis data found. Please start a new analysis.'); - } - setAnalysisData(storedData); - setIsLoading(false); - } catch (err) { - setError(err.message); - setIsLoading(false); - } - }; - loadAnalysisData(); - }, []); - - const handleNewAnalysis = async () => { - await saveToDB('analysisData', null); - navigate('/'); - }; - - const getScoreColor = (score) => { - if (!score && score !== 0) return 'bg-gray-600'; - if (score >= 70) return 'bg-emerald-500'; - if (score >= 40) return 'bg-yellow-500'; - return 'bg-limelite-red'; - }; - - const getMetricDisplayName = (metricKey) => { - const displayNames = { - wrinkle: 'Wrinkles', - radiance: 'Radiance', - firmness: 'Firmness', - oiliness: 'Oiliness', - redness: 'Redness', - dark_circle_v2: 'Dark Circles', - pore: 'Pores', - eye_bag: 'Eye Bags', - texture: 'Texture', - age_spot: 'Age Spots', - droopy_lower_eyelid: 'Droopy Lower Eyelid', - moisture: 'Moisture', - droopy_upper_eyelid: 'Droopy Upper Eyelid', - acne: 'Acne' - }; - return displayNames[metricKey] || metricKey.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); - }; - - const handleMetricClick = (metric) => { - if (metric.output_mask_name) { - const maskData = analysisData?.maskImages?.[metric.output_mask_name]; - if (maskData) { - setActiveMask(maskData); - setSelectedMetric(metric.key); - } else { - setActiveMask(null); - setSelectedMetric(null); - } - } else { - setActiveMask(null); - setSelectedMetric(null); - } - }; - - const handleClearMask = () => { - setActiveMask(null); - setSelectedMetric(null); - }; - - const handleNameChange = (e) => { - const val = e.target.value; - if (nameRegex.test(val)) { - setFormData(prev => ({ ...prev, name: val })); - if (formErrors.name) setFormErrors(prev => ({ ...prev, name: '' })); - } - }; - - const handlePhoneChange = (e) => { - const val = e.target.value; - if (phoneRegex.test(val)) { - setFormData(prev => ({ ...prev, phone: val })); - if (formErrors.phone) setFormErrors(prev => ({ ...prev, phone: '' })); - } - }; - - // OTP Handlers - const handleOtpChange = (element, index) => { - if (isNaN(element.value)) return false; - - setOtp([...otp.map((d, idx) => (idx === index ? element.value : d))]); - - // Focus next input - if (element.nextSibling && element.value !== "") { - element.nextSibling.focus(); - } - }; - - const sendWhatsappOtp = async (phone) => { - // Generate a unique ID for this specific analysis report - const reportId = Math.random().toString(36).substring(7); - const currentUrl = window.location.origin + window.location.pathname; - const verificationLink = `${currentUrl}?phone=${phone}&report=${reportId}`; - - let otpToUse = ''; - let isExisting = false; - - try { - // 1. Create/Update User & Get OTP from Backend - const userRes = await fetch(`${API_BASE_URL}/api/users`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ name: formData.name, phone: phone }) - }); - const userResText = await userRes.text(); - let userData; - try { - userData = JSON.parse(userResText); - } catch (e) { - console.error('User API returned non-JSON:', userResText); - throw new Error('Could not connect to user API properly. Please try again.'); - } - - if (userData.success) { - otpToUse = userData.user.otp; - isExisting = userData.user.isExisting || false; - setGeneratedOtp(otpToUse); - } else if (userData.message && userData.message.includes('already registered')) { - // If backend says already registered, we treat as existing user - isExisting = true; - console.log("User already registered, proceeding as existing user"); - } else { - const errorMsg = userData.message || 'Failed to register user'; - throw new Error(errorMsg); - } - - // 2. Generate PDF and Save to Backend - let generatedPdfLink = verificationLink; - if (reportRef.current) { - const reportElement = reportRef.current; - reportElement.style.display = 'block'; - - try { - const canvas = await html2canvas(reportElement, { - scale: 2, - useCORS: true, - allowTaint: true, - backgroundColor: '#ffffff' - }); - const imgData = canvas.toDataURL('image/png'); - const pdf = new jsPDF('p', 'mm', 'a4'); - const pdfWidth = pdf.internal.pageSize.getWidth(); - const pdfHeight = (canvas.height * pdfWidth) / canvas.width; - pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight); - const pdfBlob = pdf.output('blob'); - - const fd = new FormData(); - fd.append('pdf', pdfBlob, `Limelite_Report_${phone}.pdf`); - fd.append('phone', phone); - fd.append('userName', formData.name); - - const saveRes = await fetch(`${API_BASE_URL}/api/reports/save-pdf`, { - method: 'POST', - body: fd - }); - const saveData = await saveRes.json(); - - if (saveData.success) { - generatedPdfLink = `${API_BASE_URL}${saveData.link}`; - } - } catch(err) { - console.error("Failed to generate/upload PDF during save:", err); - } finally { - reportElement.style.display = 'none'; - } - } - - verificationLink = generatedPdfLink; - - console.log(`Sending WhatsApp message request to backend for ${phone}`); - - const response = await fetch(`${API_BASE_URL}/api/whatsapp/send-otp`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - phone: phone, - name: formData.name, - otpToUse: otpToUse, - verificationLink: verificationLink, - isExisting: isExisting - }) - }); - - if (!response.ok) { - const waErrText = await response.text(); - let errorData; - try { - errorData = JSON.parse(waErrText); - } catch (e) { - errorData = { message: waErrText.substring(0, 100) }; - } - console.error('Backend WhatsApp API Error:', errorData); - throw new Error(errorData.message || 'Failed to send WhatsApp message'); - } - - console.log('WhatsApp message sent successfully via backend'); - return { ...(userData.user || {}), isExisting: isExisting }; - } catch (err) { - console.error('Error in sendWhatsappOtp:', err); - throw err; - } - }; - - const verifyWhatsappOtp = async (enteredOtp) => { - try { - const res = await fetch(`${API_BASE_URL}/api/verify-otp`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ phone: formData.phone, otp: enteredOtp }) - }); - const data = await res.json(); - return data.success; - } catch (e) { - console.error("OTP verification error", e); - return false; - } - }; - - const sendReportToWhatsapp = async () => { - setIsSendingReport(true); - try { - // Sending a follow-up message with the overall score via backend - const response = await fetch(`${API_BASE_URL}/api/whatsapp/send-report`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - phone: formData.phone, - name: formData.name, - overallScore: overallScore, - origin: window.location.origin - }) - }); - - if (!response.ok) throw new Error('Failed to send report'); - - alert("Report details sent to your WhatsApp!"); - } catch (err) { - console.error('Error sending report:', err); - alert("Failed to send report. Please try again."); - } finally { - setIsSendingReport(false); - } - }; - - const handleSignupSubmit = async () => { - // const db = JSON.parse(localStorage.getItem('limelite_users') || '{}'); - // const existingUser = db[formData.phone]; - const existingUser = false; // logic moved to backend, or we can check via api but it's fine - - - let errors = {}; - if (!formData.name.trim()) errors.name = 'Name is required'; - if (formData.phone.length !== 10) errors.phone = 'Number must be exactly 10 digits'; - - // We allow both new and existing users to proceed - // If they are existing, the backend call in sendWhatsappOtp will handle it - - if (Object.keys(errors).length > 0) { - setFormErrors(errors); - return; - } - - setIsSubmittingSignup(true); - try { - const user = await sendWhatsappOtp(formData.phone); - setShowSignupModal(false); - - if (user && user.isExisting) { - setIsVerified(true); - setToast({ show: true, message: "You're already an user, welcome back!" }); - setTimeout(() => setToast({ show: false, message: '' }), 4000); - } else { - setShowOtpModal(true); - setOtp(new Array(6).fill('')); - } - } catch (err) { - setFormErrors({ phone: err.message || 'Failed to send message. Please try again.' }); - } finally { - setIsSubmittingSignup(false); - } - }; - - const handleVerifyOtp = async () => { - const enteredOtp = otp.join(''); - if (enteredOtp.length !== 6) { - setOtpError('Please enter a valid 6-digit OTP'); - return; - } - - setIsVerifying(true); - setOtpError(''); - - try { - const isValid = await verifyWhatsappOtp(enteredOtp); - if (isValid) { - alert("OTP Verified Successfully!"); - setShowOtpModal(false); - setIsVerified(true); - } else { - setOtpError('Invalid OTP. Please try again.'); - } - } catch (error) { - setOtpError('Verification failed. Please try again.'); - } finally { - setIsVerifying(false); - } - }; - - const getScoreValue = (data, key) => { - if (!data || typeof data !== 'object') return null; - if (data[key] && typeof data[key] === 'object') return data[key].ui_score || data[key].score || data[key].raw_score || null; - if (data[key] !== undefined && data[key] !== null) return data[key]; - return null; - }; - - const hasDetailedData = (data, key) => { - return data && data[key] && typeof data[key] === 'object' && data[key].ui_score !== undefined; - }; - - // --- START OF MODIFICATION --- - /** - * Adjusts the score by -5 and ensures it's not negative. - * @param {number|string} score The original score. - * @returns {number|null} The adjusted score or null. - */ - const getAdjustedScore = (score) => { - const numScore = Number(score); - if (isNaN(numScore) || score === null || score === undefined) return null; - // Apply -5 adjustment and ensure the score is not negative. - return Math.max(0, numScore - 5); - }; - // --- END OF MODIFICATION --- - - const getDisplayMetrics = (scoreData) => { - if (!scoreData) return []; - return Object.keys(scoreData) - .filter(key => !['all', 'skin_age', 'takeaways', 'resize', 'image', 'url', 'resize_image', 'image_size'].includes(key)) - .map(key => { - const rawScore = hasDetailedData(scoreData, key) ? scoreData[key].ui_score : getScoreValue(scoreData, key); - // --- START OF MODIFICATION --- - const adjustedUiScore = getAdjustedScore(rawScore); - // --- END OF MODIFICATION --- - return { - key, - name: getMetricDisplayName(key), - // Use the adjusted score for display - ui_score: adjustedUiScore, - raw_score: hasDetailedData(scoreData, key) ? scoreData[key].raw_score : null, - output_mask_name: hasDetailedData(scoreData, key) ? scoreData[key].output_mask_name : null, - hasDetails: hasDetailedData(scoreData, key) - }; - }); - }; - - const renderUserImage = () => { - const originalImage = analysisData?.originalImage; - if (!originalImage) return null; - - return ( -
- Face Analysis - {activeMask && ( - Analysis Mask - )} -
- ); - }; - - const handleDownloadPDF = async () => { - if (!reportRef.current) return; - - // Show the hidden report briefly for capture - const reportElement = reportRef.current; - reportElement.style.display = 'block'; - - try { - const canvas = await html2canvas(reportElement, { - scale: 2, - useCORS: true, - allowTaint: true, - backgroundColor: '#ffffff' - }); - const imgData = canvas.toDataURL('image/png'); - - const pdf = new jsPDF('p', 'mm', 'a4'); - const pdfWidth = pdf.internal.pageSize.getWidth(); - const pdfHeight = (canvas.height * pdfWidth) / canvas.width; - - pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight); - pdf.save(`Limelite_Face_Report_${formData.name || 'User'}.pdf`); - } catch (err) { - console.error("PDF Generation Error:", err); - alert("Failed to generate PDF. Please try again."); - } finally { - reportElement.style.display = 'none'; - } - }; - - if (isLoading) return ( -
-
-
-

Analyzing skin profile...

-
-
- ); - - if (error) return ( -
-
-
- -
-

Analysis Failed

-

{errorDescriptions[error] || error}

- -
-
- ); - - if (!analysisData || !analysisData.scoreData) return ( -
-
-

No analysis data available.

- -
-
- ); - - const { scoreData } = analysisData; - const displayMetrics = getDisplayMetrics(scoreData); - - // --- START OF MODIFICATION --- - const rawOverallScore = getScoreValue(scoreData, 'all'); - const overallScore = getAdjustedScore(rawOverallScore); // Adjusted overall score - // --- END OF MODIFICATION --- - - const skinAge = getScoreValue(scoreData, 'skin_age'); - // Displayed skin age is 5 years younger than the calculated skin_age. - // Ensure it's a number and not negative. Round to nearest integer for display. - const displayedSkinAge = (skinAge !== null && skinAge !== undefined && !isNaN(Number(skinAge))) - ? Math.max(0, Math.round(Number(skinAge) - 5)) - : null; - const takeaways = scoreData.takeaways || []; - - // Calculate stroke dasharray for the main score circle - const radius = 54; - const circumference = 2 * Math.PI * radius; - const strokeDashoffset = circumference - ((overallScore || 0) / 100) * circumference; - - // --- SORTING FOR TOP CONCERNS --- - // Filter to get valid scores and sort by lowest score first - const displayedMetricsList = isVerified - ? displayMetrics.filter(m => m.ui_score !== null).sort((a, b) => a.ui_score - b.ui_score) - : displayMetrics.filter(m => m.ui_score !== null).sort((a, b) => a.ui_score - b.ui_score).slice(0, 3); - - return ( -
- - {/* Header (Matching App.js) */} -
-
- Limelite Logo -
-
- - -
-
- - {/* Main Content */} -
- - {/* Image Section */} -
- {renderUserImage()} -
- - {/* Results Panel - Dark Theme (Rectangular) */} -
- - {/* Main Score Row - With Border Separator */} -
-
- {overallScore?.toFixed(0)} -
- Skin Health -
-
- - {activeMask && ( - - )} -
- - {/* Top Concerns */} -
-

{isVerified ? 'ANALYSIS METRICS' : 'TOP CONCERNS'}

-
- {displayedMetricsList.map((metric, idx) => ( -
isVerified && handleMetricClick(metric)} - className={`bg-[#2B2B2B] rounded-none flex flex-col items-center justify-between p-2 h-[145px] flex-shrink-0 w-[30%] relative group transition-all duration-300 ${isVerified ? 'cursor-pointer hover:bg-[#333]' : ''}`} - style={{ - border: selectedMetric === metric.key ? '1px solid transparent' : '1px solid #333', - backgroundImage: selectedMetric === metric.key ? 'linear-gradient(#2B2B2B, #2B2B2B), linear-gradient(135deg, #ffffff 0%, rgba(255,255,255,0.1) 40%, rgba(255,255,255,0.1) 60%, #ffffff 100%)' : 'none', - backgroundOrigin: 'border-box', - backgroundClip: 'padding-box, border-box' - }} - > -
- {metric.ui_score} - {metric.name} -
- - {/* Badge */} -
= 70 ? 'bg-emerald-500/20 text-[#4ade80] border border-emerald-500/30' : - metric.ui_score >= 40 ? 'bg-amber-500/20 text-[#fbbf24] border border-amber-500/30' : - 'bg-limelite-red/20 text-limelite-red border border-limelite-red/30' - }`}> - {metric.ui_score >= 70 ? 'Good' : metric.ui_score >= 40 ? 'Average' : 'Bad'} -
-
- ))} - - {!isVerified && ( -
- - - -
- )} -
-
-
- - {/* Footer CTA Section */} - {!isVerified ? ( -
- -

- Sign up to access overall analysis report -

-
- ) : ( -
- -
- )} - -
- - {/* Signup Modal */} - {showSignupModal && ( -
-
- -
-

Sign Up

- -
- -
-
- - - {formErrors.name &&

{formErrors.name}

} -
- -
- - - {formErrors.phone &&

{formErrors.phone}

} -
- - -
- -
-
- )} - - {/* OTP Modal */} - {showOtpModal && ( -
-
- -
-

OTP Verification

- -
- -

- Please enter the OTP sent to you on whatsapp. -

- -
- {otp.map((data, index) => ( - handleOtpChange(e.target, index)} - onFocus={e => e.target.select()} - /> - ))} -
- - {otpError &&

{otpError}

} - - - -
-
- )} - - {/* Help Modal */} - {showHelpModal && ( -
-
-
-

Help

- -
- -
-
- • -

- To access previous reports, please contact us at hello@limelite.com -

-
-
- • -

- If you experience any technical issues, please contact us at support@example.com -

-
-
-
-
- )} - - {/* Toast Notification */} - {toast.show && ( -
-
-
- - - -
- {toast.message} -
-
- )} - - {/* Premium Hidden PDF Report Template */} -
- {/* Header Section */} -
-
- Logo -
-
-

Face Analysis Report

-
-
-
- - {/* Accent Line */} -
- - {/* Info Row (Name, Phone, Date) */} -
-
-

Name

-

{formData.name || 'Anonymous'}

-
-
-

Contact Number

-

+91 {formData.phone || 'N/A'}

-
-
-

Analysis Date

-

- {(() => { - const d = new Date(); - const day = d.getDate(); - const suffix = (day > 3 && day < 21) || day % 10 > 3 ? 'th' : ['th', 'st', 'nd', 'rd'][day % 10]; - return `${day}${suffix} ${d.toLocaleString('en-GB', { month: 'long' })} ${d.getFullYear()}`; - })()} -

-
-
- -
- {/* Primary Imaging */} -
-
- Original Scan -
-
Analyzed skin Profile
-
- - {/* Overall Score Column */} -
-
-

Skin Health

-
-

{Math.round(overallScore)}

-
-
- {/* Estimated Skin Age removed as requested */} -
-
- - {/* Parameters Section */} -
-

- - Diagnostic Deep-Dive -

- -
- {displayMetrics.filter(m => m.key !== 'all').map((metric, idx) => ( -
-
- {metric.name} - = 70 ? '#10b981' : metric.ui_score >= 40 ? '#f59e0b' : '#ef4444' }}>{metric.ui_score}% -
-
-
= 70 ? '#10b981' : metric.ui_score >= 40 ? '#f59e0b' : 'limelite-red' - }}>
-
-
- ))} -
-
- - {/* Specialist Recommendations */} -
-

Prescriptive Guidance

-
-
- STEP 01 -

Advanced Hydration: Apply hyaluronic acid serum daily to stabilize moisture levels.

-
-
- STEP 02 -

Dermal Protection: Broad-spectrum physical sunshade is mandatory for UV protection.

-
-
- STEP 03 -

Clinical Intervention: Visit your specialist for focused treatment on top priority zones.

-
-
-
- - {/* Minimalist Footer */} -
-

- * This diagnostic data is interpreted by Limelite AI Face Vision. For detailed medical prescriptions, please book a 1-on-1 therapist consultation. -

-
-

LIMELITE CLINICAL SYSTEMS

-

support@limelite.com | limelite.com

-
-
-
-
- ); -}; - +// src/pages/AnalysisResultPage.jsx +import React, { useState, useEffect, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { loadFromDB, saveToDB } from './db'; +import jsPDF from 'jspdf'; +import html2canvas from 'html2canvas'; +import API_BASE_URL from './config'; + +const AnalysisResultPage = () => { + const navigate = useNavigate(); + + const errorDescriptions = { + 'error_below_min_image_size': 'Input image resolution is too small', + 'error_exceed_max_image_size': 'Input image resolution is too large', + 'error_src_skin_too_small': 'The skin area in the uploaded image is too small. The width of the skin needs to be greater than 60% of the width of the image.', + 'error_src_skin_out_of_bound': 'The skin area in the uploaded image is out of bound', + 'error_lighting_dark': 'The lighting in the uploaded image is too dark' + }; + + const [analysisData, setAnalysisData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + const [selectedMetric, setSelectedMetric] = useState(null); + const [activeMask, setActiveMask] = useState(null); + const [showSignupModal, setShowSignupModal] = useState(false); + const [showHelpModal, setShowHelpModal] = useState(false); + const [isVerified, setIsVerified] = useState(false); + + // Form State + const [formData, setFormData] = useState({ name: '', phone: '' }); + const [formErrors, setFormErrors] = useState({ name: '', phone: '' }); + + // Report Ref + const reportRef = useRef(null); + + // OTP State + const [showOtpModal, setShowOtpModal] = useState(false); + const [otp, setOtp] = useState(new Array(6).fill('')); + const [otpError, setOtpError] = useState(''); + const [isVerifying, setIsVerifying] = useState(false); + const [isSendingReport, setIsSendingReport] = useState(false); + const [isSubmittingSignup, setIsSubmittingSignup] = useState(false); + const [toast, setToast] = useState({ show: false, message: '' }); + + // WhatsApp API configuration has been moved to backend + + // Track the generated OTP for verification (frontend-only simulation) + const [generatedOtp, setGeneratedOtp] = useState(''); + + // Validation Regex + const nameRegex = /^[a-zA-Z\s]*$/; + const phoneRegex = /^\d{0,10}$/; + + // Ref for PDF capture + const pdfRef = useRef(null); + + useEffect(() => { + const loadAnalysisData = async () => { + try { + const urlParams = new URLSearchParams(window.location.search); + const phone = urlParams.get('phone'); + const reportId = urlParams.get('report'); + + if (phone && reportId) { + // We are coming from a verification link + try { + const res = await fetch(`${API_BASE_URL}/api/reports/${reportId}`); + if (res.ok) { + const savedReport = await res.json(); + setAnalysisData(savedReport.data); + setFormData(prev => ({ ...prev, phone: phone, name: savedReport.userName || '' })); + // Skip OTP when clicking the direct report link or from admin panel + setIsVerified(true); + setIsLoading(false); + return; + } + } catch (e) { + console.error("Failed to load report", e); + } + } + + const storedData = await loadFromDB('analysisData'); + if (!storedData) { + throw new Error('No analysis data found. Please start a new analysis.'); + } + setAnalysisData(storedData); + setIsLoading(false); + } catch (err) { + setError(err.message); + setIsLoading(false); + } + }; + loadAnalysisData(); + }, []); + + const handleNewAnalysis = async () => { + await saveToDB('analysisData', null); + navigate('/'); + }; + + const getScoreColor = (score) => { + if (!score && score !== 0) return 'bg-gray-600'; + if (score >= 70) return 'bg-emerald-500'; + if (score >= 40) return 'bg-yellow-500'; + return 'bg-limelite-red'; + }; + + const getMetricDisplayName = (metricKey) => { + const displayNames = { + wrinkle: 'Wrinkles', + radiance: 'Radiance', + firmness: 'Firmness', + oiliness: 'Oiliness', + redness: 'Redness', + dark_circle_v2: 'Dark Circles', + pore: 'Pores', + eye_bag: 'Eye Bags', + texture: 'Texture', + age_spot: 'Age Spots', + droopy_lower_eyelid: 'Droopy Lower Eyelid', + moisture: 'Moisture', + droopy_upper_eyelid: 'Droopy Upper Eyelid', + acne: 'Acne' + }; + return displayNames[metricKey] || metricKey.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); + }; + + const handleMetricClick = (metric) => { + if (metric.output_mask_name) { + const maskData = analysisData?.maskImages?.[metric.output_mask_name]; + if (maskData) { + setActiveMask(maskData); + setSelectedMetric(metric.key); + } else { + setActiveMask(null); + setSelectedMetric(null); + } + } else { + setActiveMask(null); + setSelectedMetric(null); + } + }; + + const handleClearMask = () => { + setActiveMask(null); + setSelectedMetric(null); + }; + + const handleNameChange = (e) => { + const val = e.target.value; + if (nameRegex.test(val)) { + setFormData(prev => ({ ...prev, name: val })); + if (formErrors.name) setFormErrors(prev => ({ ...prev, name: '' })); + } + }; + + const handlePhoneChange = (e) => { + const val = e.target.value; + if (phoneRegex.test(val)) { + setFormData(prev => ({ ...prev, phone: val })); + if (formErrors.phone) setFormErrors(prev => ({ ...prev, phone: '' })); + } + }; + + // OTP Handlers + const handleOtpChange = (element, index) => { + if (isNaN(element.value)) return false; + + setOtp([...otp.map((d, idx) => (idx === index ? element.value : d))]); + + // Focus next input + if (element.nextSibling && element.value !== "") { + element.nextSibling.focus(); + } + }; + + const sendWhatsappOtp = async (phone) => { + // Generate a unique ID for this specific analysis report + const reportId = Math.random().toString(36).substring(7); + const currentUrl = window.location.origin + window.location.pathname; + let verificationLink = `${currentUrl}?phone=${phone}&report=${reportId}`; + + let otpToUse = ''; + let isExisting = false; + + try { + // 1. Create/Update User & Get OTP from Backend + const userRes = await fetch(`${API_BASE_URL}/api/users`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name: formData.name, phone: phone }) + }); + const userResText = await userRes.text(); + let userData; + try { + userData = JSON.parse(userResText); + } catch (e) { + console.error('User API returned non-JSON:', userResText); + throw new Error('Could not connect to user API properly. Please try again.'); + } + + if (userData.success) { + otpToUse = userData.user.otp; + isExisting = userData.user.isExisting || false; + setGeneratedOtp(otpToUse); + } else if (userData.message && userData.message.includes('already registered')) { + // If backend says already registered, we treat as existing user + isExisting = true; + console.log("User already registered, proceeding as existing user"); + } else { + const errorMsg = userData.message || 'Failed to register user'; + throw new Error(errorMsg); + } + + // 2. Generate PDF and Save to Backend + let generatedPdfLink = verificationLink; + if (reportRef.current) { + const reportElement = reportRef.current; + reportElement.style.display = 'block'; + + try { + const canvas = await html2canvas(reportElement, { + scale: 2, + useCORS: true, + allowTaint: true, + backgroundColor: '#ffffff' + }); + const imgData = canvas.toDataURL('image/png'); + const pdf = new jsPDF('p', 'mm', 'a4'); + const pdfWidth = pdf.internal.pageSize.getWidth(); + const pdfHeight = (canvas.height * pdfWidth) / canvas.width; + pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight); + const pdfBlob = pdf.output('blob'); + + const fd = new FormData(); + fd.append('pdf', pdfBlob, `Limelite_Report_${phone}.pdf`); + fd.append('phone', phone); + fd.append('userName', formData.name); + + const saveRes = await fetch(`${API_BASE_URL}/api/reports/save-pdf`, { + method: 'POST', + body: fd + }); + const saveData = await saveRes.json(); + + if (saveData.success) { + generatedPdfLink = `${API_BASE_URL}${saveData.link}`; + } + } catch(err) { + console.error("Failed to generate/upload PDF during save:", err); + } finally { + reportElement.style.display = 'none'; + } + } + + verificationLink = generatedPdfLink; + + console.log(`Sending WhatsApp message request to backend for ${phone}`); + + const response = await fetch(`${API_BASE_URL}/api/whatsapp/send-otp`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + phone: phone, + name: formData.name, + otpToUse: otpToUse, + verificationLink: verificationLink, + isExisting: isExisting + }) + }); + + if (!response.ok) { + const waErrText = await response.text(); + let errorData; + try { + errorData = JSON.parse(waErrText); + } catch (e) { + errorData = { message: waErrText.substring(0, 100) }; + } + console.error('Backend WhatsApp API Error:', errorData); + throw new Error(errorData.message || 'Failed to send WhatsApp message'); + } + + console.log('WhatsApp message sent successfully via backend'); + return { ...(userData.user || {}), isExisting: isExisting }; + } catch (err) { + console.error('Error in sendWhatsappOtp:', err); + throw err; + } + }; + + const verifyWhatsappOtp = async (enteredOtp) => { + try { + const res = await fetch(`${API_BASE_URL}/api/verify-otp`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ phone: formData.phone, otp: enteredOtp }) + }); + const data = await res.json(); + return data.success; + } catch (e) { + console.error("OTP verification error", e); + return false; + } + }; + + const sendReportToWhatsapp = async () => { + setIsSendingReport(true); + try { + // Sending a follow-up message with the overall score via backend + const response = await fetch(`${API_BASE_URL}/api/whatsapp/send-report`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + phone: formData.phone, + name: formData.name, + overallScore: overallScore, + origin: window.location.origin + }) + }); + + if (!response.ok) throw new Error('Failed to send report'); + + alert("Report details sent to your WhatsApp!"); + } catch (err) { + console.error('Error sending report:', err); + alert("Failed to send report. Please try again."); + } finally { + setIsSendingReport(false); + } + }; + + const handleSignupSubmit = async () => { + // const db = JSON.parse(localStorage.getItem('limelite_users') || '{}'); + // const existingUser = db[formData.phone]; + const existingUser = false; // logic moved to backend, or we can check via api but it's fine + + + let errors = {}; + if (!formData.name.trim()) errors.name = 'Name is required'; + if (formData.phone.length !== 10) errors.phone = 'Number must be exactly 10 digits'; + + // We allow both new and existing users to proceed + // If they are existing, the backend call in sendWhatsappOtp will handle it + + if (Object.keys(errors).length > 0) { + setFormErrors(errors); + return; + } + + setIsSubmittingSignup(true); + try { + const user = await sendWhatsappOtp(formData.phone); + setShowSignupModal(false); + + if (user && user.isExisting) { + setIsVerified(true); + setToast({ show: true, message: "You're already an user, welcome back!" }); + setTimeout(() => setToast({ show: false, message: '' }), 4000); + } else { + setShowOtpModal(true); + setOtp(new Array(6).fill('')); + } + } catch (err) { + setFormErrors({ phone: err.message || 'Failed to send message. Please try again.' }); + } finally { + setIsSubmittingSignup(false); + } + }; + + const handleVerifyOtp = async () => { + const enteredOtp = otp.join(''); + if (enteredOtp.length !== 6) { + setOtpError('Please enter a valid 6-digit OTP'); + return; + } + + setIsVerifying(true); + setOtpError(''); + + try { + const isValid = await verifyWhatsappOtp(enteredOtp); + if (isValid) { + alert("OTP Verified Successfully!"); + setShowOtpModal(false); + setIsVerified(true); + } else { + setOtpError('Invalid OTP. Please try again.'); + } + } catch (error) { + setOtpError('Verification failed. Please try again.'); + } finally { + setIsVerifying(false); + } + }; + + const getScoreValue = (data, key) => { + if (!data || typeof data !== 'object') return null; + if (data[key] && typeof data[key] === 'object') return data[key].ui_score || data[key].score || data[key].raw_score || null; + if (data[key] !== undefined && data[key] !== null) return data[key]; + return null; + }; + + const hasDetailedData = (data, key) => { + return data && data[key] && typeof data[key] === 'object' && data[key].ui_score !== undefined; + }; + + // --- START OF MODIFICATION --- + /** + * Adjusts the score by -5 and ensures it's not negative. + * @param {number|string} score The original score. + * @returns {number|null} The adjusted score or null. + */ + const getAdjustedScore = (score) => { + const numScore = Number(score); + if (isNaN(numScore) || score === null || score === undefined) return null; + // Apply -5 adjustment and ensure the score is not negative. + return Math.max(0, numScore - 5); + }; + // --- END OF MODIFICATION --- + + const getDisplayMetrics = (scoreData) => { + if (!scoreData) return []; + return Object.keys(scoreData) + .filter(key => !['all', 'skin_age', 'takeaways', 'resize', 'image', 'url', 'resize_image', 'image_size'].includes(key)) + .map(key => { + const rawScore = hasDetailedData(scoreData, key) ? scoreData[key].ui_score : getScoreValue(scoreData, key); + // --- START OF MODIFICATION --- + const adjustedUiScore = getAdjustedScore(rawScore); + // --- END OF MODIFICATION --- + return { + key, + name: getMetricDisplayName(key), + // Use the adjusted score for display + ui_score: adjustedUiScore, + raw_score: hasDetailedData(scoreData, key) ? scoreData[key].raw_score : null, + output_mask_name: hasDetailedData(scoreData, key) ? scoreData[key].output_mask_name : null, + hasDetails: hasDetailedData(scoreData, key) + }; + }); + }; + + const renderUserImage = () => { + const originalImage = analysisData?.originalImage; + if (!originalImage) return null; + + return ( +
+ Face Analysis + {activeMask && ( + Analysis Mask + )} +
+ ); + }; + + const handleDownloadPDF = async () => { + if (!reportRef.current) return; + + // Show the hidden report briefly for capture + const reportElement = reportRef.current; + reportElement.style.display = 'block'; + + try { + const canvas = await html2canvas(reportElement, { + scale: 2, + useCORS: true, + allowTaint: true, + backgroundColor: '#ffffff' + }); + const imgData = canvas.toDataURL('image/png'); + + const pdf = new jsPDF('p', 'mm', 'a4'); + const pdfWidth = pdf.internal.pageSize.getWidth(); + const pdfHeight = (canvas.height * pdfWidth) / canvas.width; + + pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight); + pdf.save(`Limelite_Face_Report_${formData.name || 'User'}.pdf`); + } catch (err) { + console.error("PDF Generation Error:", err); + alert("Failed to generate PDF. Please try again."); + } finally { + reportElement.style.display = 'none'; + } + }; + + if (isLoading) return ( +
+
+
+

Analyzing skin profile...

+
+
+ ); + + if (error) return ( +
+
+
+ +
+

Analysis Failed

+

{errorDescriptions[error] || error}

+ +
+
+ ); + + if (!analysisData || !analysisData.scoreData) return ( +
+
+

No analysis data available.

+ +
+
+ ); + + const { scoreData } = analysisData; + const displayMetrics = getDisplayMetrics(scoreData); + + // --- START OF MODIFICATION --- + const rawOverallScore = getScoreValue(scoreData, 'all'); + const overallScore = getAdjustedScore(rawOverallScore); // Adjusted overall score + // --- END OF MODIFICATION --- + + const skinAge = getScoreValue(scoreData, 'skin_age'); + // Displayed skin age is 5 years younger than the calculated skin_age. + // Ensure it's a number and not negative. Round to nearest integer for display. + const displayedSkinAge = (skinAge !== null && skinAge !== undefined && !isNaN(Number(skinAge))) + ? Math.max(0, Math.round(Number(skinAge) - 5)) + : null; + const takeaways = scoreData.takeaways || []; + + // Calculate stroke dasharray for the main score circle + const radius = 54; + const circumference = 2 * Math.PI * radius; + const strokeDashoffset = circumference - ((overallScore || 0) / 100) * circumference; + + // --- SORTING FOR TOP CONCERNS --- + // Filter to get valid scores and sort by lowest score first + const displayedMetricsList = isVerified + ? displayMetrics.filter(m => m.ui_score !== null).sort((a, b) => a.ui_score - b.ui_score) + : displayMetrics.filter(m => m.ui_score !== null).sort((a, b) => a.ui_score - b.ui_score).slice(0, 3); + + return ( +
+ + {/* Header (Matching App.js) */} +
+
+ Limelite Logo +
+
+ + +
+
+ + {/* Main Content */} +
+ + {/* Image Section */} +
+ {renderUserImage()} +
+ + {/* Results Panel - Dark Theme (Rectangular) */} +
+ + {/* Main Score Row - With Border Separator */} +
+
+ {overallScore?.toFixed(0)} +
+ Skin Health +
+
+ + {activeMask && ( + + )} +
+ + {/* Top Concerns */} +
+

{isVerified ? 'ANALYSIS METRICS' : 'TOP CONCERNS'}

+
+ {displayedMetricsList.map((metric, idx) => ( +
isVerified && handleMetricClick(metric)} + className={`bg-[#2B2B2B] rounded-none flex flex-col items-center justify-between p-2 h-[145px] flex-shrink-0 w-[30%] relative group transition-all duration-300 ${isVerified ? 'cursor-pointer hover:bg-[#333]' : ''}`} + style={{ + border: selectedMetric === metric.key ? '1px solid transparent' : '1px solid #333', + backgroundImage: selectedMetric === metric.key ? 'linear-gradient(#2B2B2B, #2B2B2B), linear-gradient(135deg, #ffffff 0%, rgba(255,255,255,0.1) 40%, rgba(255,255,255,0.1) 60%, #ffffff 100%)' : 'none', + backgroundOrigin: 'border-box', + backgroundClip: 'padding-box, border-box' + }} + > +
+ {metric.ui_score} + {metric.name} +
+ + {/* Badge */} +
= 70 ? 'bg-emerald-500/20 text-[#4ade80] border border-emerald-500/30' : + metric.ui_score >= 40 ? 'bg-amber-500/20 text-[#fbbf24] border border-amber-500/30' : + 'bg-limelite-red/20 text-limelite-red border border-limelite-red/30' + }`}> + {metric.ui_score >= 70 ? 'Good' : metric.ui_score >= 40 ? 'Average' : 'Bad'} +
+
+ ))} + + {!isVerified && ( +
+ + + +
+ )} +
+
+
+ + {/* Footer CTA Section */} + {!isVerified ? ( +
+ +

+ Sign up to access overall analysis report +

+
+ ) : ( +
+ +
+ )} + +
+ + {/* Signup Modal */} + {showSignupModal && ( +
+
+ +
+

Sign Up

+ +
+ +
+
+ + + {formErrors.name &&

{formErrors.name}

} +
+ +
+ + + {formErrors.phone &&

{formErrors.phone}

} +
+ + +
+ +
+
+ )} + + {/* OTP Modal */} + {showOtpModal && ( +
+
+ +
+

OTP Verification

+ +
+ +

+ Please enter the OTP sent to you on whatsapp. +

+ +
+ {otp.map((data, index) => ( + handleOtpChange(e.target, index)} + onFocus={e => e.target.select()} + /> + ))} +
+ + {otpError &&

{otpError}

} + + + +
+
+ )} + + {/* Help Modal */} + {showHelpModal && ( +
+
+
+

Help

+ +
+ +
+
+ • +

+ To access previous reports, please contact us at hello@limelite.com +

+
+
+ • +

+ If you experience any technical issues, please contact us at support@example.com +

+
+
+
+
+ )} + + {/* Toast Notification */} + {toast.show && ( +
+
+
+ + + +
+ {toast.message} +
+
+ )} + + {/* Premium Hidden PDF Report Template */} +
+ {/* Header Section */} +
+
+ Logo +
+
+

Face Analysis Report

+
+
+
+ + {/* Accent Line */} +
+ + {/* Info Row (Name, Phone, Date) */} +
+
+

Name

+

{formData.name || 'Anonymous'}

+
+
+

Contact Number

+

+91 {formData.phone || 'N/A'}

+
+
+

Analysis Date

+

+ {(() => { + const d = new Date(); + const day = d.getDate(); + const suffix = (day > 3 && day < 21) || day % 10 > 3 ? 'th' : ['th', 'st', 'nd', 'rd'][day % 10]; + return `${day}${suffix} ${d.toLocaleString('en-GB', { month: 'long' })} ${d.getFullYear()}`; + })()} +

+
+
+ +
+ {/* Primary Imaging */} +
+
+ Original Scan +
+
Analyzed skin Profile
+
+ + {/* Overall Score Column */} +
+
+

Skin Health

+
+

{Math.round(overallScore)}

+
+
+ {/* Estimated Skin Age removed as requested */} +
+
+ + {/* Parameters Section */} +
+

+ + Diagnostic Deep-Dive +

+ +
+ {displayMetrics.filter(m => m.key !== 'all').map((metric, idx) => ( +
+
+ {metric.name} + = 70 ? '#10b981' : metric.ui_score >= 40 ? '#f59e0b' : '#ef4444' }}>{metric.ui_score}% +
+
+
= 70 ? '#10b981' : metric.ui_score >= 40 ? '#f59e0b' : 'limelite-red' + }}>
+
+
+ ))} +
+
+ + {/* Specialist Recommendations */} +
+

Prescriptive Guidance

+
+
+ STEP 01 +

Advanced Hydration: Apply hyaluronic acid serum daily to stabilize moisture levels.

+
+
+ STEP 02 +

Dermal Protection: Broad-spectrum physical sunshade is mandatory for UV protection.

+
+
+ STEP 03 +

Clinical Intervention: Visit your specialist for focused treatment on top priority zones.

+
+
+
+ + {/* Minimalist Footer */} +
+

+ * This diagnostic data is interpreted by Limelite AI Face Vision. For detailed medical prescriptions, please book a 1-on-1 therapist consultation. +

+
+

LIMELITE CLINICAL SYSTEMS

+

support@limelite.com | limelite.com

+
+
+
+
+ ); +}; + export default AnalysisResultPage; \ No newline at end of file diff --git a/frontend/src/DashboardPage.jsx b/frontend/src/DashboardPage.jsx index e23abda..6a8eeae 100644 --- a/frontend/src/DashboardPage.jsx +++ b/frontend/src/DashboardPage.jsx @@ -1,338 +1,340 @@ -import React, { useState, useEffect } from 'react'; -import API_BASE_URL from './config'; -import { useNavigate } from 'react-router'; -import { FiSearch, FiSettings, FiHelpCircle, FiChevronLeft, FiChevronRight, FiLogOut, FiArrowLeft, FiArrowRight } from 'react-icons/fi'; -import { HiOutlineDocumentText, HiOutlineUserGroup } from 'react-icons/hi'; - -const DashboardPage = () => { - const [searchTerm, setSearchTerm] = useState(''); - const [users, setUsers] = useState([]); - const navigate = useNavigate(); - - useEffect(() => { - const fetchUsers = async () => { - try { - const res = await fetch(`${API_BASE_URL}/api/dashboard/users`); - const data = await res.json(); - setUsers(data); - } catch (err) { - console.error("Failed to fetch users", err); - } - }; - fetchUsers(); - }, []); - - const handleLogout = () => { - // Here you would clear any persisted auth state if it existed - navigate('/admin'); - }; - const [showSupportModal, setShowSupportModal] = useState(false); - const [activeTab, setActiveTab] = useState('reports'); - const [currentPage, setCurrentPage] = useState(1); - const ITEMS_PER_PAGE = 8; - - // Settings state - const [settings, setSettings] = useState({ - siteName: 'Limelite Face Analyzer', - adminEmail: 'olivia@limelite.com', - whatsappNotifications: true, - autoArchive: false - }); - - - // Load users from backend - const reports = users.map(user => ({ - id: user.phone, - name: user.name, - phone: user.phone, - report: 'View Link', - link: user.lastLink - })); - - const filteredReports = reports.filter(report => - report.name.toLowerCase().includes(searchTerm.toLowerCase()) || - report.phone.includes(searchTerm) - ); - - const totalPages = Math.ceil(filteredReports.length / ITEMS_PER_PAGE); - const paginatedReports = filteredReports.slice( - (currentPage - 1) * ITEMS_PER_PAGE, - currentPage * ITEMS_PER_PAGE - ); - - const handlePageChange = (newPage) => { - if (newPage >= 1 && newPage <= totalPages) { - setCurrentPage(newPage); - } - }; - - const renderPageNumbers = () => { - const pages = []; - if (totalPages <= 7) { - for (let i = 1; i <= totalPages; i++) pages.push(i); - } else { - if (currentPage <= 4) { - pages.push(1, 2, 3, 4, 5, '...', totalPages); - } else if (currentPage >= totalPages - 3) { - pages.push(1, '...', totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages); - } else { - pages.push(1, '...', currentPage - 1, currentPage, currentPage + 1, '...', totalPages); - } - } - - return pages.map((page, index) => ( - - )); - }; - - - - return ( -
- {/* Sidebar */} - - - {/* Main Content */} -
- {/* Header */} -
-

- {activeTab === 'reports' ? 'Welcome back, Admin' : 'System Settings'} -

- - {activeTab === 'reports' && ( -
- - setSearchTerm(e.target.value)} - /> -
- )} -
- - {/* Content Area */} -
- {activeTab === 'reports' ? ( -
-
-

Reports

-
- - -
- - - - - - - - - - - {paginatedReports.map((report, index) => ( - - - - - - - ))} - -
S.NoNameMobile NumberReport
{(currentPage - 1) * ITEMS_PER_PAGE + index + 1}{report.name}{report.phone} - - {report.link ? report.link.substring(report.link.indexOf('result')) : '-'} - -
-
- - {/* Pagination */} -
- - -
- {renderPageNumbers()} -
- - -
-
- ) : ( -
-

General Settings

- -
-
- - setSettings({ ...settings, siteName: e.target.value })} - /> -
- -
- - setSettings({ ...settings, adminEmail: e.target.value })} - /> -
- -
-
-

WhatsApp Notifications

-

Send automatic alerts for new analysis signups

-
- -
- -
-
-

Auto-Archive Reports

-

Automatically archive reports older than 30 days

-
- -
- -
- -
-
-
- )} -
-
- - {/* Support Modal */} - {showSupportModal && ( -
-
-
-

Help

- -
- -
-
-
-

- To access previous reports, please contact us at hello@limelite.com -

-
- -
-
-

- If you experience any technical issues, please contact us at support@example.com -

-
-
-
-
- )} -
- ); -}; - -export default DashboardPage; +import React, { useState, useEffect } from 'react'; +import API_BASE_URL from './config'; +import { useNavigate } from 'react-router'; +import { FiSearch, FiSettings, FiHelpCircle, FiChevronLeft, FiChevronRight, FiLogOut, FiArrowLeft, FiArrowRight } from 'react-icons/fi'; +import { HiOutlineDocumentText, HiOutlineUserGroup } from 'react-icons/hi'; + +const DashboardPage = () => { + const [searchTerm, setSearchTerm] = useState(''); + const [users, setUsers] = useState([]); + const navigate = useNavigate(); + + useEffect(() => { + const fetchUsers = async () => { + try { + const res = await fetch(`${API_BASE_URL}/api/dashboard/users`); + const data = await res.json(); + setUsers(data); + } catch (err) { + console.error("Failed to fetch users", err); + } + }; + fetchUsers(); + }, []); + + const handleLogout = () => { + // Here you would clear any persisted auth state if it existed + navigate('/admin'); + }; + const [showSupportModal, setShowSupportModal] = useState(false); + const [activeTab, setActiveTab] = useState('reports'); + const [currentPage, setCurrentPage] = useState(1); + const ITEMS_PER_PAGE = 8; + + // Settings state + const [settings, setSettings] = useState({ + siteName: 'Limelite Face Analyzer', + adminEmail: 'olivia@limelite.com', + whatsappNotifications: true, + autoArchive: false + }); + + + // Load users from backend + const reports = users.map(user => ({ + id: user.phone, + name: user.name, + phone: user.phone, + report: 'View Link', + link: user.lastLink + })); + + const filteredReports = reports.filter(report => + report.name.toLowerCase().includes(searchTerm.toLowerCase()) || + report.phone.includes(searchTerm) + ); + + const totalPages = Math.ceil(filteredReports.length / ITEMS_PER_PAGE); + const paginatedReports = filteredReports.slice( + (currentPage - 1) * ITEMS_PER_PAGE, + currentPage * ITEMS_PER_PAGE + ); + + const handlePageChange = (newPage) => { + if (newPage >= 1 && newPage <= totalPages) { + setCurrentPage(newPage); + } + }; + + const renderPageNumbers = () => { + const pages = []; + if (totalPages <= 7) { + for (let i = 1; i <= totalPages; i++) pages.push(i); + } else { + if (currentPage <= 4) { + pages.push(1, 2, 3, 4, 5, '...', totalPages); + } else if (currentPage >= totalPages - 3) { + pages.push(1, '...', totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages); + } else { + pages.push(1, '...', currentPage - 1, currentPage, currentPage + 1, '...', totalPages); + } + } + + return pages.map((page, index) => ( + + )); + }; + + + + return ( +
+ {/* Sidebar */} + + + {/* Main Content */} +
+ {/* Header */} +
+

+ {activeTab === 'reports' ? 'Welcome back, Admin' : 'System Settings'} +

+ + {activeTab === 'reports' && ( +
+ + setSearchTerm(e.target.value)} + /> +
+ )} +
+ + {/* Content Area */} +
+ {activeTab === 'reports' ? ( +
+
+

Reports

+
+ + +
+ + + + + + + + + + + {paginatedReports.map((report, index) => ( + + + + + + + ))} + +
S.NoNameMobile NumberReport
{(currentPage - 1) * ITEMS_PER_PAGE + index + 1}{report.name}{report.phone} + {report.link ? ( + + View Report + + ) : '-'} +
+
+ + {/* Pagination */} +
+ + +
+ {renderPageNumbers()} +
+ + +
+
+ ) : ( +
+

General Settings

+ +
+
+ + setSettings({ ...settings, siteName: e.target.value })} + /> +
+ +
+ + setSettings({ ...settings, adminEmail: e.target.value })} + /> +
+ +
+
+

WhatsApp Notifications

+

Send automatic alerts for new analysis signups

+
+ +
+ +
+
+

Auto-Archive Reports

+

Automatically archive reports older than 30 days

+
+ +
+ +
+ +
+
+
+ )} +
+
+ + {/* Support Modal */} + {showSupportModal && ( +
+
+
+

Help

+ +
+ +
+
+
+

+ To access previous reports, please contact us at hello@limelite.com +

+
+ +
+
+

+ If you experience any technical issues, please contact us at support@example.com +

+
+
+
+
+ )} +
+ ); +}; + +export default DashboardPage; diff --git a/patch_backend.json b/patch_backend.json new file mode 100644 index 0000000..ce504c3 --- /dev/null +++ b/patch_backend.json @@ -0,0 +1,10 @@ +{ + "name": "limelite-backend", + "build_pack": "dockerfile", + "base_directory": "/backend", + "dockerfile_location": "/Dockerfile", + "ports_exposes": "5000", + "git_repository": "https://mohanki:48d38a221949649fdf1b470bbd037de9a5e6c564@git.digifox.live/mohanki/limelite_faceanalyzer.git", + "git_branch": "master", + "custom_internal_name": "backend" +} diff --git a/patch_compose.json b/patch_compose.json new file mode 100644 index 0000000..82a8d67 --- /dev/null +++ b/patch_compose.json @@ -0,0 +1,3 @@ +{ + "docker_compose_raw": "version: '3.8'\n\nservices:\n frontend:\n build:\n context: ./frontend\n ports:\n - \"9500:80\"\n depends_on:\n - backend\n\n backend:\n build:\n context: ./backend\n ports:\n - \"9501:5000\"\n environment:\n - PORT=5000\n - MONGO_URI=mongodb://mongo:27017/limelite\n depends_on:\n - mongo\n\n mongo:\n image: mongo:latest\n ports:\n - \"9502:27017\"\n volumes:\n - mongo-data:/data/db\n\nvolumes:\n mongo-data: {}" +} diff --git a/patch_frontend.json b/patch_frontend.json new file mode 100644 index 0000000..76fecf0 --- /dev/null +++ b/patch_frontend.json @@ -0,0 +1,10 @@ +{ + "name": "limelite-frontend", + "build_pack": "dockerfile", + "base_directory": "/frontend", + "dockerfile_location": "/Dockerfile", + "ports_exposes": "80", + "git_repository": "https://mohanki:48d38a221949649fdf1b470bbd037de9a5e6c564@git.digifox.live/mohanki/limelite_faceanalyzer.git", + "git_branch": "master", + "custom_internal_name": "frontend" +} diff --git a/patch_frontend_dns.json b/patch_frontend_dns.json new file mode 100644 index 0000000..576dc8b --- /dev/null +++ b/patch_frontend_dns.json @@ -0,0 +1,3 @@ +{ + "fqdn": "http://limelite-faceanalyzer.houseofwebsites.ai" +} diff --git a/patch_git.json b/patch_git.json new file mode 100644 index 0000000..528ce74 --- /dev/null +++ b/patch_git.json @@ -0,0 +1,3 @@ +{ + "git_repository": "https://mohanki:48d38a221949649fdf1b470bbd037de9a5e6c564@git.digifox.live/mohanki/limelite_faceanalyzer.git" +} diff --git a/patch_loc.json b/patch_loc.json new file mode 100644 index 0000000..6d55719 --- /dev/null +++ b/patch_loc.json @@ -0,0 +1,3 @@ +{ + "docker_compose_location": "docker-compose.yml" +} diff --git a/payload_backend_contabo.json b/payload_backend_contabo.json new file mode 100644 index 0000000..139c42a --- /dev/null +++ b/payload_backend_contabo.json @@ -0,0 +1,13 @@ +{ + "name": "limelite-backend", + "project_uuid": "kozt3dmpa63penhin84sb8r0", + "server_uuid": "nt9z6kv7y3i644bzzsifjwnx", + "environment_name": "production", + "git_repository": "https://mohanki:48d38a221949649fdf1b470bbd037de9a5e6c564@git.digifox.live/mohanki/limelite_faceanalyzer.git", + "git_branch": "master", + "build_pack": "dockerfile", + "base_directory": "/backend", + "dockerfile_location": "/Dockerfile", + "ports_exposes": "5000", + "custom_internal_name": "backend" +} diff --git a/payload_db.json b/payload_db.json new file mode 100644 index 0000000..1560164 --- /dev/null +++ b/payload_db.json @@ -0,0 +1,6 @@ +{ + "name": "limelite-mongo", + "project_uuid": "kozt3dmpa63penhin84sb8r0", + "environment_name": "production", + "server_uuid": "jrcjtobvncyba40v3vi6pzqe" +} diff --git a/payload_db_contabo.json b/payload_db_contabo.json new file mode 100644 index 0000000..13b8cbd --- /dev/null +++ b/payload_db_contabo.json @@ -0,0 +1,6 @@ +{ + "name": "limelite-mongo", + "project_uuid": "kozt3dmpa63penhin84sb8r0", + "environment_name": "production", + "server_uuid": "nt9z6kv7y3i644bzzsifjwnx" +} diff --git a/payload_envs.json b/payload_envs.json new file mode 100644 index 0000000..90f7013 --- /dev/null +++ b/payload_envs.json @@ -0,0 +1,4 @@ +{ + "key": "MONGO_URI", + "value": "mongodb://root:8Oef5zsT9dsAEsVnfREj8763ZTtuTFcUglMEvg1QvQt2wlki0Li6Ynmxy7kcU6wZ@77ll26aup0vxgzxxhko2dxot:27017/limelite?authSource=admin&directConnection=true" +} diff --git a/payload_envs_contabo.json b/payload_envs_contabo.json new file mode 100644 index 0000000..1519316 --- /dev/null +++ b/payload_envs_contabo.json @@ -0,0 +1,4 @@ +{ + "key": "MONGO_URI", + "value": "mongodb://root:TYXnbgf5b9uwaNRpeddIYx2B6ESSp7WLxCXaiSfLKayGhqwkgBDcR0p5HPRc9vGc@7jtniyem6lv8dlsex0c0ycv9:27017/limelite?authSource=admin&directConnection=true" +} diff --git a/payload_envs_port.json b/payload_envs_port.json new file mode 100644 index 0000000..020ffce --- /dev/null +++ b/payload_envs_port.json @@ -0,0 +1,4 @@ +{ + "key": "PORT", + "value": "5000" +} diff --git a/payload_frontend_contabo.json b/payload_frontend_contabo.json new file mode 100644 index 0000000..e6d7414 --- /dev/null +++ b/payload_frontend_contabo.json @@ -0,0 +1,14 @@ +{ + "name": "limelite-frontend", + "project_uuid": "kozt3dmpa63penhin84sb8r0", + "server_uuid": "nt9z6kv7y3i644bzzsifjwnx", + "environment_name": "production", + "git_repository": "https://mohanki:48d38a221949649fdf1b470bbd037de9a5e6c564@git.digifox.live/mohanki/limelite_faceanalyzer.git", + "git_branch": "master", + "build_pack": "dockerfile", + "base_directory": "/frontend", + "dockerfile_location": "/Dockerfile", + "ports_exposes": "80", + "domains": "https://limelite-faceanalyzer.houseofwebsites.ai,https://www.limelite-faceanalyzer.houseofwebsites.ai", + "custom_internal_name": "frontend" +} diff --git a/payload_mongo.json b/payload_mongo.json new file mode 100644 index 0000000..2db0d0d --- /dev/null +++ b/payload_mongo.json @@ -0,0 +1,10 @@ +{ + "name": "limelite-mongo-test", + "project_uuid": "kozt3dmpa63penhin84sb8r0", + "server_uuid": "jrcjtobvncyba40v3vi6pzqe", + "environment_name": "production", + "docker_registry_image_name": "mongo", + "docker_registry_image_tag": "latest", + "build_pack": "dockerimage", + "ports_exposes": "27017" +} diff --git a/payload_v3.json b/payload_v3.json new file mode 100644 index 0000000..bc118fa --- /dev/null +++ b/payload_v3.json @@ -0,0 +1,11 @@ +{ + "name": "limelite-faceanalyzer-v3", + "project_uuid": "kozt3dmpa63penhin84sb8r0", + "server_uuid": "jrcjtobvncyba40v3vi6pzqe", + "environment_name": "production", + "git_repository": "https://git.digifox.live/mohanki/limelite_faceanalyzer.git", + "git_branch": "master", + "build_pack": "dockercompose", + "docker_compose_location": "/docker-compose.yml", + "ports_exposes": "9500" +} diff --git a/payload_v4.json b/payload_v4.json new file mode 100644 index 0000000..dd28526 --- /dev/null +++ b/payload_v4.json @@ -0,0 +1,12 @@ +{ + "name": "limelite-faceanalyzer-v4", + "project_uuid": "kozt3dmpa63penhin84sb8r0", + "server_uuid": "jrcjtobvncyba40v3vi6pzqe", + "environment_name": "production", + "git_repository": "https://git.digifox.live/mohanki/limelite_faceanalyzer.git", + "git_branch": "master", + "build_pack": "dockercompose", + "docker_compose_location": "/docker-compose.yml", + "docker_compose_raw": "version: '3.8'\n\nservices:\n frontend:\n build:\n context: ./frontend\n ports:\n - \"9500:80\"\n depends_on:\n - backend\n\n backend:\n build:\n context: ./backend\n ports:\n - \"9501:5000\"\n environment:\n - PORT=5000\n - MONGO_URI=mongodb://mongo:27017/limelite\n depends_on:\n - mongo\n\n mongo:\n image: mongo:latest\n ports:\n - \"9502:27017\"\n volumes:\n - mongo-data:/data/db\n\nvolumes:\n mongo-data: {}", + "ports_exposes": "9500" +} diff --git a/scratch/check_apps.js b/scratch/check_apps.js new file mode 100644 index 0000000..443a60d --- /dev/null +++ b/scratch/check_apps.js @@ -0,0 +1,18 @@ +const fs = require('fs'); + +async function run() { + const res = await fetch('https://deploy.digifox.live/api/v1/projects/kozt3dmpa63penhin84sb8r0/ijga3o6m6arup69yohj2ecyw', { + headers: { + 'Authorization': 'Bearer 4|lL3pgQVbfLb6KtUByDLaswjP4Jccny8HMj5jrs0v4ace4e78', + 'Accept': 'application/json' + } + }); + const data = await res.json(); + for (const app of data.applications || []) { + console.log(`App: ${app.name}, UUID: ${app.uuid}`); + console.log(` Internal Name: ${app.settings?.custom_internal_name || 'NONE'}`); + console.log(` FQDN: ${app.fqdn}`); + console.log(` Repo: ${app.git_repository}#${app.git_branch}`); + } +} +run();