added upload indication
This commit is contained in:
24
.dockerignore
Normal file
24
.dockerignore
Normal file
@@ -0,0 +1,24 @@
|
||||
# Keep Docker build contexts lean and secrets out of images
|
||||
.git
|
||||
.gitignore
|
||||
.venv
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.pyo
|
||||
*.so
|
||||
.mypy_cache/
|
||||
.pytest_cache/
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
.eggs/
|
||||
.idea/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
|
||||
# Local data, screenshots, and env files
|
||||
data/
|
||||
screenshot.png
|
||||
.env
|
||||
*.env
|
||||
agent.md
|
||||
@@ -3,12 +3,12 @@ HOST=0.0.0.0
|
||||
PORT=8080
|
||||
|
||||
# Immich connection
|
||||
IMMICH_BASE_URL=http://127.0.0.1:2283//api
|
||||
IMMICH_API_KEY=ADD-YOUR-API-KEY
|
||||
IMMICH_BASE_URL=http://127.0.0.1:2283/api
|
||||
IMMICH_API_KEY=ADD-YOUR-API-KEY #key needs asset.upload (default functions)
|
||||
MAX_CONCURRENT=3
|
||||
|
||||
# Optional admin token to allow UI-based config updates
|
||||
CONFIG_TOKEN=change-me
|
||||
# Optional: Album name for auto-adding uploads (creates if doesn't exist)
|
||||
IMMICH_ALBUM_NAME=dead-drop #key needs album.create,album.read,albumAsset.create (extended functions)
|
||||
|
||||
# Data path inside the container
|
||||
STATE_DB=/data/state.db
|
||||
25
.gitignore
vendored
25
.gitignore
vendored
@@ -1 +1,26 @@
|
||||
*.env
|
||||
agent.md
|
||||
|
||||
# Python cache and build artifacts
|
||||
__pycache__/
|
||||
app/__pycache__/*
|
||||
*.py[cod]
|
||||
*.pyo
|
||||
*.so
|
||||
.mypy_cache/
|
||||
.pytest_cache/
|
||||
*.egg-info/
|
||||
.eggs/
|
||||
build/
|
||||
dist/
|
||||
|
||||
# Virtual envs and IDE
|
||||
.venv/
|
||||
.idea/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
|
||||
# Local data/dbs
|
||||
data/*.db
|
||||
data/*.sqlite
|
||||
data/*.sqlite3
|
||||
|
||||
@@ -43,7 +43,7 @@ version: "3.9"
|
||||
|
||||
services:
|
||||
immich-drop:
|
||||
image: ttlequals0/immich-drop:latest
|
||||
image: ghcr.io/nasogaa/immich-drop:latest
|
||||
pull_policy: always
|
||||
container_name: immich-drop
|
||||
restart: unless-stopped
|
||||
@@ -165,11 +165,12 @@ PORT=8080
|
||||
|
||||
# Immich connection (include /api)
|
||||
IMMICH_BASE_URL=http://REPLACE_ME:2283/api
|
||||
IMMICH_API_KEY=REPLACE_ME
|
||||
IMMICH_API_KEY=ADD-YOUR-API-KEY #key needs asset.upload (default functions)
|
||||
|
||||
MAX_CONCURRENT=3
|
||||
|
||||
# Optional: Album name for auto-adding uploads (creates if doesn't exist)
|
||||
IMMICH_ALBUM_NAME=dead-drop
|
||||
IMMICH_ALBUM_NAME=dead-drop #key needs album.create,album.read,albumAsset.create (extended functions)
|
||||
|
||||
# Local dedupe cache
|
||||
STATE_DB=./data/state.db # local dev -> ./state.db (data folder is created in docker image)
|
||||
|
||||
@@ -435,6 +435,7 @@ async def api_upload(
|
||||
|
||||
return await do_upload()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run("backend.main:app", host=HOST, port=PORT, reload=True)
|
||||
"""
|
||||
Note: Do not run this module directly. Use `python main.py` from
|
||||
project root, which starts `uvicorn app.app:app` with reload.
|
||||
"""
|
||||
|
||||
@@ -3,6 +3,10 @@ const sessionId = (crypto && crypto.randomUUID) ? crypto.randomUUID() : (Math.ra
|
||||
let items = [];
|
||||
let socket;
|
||||
|
||||
// Status precedence: never regress (e.g., uploading -> done shouldn't go back to uploading)
|
||||
const STATUS_ORDER = { queued: 0, checking: 1, uploading: 2, duplicate: 3, done: 3, error: 4 };
|
||||
const FINAL_STATES = new Set(['done','duplicate','error']);
|
||||
|
||||
// --- Dark mode ---
|
||||
function initDarkMode() {
|
||||
const stored = localStorage.getItem('theme');
|
||||
@@ -87,9 +91,24 @@ function openSocket(){
|
||||
const { item_id, status, progress, message } = msg;
|
||||
const it = items.find(x => x.id===item_id);
|
||||
if(!it) return;
|
||||
it.status = status;
|
||||
if(typeof progress==='number') it.progress = progress;
|
||||
if(message) it.message = message;
|
||||
// If we've already finalized this item, ignore late/regressive updates
|
||||
if (FINAL_STATES.has(it.status)) return;
|
||||
|
||||
const cur = STATUS_ORDER[it.status] ?? 0;
|
||||
const inc = STATUS_ORDER[status] ?? 0;
|
||||
if (inc < cur) {
|
||||
// ignore regressive status updates
|
||||
} else {
|
||||
it.status = status;
|
||||
}
|
||||
if (typeof progress==='number') {
|
||||
// never decrease progress
|
||||
it.progress = Math.max(it.progress || 0, progress);
|
||||
}
|
||||
if (message) it.message = message;
|
||||
if (FINAL_STATES.has(it.status)) {
|
||||
it.progress = 100;
|
||||
}
|
||||
render();
|
||||
};
|
||||
socket.onclose = () => setTimeout(openSocket, 2000);
|
||||
@@ -118,6 +137,15 @@ async function runQueue(){
|
||||
next.status='error';
|
||||
next.message = body.error || 'Upload failed';
|
||||
render();
|
||||
} else if (res.ok) {
|
||||
// Fallback finalize on HTTP success in case WS final message is missed
|
||||
const statusText = (body && body.status) ? String(body.status) : '';
|
||||
const isDuplicate = /duplicate/i.test(statusText);
|
||||
next.status = isDuplicate ? 'duplicate' : 'done';
|
||||
next.message = statusText || (isDuplicate ? 'Duplicate' : 'Uploaded');
|
||||
next.progress = 100;
|
||||
render();
|
||||
try { showBanner(isDuplicate ? `Duplicate: ${next.name}` : `Uploaded: ${next.name}`, isDuplicate ? 'warn' : 'ok'); } catch {}
|
||||
}
|
||||
}catch(err){
|
||||
next.status='error';
|
||||
@@ -141,6 +169,20 @@ const pingStatus = document.getElementById('pingStatus');
|
||||
const banner = document.getElementById('topBanner');
|
||||
const btnTheme = document.getElementById('btnTheme');
|
||||
|
||||
// --- Simple banner helper ---
|
||||
function showBanner(text, kind='ok'){
|
||||
if(!banner) return;
|
||||
banner.textContent = text;
|
||||
// reset classes and apply based on kind
|
||||
banner.className = 'rounded-2xl p-3 text-center transition-colors ' + (
|
||||
kind==='ok' ? 'border border-green-200 bg-green-50 text-green-700 dark:bg-green-900 dark:border-green-700 dark:text-green-300'
|
||||
: kind==='warn' ? 'border border-amber-200 bg-amber-50 text-amber-700 dark:bg-amber-900 dark:border-amber-700 dark:text-amber-300'
|
||||
: 'border border-red-200 bg-red-50 text-red-700 dark:bg-red-900 dark:border-red-700 dark:text-red-300'
|
||||
);
|
||||
banner.classList.remove('hidden');
|
||||
setTimeout(() => banner.classList.add('hidden'), 3000);
|
||||
}
|
||||
|
||||
// --- Connection test with ephemeral banner ---
|
||||
btnPing.onclick = async () => {
|
||||
pingStatus.textContent = 'checking…';
|
||||
@@ -154,9 +196,7 @@ btnPing.onclick = async () => {
|
||||
if(j.album_name) {
|
||||
bannerText += ` | Uploading to album: "${j.album_name}"`;
|
||||
}
|
||||
banner.textContent = bannerText;
|
||||
banner.classList.remove('hidden');
|
||||
setTimeout(() => banner.classList.add('hidden'), 4000);
|
||||
showBanner(bannerText, 'ok');
|
||||
}
|
||||
}catch{
|
||||
pingStatus.textContent = 'No connection';
|
||||
|
||||
Reference in New Issue
Block a user