Compare commits

...

12 Commits

Author SHA1 Message Date
sam
5028a2439e fix
All checks were successful
Docker Deploy / build-and-deploy (push) Successful in 21s
2026-02-06 11:47:09 +00:00
sam
1e78d99624 opt
All checks were successful
Docker Deploy / build-and-deploy (push) Successful in 21s
2026-02-06 11:38:13 +00:00
sam
89d1b15543 multi delete
All checks were successful
Docker Deploy / build-and-deploy (push) Successful in 6s
2026-02-06 11:21:42 +00:00
sam
82c7457ed1 new
All checks were successful
Docker Deploy / build-and-deploy (push) Successful in 17s
2026-02-06 11:13:14 +00:00
sam
f5adb1000c yaml
Some checks failed
Docker Deploy / build-and-deploy (push) Failing after 18s
2026-02-06 10:57:23 +00:00
sam
36abceafb1 back button
Some checks failed
Docker Deploy / build-and-deploy (push) Failing after 6s
2026-02-06 10:54:30 +00:00
sam
a9774f41c9 port
All checks were successful
Docker Deploy / build-and-deploy (push) Successful in 4m3s
2026-02-06 10:37:03 +00:00
sam
9d88dbc90f ss
All checks were successful
Docker Deploy / build-and-deploy (push) Successful in 5s
2026-02-06 09:49:19 +00:00
sam
89b9c6ea36 d
Some checks failed
Docker Deploy / build-and-deploy (push) Failing after 7s
2026-02-06 09:46:15 +00:00
sam
c380b01e49 doc
All checks were successful
Docker Deploy / build-and-deploy (push) Successful in 20s
2026-02-06 09:42:27 +00:00
sam
2ca60d6feb del
Some checks failed
Docker Deploy / build-and-deploy (push) Failing after 38s
2026-02-06 09:36:45 +00:00
sam
8a6172f5a7 assa 2026-02-06 09:36:18 +00:00
7 changed files with 213 additions and 44 deletions

View File

@@ -0,0 +1,53 @@
name: Docker Deploy
on: [push]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Build Docker Image
run: |
docker build -t my-local-app:latest .
# - name: Remove Old Container
# run: |
# # Use '|| true' to force a success exit code even if the container is missing
# docker stop my-running-app || true
# docker rm my-running-app || true
- name: Remove Old Container and Free Port
run: |
# 1. Stop and remove by NAME (what we had before)
docker stop my-running-app || true
docker rm my-running-app || true
# 2. EMERGENCY: Stop any container actually using port 9002
# This finds the ID of any container bound to 9002 and kills it
PORT_OWNER=$(docker ps -q --filter "publish=9002")
if [ ! -z "$PORT_OWNER" ]; then
docker stop $PORT_OWNER
docker rm $PORT_OWNER
fi
- name: Start New Container
run: |
docker run -d \
--name my-running-app \
-p 9002:5005 \
--restart unless-stopped \
my-local-app:latest
# - name: Deploy to CasaOS (Docker)
# run: |
# # Stop and remove the old container if it exists
# docker stop my-running-app || true
# docker rm my-running-app || true
# # Run the new container
# docker run -d \
# --name my-running-app \
# -p 9002:5005 \
# --restart always \
# my-local-app:latest

22
Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
# Use a lightweight Python image
FROM python:3.11-slim
# Set the working directory inside the container
WORKDIR /app
# Copy the requirements file first to leverage Docker cache
COPY requirements.txt .
# Install dependencies
RUN pip install --no-cache-dir -r requirements.txt
# Copy the rest of your application code
COPY . .
# Expose the port Flask runs on (default is 5000)
EXPOSE 5005
# Run the application using Gunicorn for production
# Replace 'app:app' with 'your_filename:app_variable_name'
# CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
CMD ["python", "app.py"]

33
app.py
View File

@@ -26,7 +26,7 @@ def get_file_info(filepath):
def index():
"""Serves the main UI."""
# Default to current working directory
start_path = os.getcwd()
# start_path = os.getcwd()
return render_template('index.html', start_path='/toor')
@app.route('/api/scan')
@@ -113,6 +113,37 @@ def delete_file():
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/delete_multiple', methods=['DELETE'])
def delete_multiple_files():
"""Deletes multiple files."""
data = request.get_json()
paths = data.get('paths', [])
if not paths:
return jsonify({"error": "No file paths provided"}), 400
errors = []
success_count = 0
for path in paths:
if not path or not os.path.exists(path):
errors.append({"path": path, "error": "File not found"})
continue
try:
if os.path.isdir(path):
errors.append({"path": path, "error": "Cannot delete directories"})
else:
os.remove(path)
success_count += 1
except Exception as e:
errors.append({"path": path, "error": str(e)})
if not errors:
return jsonify({"success": True, "message": f"{success_count} files deleted."})
else:
return jsonify({"success": False, "error": "Some files could not be deleted", "details": errors}), 500
if __name__ == '__main__':
# Run on localhost
print("Starting File Explorer on http://127.0.0.1:5005")

View File

@@ -1,26 +0,0 @@
name: Docker Deploy
on: [push]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Build Docker Image
run: |
docker build -t my-local-app:latest .
- name: Deploy to CasaOS (Docker)
run: |
# Stop and remove the old container if it exists
docker stop my-running-app || true
docker rm my-running-app || true
# Run the new container
docker run -d \
--name my-running-app \
-p 9002:5005 \
--restart always \
my-local-app:latest

View File

@@ -1,7 +0,0 @@
name: Connectivity Test
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: echo "The runner is working!"

2
requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
flask
gunicorn

View File

@@ -40,6 +40,9 @@
<input class="form-check-input" type="checkbox" id="recursiveToggle">
<label class="form-check-label" for="recursiveToggle">Recursive Scan (Subfolders)</label>
</div>
<button class="btn btn-danger ms-auto d-none" id="deleteSelectedBtn" onclick="deleteSelected()">
<i class="fa-solid fa-trash-alt me-1"></i>Delete Selected
</button>
</div>
</div>
</div>
@@ -49,6 +52,7 @@
<table class="table table-dark table-hover align-middle">
<thead>
<tr>
<th style="width: 30px;"><input type="checkbox" class="form-check-input" id="selectAllCheckbox"></th>
<th class="icon-col"></th>
<th>Name</th>
<th>Size</th>
@@ -86,8 +90,27 @@
const API_BASE = '/api';
let currentPath = "";
// Initial Load
document.addEventListener('DOMContentLoaded', () => loadFiles());
document.addEventListener('DOMContentLoaded', () => {
// Initial Load
loadFiles();
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
const fileTableBody = document.getElementById('fileTableBody');
selectAllCheckbox.addEventListener('change', (e) => {
const checkboxes = fileTableBody.querySelectorAll('.file-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = e.target.checked;
});
updateDeleteButtonVisibility();
});
fileTableBody.addEventListener('change', (e) => {
if (e.target.classList.contains('file-checkbox')) {
updateDeleteButtonVisibility();
}
});
});
async function loadFiles() {
const path = document.getElementById('pathInput').value;
@@ -119,15 +142,36 @@
function renderTable(files) {
const tbody = document.getElementById('fileTableBody');
// tbody is cleared in loadFiles, so we just append.
// Add "Go Up" link if not at root
if (currentPath && currentPath !== '/' && currentPath.length > 1) {
let parentPath = currentPath.substring(0, currentPath.lastIndexOf('/'));
if (parentPath === '') parentPath = '/';
const tr = document.createElement('tr');
tr.className = 'file-row';
tr.onclick = () => enterDir(parentPath);
tr.innerHTML = `
<td></td>
<td class="icon-col"><i class="fa-solid fa-arrow-turn-up text-secondary"></i></td>
<td>..</td>
<td></td>
<td class="text-end"></td>
`;
tbody.appendChild(tr);
}
if (files.length === 0) {
tbody.innerHTML = '<tr><td colspan="4" class="text-center text-muted">No files found.</td></tr>';
// If we haven't added the "up" directory, show no files.
if (tbody.childElementCount === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">No files found.</td></tr>';
}
return;
}
files.forEach(file => {
const tr = document.createElement('tr');
tr.className = 'file-row';
// Icon logic
let icon = 'fa-file';
@@ -140,15 +184,25 @@
const size = file.is_dir ? '-' : (file.size / 1024).toFixed(1) + ' KB';
tr.innerHTML = `
<td><input type="checkbox" class="form-check-input file-checkbox" data-path="${file.path.replace(/\\/g, '\\\\')}"></td>
<td class="icon-col"><i class="fa-solid ${icon} ${color}"></i></td>
<td onclick="${file.is_dir ? `enterDir('${file.path.replace(/\\/g, '\\\\')}')` : ''}">
${file.name}
</td>
<td class="file-name-cell">${file.name}</td>
<td>${size}</td>
<td class="text-end">
${getActions(file)}
</td>
`;
// Row click should not trigger when clicking on interactive elements
tr.addEventListener('click', (e) => {
// Ignore clicks on actions, checkboxes, or links
if (e.target.closest('button, a, input')) return;
if (file.is_dir) {
enterDir(file.path);
}
});
tbody.appendChild(tr);
});
}
@@ -210,7 +264,47 @@
alert("Network error");
}
}
async function deleteSelected() {
const selectedCheckboxes = document.querySelectorAll('.file-checkbox:checked');
if (selectedCheckboxes.length === 0) {
alert("Please select files to delete.");
return;
}
if (!confirm(`Are you sure you want to delete ${selectedCheckboxes.length} selected files? This cannot be undone.`)) return;
const paths = Array.from(selectedCheckboxes).map(cb => cb.dataset.path);
try {
const res = await fetch(`${API_BASE}/delete_multiple`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ paths: paths })
});
const data = await res.json();
if (data.success) {
loadFiles(); // Refresh list
} else {
alert("Error deleting files: " + (data.error || "Unknown error"));
}
} catch (err) {
alert("Network error during multiple delete.");
}
}
function updateDeleteButtonVisibility() {
const selectedCheckboxes = document.querySelectorAll('.file-checkbox:checked');
const deleteBtn = document.getElementById('deleteSelectedBtn');
if (selectedCheckboxes.length > 0) {
deleteBtn.classList.remove('d-none');
} else {
deleteBtn.classList.add('d-none');
}
}
</script>
</body>
</html>