Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4d8652d
Add files via upload
Kosztyk Dec 2, 2025
a30e543
Add files via upload
Kosztyk Dec 2, 2025
e67c1b3
Add files via upload
Kosztyk Dec 2, 2025
4c28300
Configure ClamAV REST API and change port mapping
Kosztyk Dec 2, 2025
4697b15
Add files via upload
Kosztyk Dec 4, 2025
718f87e
Add files via upload
Kosztyk Dec 4, 2025
179abac
Add files via upload
Kosztyk Dec 4, 2025
6b2c737
Add files via upload
Kosztyk Dec 4, 2025
48d1ce5
Add files via upload
Kosztyk Dec 4, 2025
f2f48c9
Add files via upload
Kosztyk Dec 4, 2025
d9bb395
Add files via upload
Kosztyk Dec 4, 2025
dff8926
Add files via upload
Kosztyk Dec 4, 2025
e629fca
Add files via upload
Kosztyk Dec 4, 2025
5519374
Add files via upload
Kosztyk Dec 4, 2025
6fe7899
Add files via upload
Kosztyk Dec 4, 2025
d4be44c
Add files via upload
Kosztyk Dec 4, 2025
3420464
Add files via upload
Kosztyk Dec 4, 2025
8ec2a22
Update package.json
Kosztyk Dec 4, 2025
55fa5ae
Add files via upload
Kosztyk Dec 4, 2025
4fd9862
Add files via upload
Kosztyk Dec 4, 2025
1cdbd9e
Add files via upload
Kosztyk Dec 4, 2025
60d4a48
Update README.md
Kosztyk Dec 5, 2025
97ddf01
Add files via upload
Kosztyk Dec 8, 2025
96e7123
Add files via upload
Kosztyk Dec 8, 2025
5083935
Add files via upload
Kosztyk Dec 8, 2025
3df62fc
Update package.json
Kosztyk Dec 26, 2025
09e0e84
Merge branch 'main' into main
Kosztyk Dec 26, 2025
9c30944
Merge branch 'main' into main
Kosztyk Dec 27, 2025
61894a7
Merge branch 'C4illin:main' into main
Kosztyk Jan 4, 2026
da67096
Merge branch 'main' into main
Kosztyk Jan 5, 2026
5e19e84
Add files via upload
Kosztyk Jan 5, 2026
2d5c8a0
Add files via upload
Kosztyk Jan 5, 2026
e4624a8
Delete src/helpers/erugo.ts.crdownload
Kosztyk Jan 5, 2026
3bcb511
Delete src/helpers/env.ts.crdownload
Kosztyk Jan 5, 2026
dbd1805
Add files via upload
Kosztyk Jan 5, 2026
86e67d4
Add files via upload
Kosztyk Jan 5, 2026
8eda62f
Add files via upload
Kosztyk Jan 5, 2026
2d954a4
Add files via upload
Kosztyk Jan 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,20 @@ Use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summar
<img src="https://contrib.rocks/image?repo=C4illin/ConvertX" alt="Image with all contributors"/>
</a>

## Files updated:

src/db/types.ts
src/db/db.ts
src/pages/root.tsx
src/pages/user.tsx
src/pages/upload.tsx
src/pages/antivirus.tsx
src/components/base.tsx
public/script.js
public/theme-init.js
public/theme.css
src/main.css

![Alt](https://repobeats.axiom.co/api/embed/dcdabd0564fcdcccbf5680c1bdc2efad54a3d4d9.svg "Repobeats analytics image")

## Star History
Expand Down
29 changes: 28 additions & 1 deletion compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,32 @@ services:
# - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
- TZ=Europe/Stockholm # set your timezone, defaults to UTC
# - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Convertx injects CLAMAV_URL pointing to a non-existent Docker hostname (clam_av_api), so every server-side ClamAV fetch fails and uploads are never actually scanned, bypassing the antivirus workflow.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Convertx injects `CLAMAV_URL` pointing to a non-existent Docker hostname (`clam_av_api`), so every server-side ClamAV fetch fails and uploads are never actually scanned, bypassing the antivirus workflow.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
+ - CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Service hostname mismatch: clam_av_api should be clamav-rest-api to match the actual service name defined in this compose file. Docker Compose uses service names for internal DNS resolution.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Service hostname mismatch: `clam_av_api` should be `clamav-rest-api` to match the actual service name defined in this compose file. Docker Compose uses service names for internal DNS resolution.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
- CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Hostname mismatch: clam_av_api should be clamav-rest-api to match the service name. Docker Compose uses service names as DNS hostnames within the network.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Hostname mismatch: `clam_av_api` should be `clamav-rest-api` to match the service name. Docker Compose uses service names as DNS hostnames within the network.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
- CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Service name mismatch: clam_av_api doesn't match the defined service name clamav-rest-api. Docker Compose uses service names for DNS resolution, so this URL will fail to resolve.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Service name mismatch: `clam_av_api` doesn&#39;t match the defined service name `clamav-rest-api`. Docker Compose uses service names for DNS resolution, so this URL will fail to resolve.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
- CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Hostname mismatch: clam_av_api does not match the service name clamav-rest-api. Docker Compose uses service names as DNS hostnames, so this URL will fail to resolve.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Hostname mismatch: `clam_av_api` does not match the service name `clamav-rest-api`. Docker Compose uses service names as DNS hostnames, so this URL will fail to resolve.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
- CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Service name mismatch: CLAMAV_URL references clam_av_api but the service is named clamav-rest-api. The antivirus scanning will fail to connect. Use http://clamav-rest-api:3000/api/v1/scan instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 19:

<comment>Service name mismatch: `CLAMAV_URL` references `clam_av_api` but the service is named `clamav-rest-api`. The antivirus scanning will fail to connect. Use `http://clamav-rest-api:3000/api/v1/scan` instead.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
       # - HIDE_HISTORY=true # hides the history tab in the web interface, defaults to false
       - TZ=Europe/Stockholm # set your timezone, defaults to UTC
       # - UNAUTHENTICATED_USER_SHARING=true # for use with ALLOW_UNAUTHENTICATED=true to share history with all unauthenticated users / devices
+      - CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
     ports:
-      - 3000:3000
</file context>
Suggested change
- CLAMAV_URL=http://clam_av_api:3000/api/v1/scan
- CLAMAV_URL=http://clamav-rest-api:3000/api/v1/scan
Fix with Cubic

ports:
- 3000:3000
- 8080:3000

clamav-rest-api:
image: benzino77/clamav-rest-api:latest
container_name: clamav-rest-api
restart: unless-stopped
environment:
- NODE_ENV=production
# field name expected in the multipart form
- APP_FORM_KEY=FILES
# talk to your existing ClamAV daemon
- CLAMD_IP=CLAMAV_server_IP
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: CLAMD_IP is left as the placeholder CLAMAV_server_IP even though the ClamAV daemon service defined below is named clamav. The REST API container will fail to connect to the daemon, so scans will always fail.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>`CLAMD_IP` is left as the placeholder `CLAMAV_server_IP` even though the ClamAV daemon service defined below is named `clamav`. The REST API container will fail to connect to the daemon, so scans will always fail.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Placeholder value not configured: CLAMAV_server_IP should be clamav to reference the ClamAV daemon service defined in this compose file. Docker Compose provides DNS resolution using service names.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>Placeholder value not configured: `CLAMAV_server_IP` should be `clamav` to reference the ClamAV daemon service defined in this compose file. Docker Compose provides DNS resolution using service names.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Placeholder value: CLAMAV_server_IP should be clamav to reference the ClamAV daemon service defined in this compose file.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>Placeholder value: `CLAMAV_server_IP` should be `clamav` to reference the ClamAV daemon service defined in this compose file.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Placeholder value CLAMAV_server_IP needs to be replaced with the actual ClamAV service name. Since the clamav service is in the same compose file, use clamav as the hostname.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>Placeholder value `CLAMAV_server_IP` needs to be replaced with the actual ClamAV service name. Since the `clamav` service is in the same compose file, use `clamav` as the hostname.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Placeholder value not replaced: CLAMAV_server_IP should be clamav to reference the ClamAV daemon service defined in the same compose file.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>Placeholder value not replaced: `CLAMAV_server_IP` should be `clamav` to reference the ClamAV daemon service defined in the same compose file.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Placeholder value not replaced: CLAMD_IP=CLAMAV_server_IP should be CLAMD_IP=clamav to reference the clamav service defined in this compose file. The REST API won't be able to connect to the ClamAV daemon.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 32:

<comment>Placeholder value not replaced: `CLAMD_IP=CLAMAV_server_IP` should be `CLAMD_IP=clamav` to reference the clamav service defined in this compose file. The REST API won&#39;t be able to connect to the ClamAV daemon.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # field name expected in the multipart form
+      - APP_FORM_KEY=FILES
+      # talk to your existing ClamAV daemon
+      - CLAMD_IP=CLAMAV_server_IP
+      - CLAMD_PORT=3310
+      # max allowed file size (here: 250 MB)
</file context>
Suggested change
- CLAMD_IP=CLAMAV_server_IP
- CLAMD_IP=clamav
Fix with Cubic

- CLAMD_PORT=3310
# max allowed file size (here: 250 MB)
- APP_MAX_FILE_SIZE=262144000
ports:
# outside:inside
- "3000:3000"

clamav:
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: YAML indentation error: The clamav service has 3 spaces of indentation instead of 2, which will cause a YAML parsing error and prevent the compose file from being loaded.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 40:

<comment>YAML indentation error: The `clamav` service has 3 spaces of indentation instead of 2, which will cause a YAML parsing error and prevent the compose file from being loaded.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # outside:inside
+      - &quot;3000:3000&quot;
+
+   clamav:
+    image: clamav/clamav:latest
+    container_name: clamav
</file context>
Suggested change
clamav:
clamav:
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: YAML syntax error: The clamav: service has 3 spaces indentation instead of 2 spaces like other services. This will cause a parsing error.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 40:

<comment>YAML syntax error: The `clamav:` service has 3 spaces indentation instead of 2 spaces like other services. This will cause a parsing error.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # outside:inside
+      - &quot;3000:3000&quot;
+
+   clamav:
+    image: clamav/clamav:latest
+    container_name: clamav
</file context>
Suggested change
clamav:
clamav:
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: YAML indentation error: clamav: has 3 spaces instead of 2. This will cause a YAML parsing error and prevent Docker Compose from running.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 40:

<comment>YAML indentation error: `clamav:` has 3 spaces instead of 2. This will cause a YAML parsing error and prevent Docker Compose from running.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # outside:inside
+      - &quot;3000:3000&quot;
+
+   clamav:
+    image: clamav/clamav:latest
+    container_name: clamav
</file context>
Suggested change
clamav:
clamav:
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: YAML indentation error: This line has 3 spaces instead of 2, which breaks YAML parsing. Service definitions must be consistently indented.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 40:

<comment>YAML indentation error: This line has 3 spaces instead of 2, which breaks YAML parsing. Service definitions must be consistently indented.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # outside:inside
+      - &quot;3000:3000&quot;
+
+   clamav:
+    image: clamav/clamav:latest
+    container_name: clamav
</file context>
Suggested change
clamav:
clamav:
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: YAML indentation error: clamav service has 3 spaces instead of 2. This will cause YAML parsing to fail or produce unexpected structure.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At compose.yaml, line 40:

<comment>YAML indentation error: `clamav` service has 3 spaces instead of 2. This will cause YAML parsing to fail or produce unexpected structure.</comment>

<file context>
@@ -16,5 +16,32 @@ services:
+      # outside:inside
+      - &quot;3000:3000&quot;
+
+   clamav:
+    image: clamav/clamav:latest
+    container_name: clamav
</file context>
Suggested change
clamav:
clamav:
Fix with Cubic

image: clamav/clamav:latest
container_name: clamav
restart: unless-stopped
ports:
- "3310:3310"
environment:
- CLAMAV_NO_FRESHCLAMD=false
248 changes: 216 additions & 32 deletions public/results.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,222 @@
const webroot = document.querySelector("meta[name='webroot']").content;
const jobId = window.location.pathname.split("/").pop();
const main = document.querySelector("main");
let progressElem = document.querySelector("progress");

const refreshData = () => {
// console.log("Refreshing data...", progressElem.value, progressElem.max);
if (progressElem.value !== progressElem.max) {
fetch(`${webroot}/progress/${jobId}`, {
method: "POST",
})
.then((res) => res.text())
.then((html) => {
main.innerHTML = html;
})
.catch((err) => console.log(err));

setTimeout(refreshData, 1000);
// public/results.js
//
// Handles live progress updates for /results/:jobId and Share via Erugo modal actions.
// IMPORTANT: /progress/:jobId re-renders the <main> content, so we must not keep stale
// element references. Use event delegation + (re)query elements when needed.

(function () {
const webrootMeta = document.querySelector("meta[name='webroot']");
const webroot = webrootMeta ? webrootMeta.content : "";
const jobId = window.location.pathname.split("/").pop();
const main = document.querySelector("main");

// -----------------------------
// Progress refresh
// -----------------------------
async function refreshData() {
if (!main || !jobId) return;

const progressElem = main.querySelector("progress");
if (!progressElem) return;

const max = Number(progressElem.getAttribute("max") || "0");
const val = Number(progressElem.getAttribute("value") || "0");

// Only refresh while still processing
if (max > 0 && val >= max) return;

try {
const res = await fetch(`${webroot}/progress/${jobId}`, { method: "POST", cache: "no-store" });
const html = await res.text();
main.innerHTML = html;
} catch (err) {
console.error("[ConvertX] progress refresh failed", err);
}
}

// Poll every second while job is running
setInterval(refreshData, 1000);
// Run once immediately so the page updates without waiting 1s.
refreshData();

// -----------------------------
// Share modal helpers
// -----------------------------
function getEls() {
return {
modal: document.getElementById("cxShareModal"),
closeBtn: document.getElementById("cxShareClose"),
cancelBtn: document.getElementById("cxShareCancel"),
submitBtn: document.getElementById("cxShareSubmit"),
statusEl: document.getElementById("cxShareStatus"),

emailEl: document.getElementById("cxShareEmail"),
nameEl: document.getElementById("cxShareName"),
descEl: document.getElementById("cxShareDescription"),

linkBlock: document.getElementById("cxShareLinkBlock"),
linkEl: document.getElementById("cxShareLink"),
copyBtn: document.getElementById("cxShareCopy"),
};
}

let currentJobId = null;
let currentFileName = null;

function openModal(jobIdValue, fileNameValue) {
const els = getEls();
if (!els.modal) return;

currentJobId = jobIdValue;
currentFileName = fileNameValue;

if (els.nameEl) els.nameEl.value = fileNameValue || "";
if (els.emailEl) els.emailEl.value = "";
if (els.descEl) els.descEl.value = "";

if (els.linkBlock) els.linkBlock.classList.add("hidden");
if (els.linkEl) els.linkEl.value = "";
if (els.statusEl) els.statusEl.textContent = "";

els.modal.classList.remove("hidden");
els.modal.classList.add("flex");

if (els.emailEl) els.emailEl.focus();
}

progressElem = document.querySelector("progress");
};
function closeModal() {
const els = getEls();
if (!els.modal) return;

refreshData();
els.modal.classList.add("hidden");
els.modal.classList.remove("flex");

window.downloadAll = function () {
// Get all download links
const downloadLinks = document.querySelectorAll("tbody a[download]");
currentJobId = null;
currentFileName = null;
}

// Trigger download for each link
downloadLinks.forEach((link, index) => {
// We add a delay for each download to prevent them from starting at the same time
setTimeout(() => {
const event = new MouseEvent("click");
link.dispatchEvent(event);
}, index * 300);
async function shareFile(jobIdValue, fileNameValue) {
const els = getEls();
if (!els.submitBtn || !els.statusEl) return;

const recipientEmail = (els.emailEl && els.emailEl.value ? els.emailEl.value.trim() : "") || undefined;
const shareName = (els.nameEl && els.nameEl.value ? els.nameEl.value.trim() : "") || undefined;
const description = (els.descEl && els.descEl.value ? els.descEl.value.trim() : "") || undefined;

els.submitBtn.disabled = true;
els.submitBtn.setAttribute("aria-busy", "true");
els.statusEl.textContent = "Sending...";

try {
const payload = { fileName: fileNameValue, recipientEmail, shareName, description };

const res = await fetch(`${webroot}/share-to-erugo/${jobIdValue}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});

const text = await res.text();
let json = null;
try {
json = text ? JSON.parse(text) : null;
} catch {
json = { raw: text };
}

if (!res.ok) {
console.error("[ConvertX] share-to-erugo failed", res.status, json);
els.statusEl.textContent = "Failed. See logs.";
return;
}

const url =
(json && (json.share_url || json.share_link)) ||
(json && json.data && json.data.url) ||
(json && json.data && json.data.share && json.data.share.url) ||
null;

if (url && els.linkEl && els.linkBlock) {
els.linkEl.value = url;
els.linkBlock.classList.remove("hidden");
}

if (recipientEmail) {
els.statusEl.textContent = url ? "Sent. Link also shown below." : "Sent.";
} else {
els.statusEl.textContent = url ? "Created. Copy the link below." : "Created.";
}
} catch (err) {
console.error(err);
els.statusEl.textContent = "Failed. See logs.";
} finally {
els.submitBtn.disabled = false;
els.submitBtn.removeAttribute("aria-busy");
}
}

// -----------------------------
// Global event handlers (delegated)
// -----------------------------
document.addEventListener("click", async (event) => {
const target = event.target;

// Share icon buttons inside the table
const shareBtn = target && target.closest ? target.closest("button[data-share='true']") : null;
if (shareBtn) {
const jobIdAttr = shareBtn.getAttribute("data-job-id");
const fileNameAttr = shareBtn.getAttribute("data-file-name");
if (!jobIdAttr || !fileNameAttr) return;
openModal(jobIdAttr, fileNameAttr);
return;
}

// Modal close/cancel
if (target && target.id === "cxShareClose") {
closeModal();
return;
}
if (target && target.id === "cxShareCancel") {
closeModal();
return;
}

// Click outside modal (on overlay)
const els = getEls();
if (els.modal && target === els.modal) {
closeModal();
return;
}

// Submit
if (target && target.id === "cxShareSubmit") {
if (!currentJobId || !currentFileName) return;
await shareFile(currentJobId, currentFileName);
return;
}

// Copy
if (target && target.id === "cxShareCopy") {
const e = getEls();
if (!e.linkEl || !e.statusEl) return;
try {
await navigator.clipboard.writeText(e.linkEl.value);
e.statusEl.textContent = "Copied.";
} catch {
e.linkEl.focus();
e.linkEl.select();
e.statusEl.textContent = "Select + copy (Ctrl/Cmd+C).";
}
return;
}
});

document.addEventListener("keydown", (event) => {
if (event.key !== "Escape") return;
const els = getEls();
if (els.modal && !els.modal.classList.contains("hidden")) {
closeModal();
}
});
};
})();

Loading