feat: Gitea Actions CI/CD, Docker registry images, drive drag-and-drop
Build & Release / Docker / backend (push) Failing after 1m31s
Build & Release / Docker / frontend (push) Failing after 1m42s
Build & Release / Deploy to server (push) Has been cancelled
Build & Release / Create release (push) Has been cancelled
Build & Release / Desktop / Linux & Windows (push) Has been cancelled

- Add .gitea/workflows/docker-build.yml: builds backend and frontend
  Docker images on every push, pushes to Gitea Container Registry
  (git.necloud.us/nate/cloudlink/{backend,frontend}:latest + SHA tag)
- Add desktop build job: Linux AppImage and Windows NSIS installer via
  Wine cross-compilation, uploaded as workflow artifacts
- Add release job: creates a Gitea release with installers attached when
  a v* tag is pushed
- Add deploy job: SSH pull + restart on pushes to master
- docker-compose.yml: add image: fields alongside build: so the server
  can pull pre-built images with docker compose pull
- DrivePage: add drag-and-drop upload with full-screen drop overlay;
  refactor upload logic into a shared uploadFile callback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-08-28 10:41:39 -04:00
1 parent 9b57dd9b94
commit 2823b8e10e
3 files changed
+213 -7

No files matched your search

+32 -7
View File
@@ -1,4 +1,4 @@
import { useRef, useState, useEffect } from 'react';
import { useRef, useState, useEffect, useCallback } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { Upload, File, Trash2, Download, Lock } from 'lucide-react';
import { formatDistanceToNow } from 'date-fns';
@@ -34,6 +34,7 @@ export default function DrivePage() {
const qc = useQueryClient();
const inputRef = useRef<HTMLInputElement>(null);
const [uploading, setUploading] = useState(false);
const [dragOver, setDragOver] = useState(false);
const [decryptedNames, setDecryptedNames] = useState<Record<string, string>>({});
const { data: files = [] } = useQuery<DriveFile[]>({
@@ -60,11 +61,9 @@ export default function DrivePage() {
})();
}, [files, masterKey]);
async function handleUpload(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file || !masterKey) return;
const uploadFile = useCallback(async (file: File) => {
if (!masterKey) return;
setUploading(true);
try {
const fileKey = await generateFileKey();
const encryptedBlob = await encryptFile(file, fileKey);
@@ -83,6 +82,18 @@ export default function DrivePage() {
setUploading(false);
if (inputRef.current) inputRef.current.value = '';
}
}, [masterKey, qc]);
function handleUpload(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (file) uploadFile(file);
}
function handleDrop(e: React.DragEvent) {
e.preventDefault();
setDragOver(false);
const file = e.dataTransfer.files[0];
if (file) uploadFile(file);
}
async function handleDownload(f: DriveFile) {
@@ -125,11 +136,25 @@ export default function DrivePage() {
<input ref={inputRef} type="file" className="hidden" onChange={handleUpload} />
</header>
<div className="flex-1 overflow-y-auto p-6">
<div
className="flex-1 overflow-y-auto p-6"
onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
onDragLeave={() => setDragOver(false)}
onDrop={handleDrop}
>
{dragOver && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 pointer-events-none">
<div className="flex flex-col items-center gap-3 bg-surface-1 border-2 border-dashed border-accent rounded-2xl px-16 py-12">
<Upload className="w-12 h-12 text-accent" />
<p className="text-lg font-semibold">Drop to encrypt &amp; upload</p>
</div>
</div>
)}
{files.length === 0 ? (
<div className="flex flex-col items-center justify-center h-48 text-slate-500 gap-3">
<File className="w-10 h-10" />
<p>No files yet — upload your first encrypted file</p>
<p>No files yet — drag &amp; drop or click Upload</p>
</div>
) : (
<div className="grid grid-cols-1 gap-2">