Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44fb456405 | |||
| 8f9b9b4945 | |||
| cae8f3cbf6 | |||
| e4e48b13d8 | |||
| 8a78b2bb2e |
@@ -1,6 +1,6 @@
|
||||
<img src="logoExcaliDash.png" alt="ExcaliDash Logo" width="80" height="88">
|
||||
|
||||
# ExcaliDash
|
||||
# ExcaliDash v0.1.8
|
||||
|
||||

|
||||

|
||||
|
||||
-14
@@ -27,17 +27,3 @@ CSRF Protection (8a78b2b)
|
||||
- Updated docker-compose configurations with new environment variables
|
||||
- E2E test suite improvements and reliability fixes
|
||||
- Added Kubernetes deployment note in README
|
||||
|
||||
### Kubernetes
|
||||
|
||||
A `CSRF_SECRET` environment variable is now required for CSRF protection. Generate a secure 32+ character random string:
|
||||
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
|
||||
Add it to your deployment:
|
||||
- Docker Compose: Add CSRF_SECRET=<your-secret> to the backend service environment
|
||||
- Kubernetes: Add to your ConfigMap/Secret and reference in the backend deployment
|
||||
|
||||
If not set, the backend will refuse to start.
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "0.3.0",
|
||||
"version": "0.2.1",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -532,6 +532,7 @@ export const validateImportedDrawing = (data: any): boolean => {
|
||||
// CSRF Protection
|
||||
// ============================================================================
|
||||
|
||||
const CSRF_TOKEN_LENGTH = 32;
|
||||
const CSRF_TOKEN_HEADER = "x-csrf-token";
|
||||
const CSRF_TOKEN_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours
|
||||
const CSRF_TOKEN_FUTURE_SKEW_MS = 5 * 60 * 1000; // 5 minutes clock skew tolerance
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"version": "0.3.0",
|
||||
"version": "0.2.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -48,7 +48,7 @@ export const UploadProvider: React.FC<{ children: ReactNode }> = ({ children })
|
||||
|
||||
const uploadFiles = useCallback(async (files: File[], targetCollectionId: string | null) => {
|
||||
const newTasks: UploadTask[] = files.map(f => ({
|
||||
id: crypto.randomUUID(),
|
||||
id: Math.random().toString(36).substring(2, 11),
|
||||
fileName: f.name,
|
||||
status: 'pending',
|
||||
progress: 0
|
||||
@@ -56,12 +56,12 @@ export const UploadProvider: React.FC<{ children: ReactNode }> = ({ children })
|
||||
|
||||
setTasks(prev => [...newTasks, ...prev]);
|
||||
|
||||
// Map file index to task ID for progress callbacks (handles duplicate filenames)
|
||||
const indexToTaskId = new Map<number, string>();
|
||||
newTasks.forEach((t, index) => indexToTaskId.set(index, t.id));
|
||||
// Map file names to task IDs for progress callbacks
|
||||
const fileTaskMap = new Map<string, string>();
|
||||
newTasks.forEach(t => fileTaskMap.set(t.fileName, t.id));
|
||||
|
||||
const handleProgress = (fileIndex: number, status: UploadStatus, progress: number, error?: string) => {
|
||||
const taskId = indexToTaskId.get(fileIndex);
|
||||
const handleProgress = (fileName: string, status: UploadStatus, progress: number, error?: string) => {
|
||||
const taskId = fileTaskMap.get(fileName);
|
||||
if (taskId) {
|
||||
updateTask(taskId, { status, progress, error });
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ export const importDrawings = async (
|
||||
targetCollectionId: string | null,
|
||||
onSuccess?: () => void | Promise<void>,
|
||||
onProgress?: (
|
||||
fileIndex: number,
|
||||
fileName: string,
|
||||
status: UploadStatus,
|
||||
progress: number,
|
||||
error?: string
|
||||
@@ -25,20 +25,12 @@ export const importDrawings = async (
|
||||
let failCount = 0;
|
||||
const errors: string[] = [];
|
||||
|
||||
// Build a map from drawingFile index to original file index for progress reporting
|
||||
const originalIndexMap = new Map<number, number>();
|
||||
drawingFiles.forEach((df, i) => {
|
||||
const originalIndex = files.indexOf(df);
|
||||
originalIndexMap.set(i, originalIndex);
|
||||
});
|
||||
|
||||
// We process files in parallel (Promise.all) but we could limit concurrency if needed.
|
||||
// For now, full parallel is fine as browser limits connection count anyway.
|
||||
await Promise.all(
|
||||
drawingFiles.map(async (file, drawingIndex) => {
|
||||
const fileIndex = originalIndexMap.get(drawingIndex) ?? drawingIndex;
|
||||
drawingFiles.map(async (file) => {
|
||||
try {
|
||||
if (onProgress) onProgress(fileIndex, 'processing', 0); // Parsing phase
|
||||
if (onProgress) onProgress(file.name, 'processing', 0); // Parsing phase
|
||||
|
||||
const text = await file.text();
|
||||
const data = JSON.parse(text);
|
||||
@@ -69,7 +61,7 @@ export const importDrawings = async (
|
||||
preview: svg.outerHTML,
|
||||
};
|
||||
|
||||
if (onProgress) onProgress(fileIndex, 'uploading', 0);
|
||||
if (onProgress) onProgress(file.name, 'uploading', 0);
|
||||
|
||||
await api.post("/drawings", payload, {
|
||||
headers: {
|
||||
@@ -81,12 +73,12 @@ export const importDrawings = async (
|
||||
const percentCompleted = Math.round(
|
||||
(progressEvent.loaded * 100) / progressEvent.total
|
||||
);
|
||||
onProgress(fileIndex, 'uploading', percentCompleted);
|
||||
onProgress(file.name, 'uploading', percentCompleted);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (onProgress) onProgress(fileIndex, 'success', 100);
|
||||
if (onProgress) onProgress(file.name, 'success', 100);
|
||||
successCount++;
|
||||
|
||||
} catch (err: any) {
|
||||
@@ -98,7 +90,7 @@ export const importDrawings = async (
|
||||
err?.message ||
|
||||
"Upload failed";
|
||||
errors.push(`${file.name}: ${errorMessage}`);
|
||||
if (onProgress) onProgress(fileIndex, 'error', 0, errorMessage);
|
||||
if (onProgress) onProgress(file.name, 'error', 0, errorMessage);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
@@ -15,16 +15,19 @@ try {
|
||||
console.warn("Unable to read VERSION file:", error);
|
||||
}
|
||||
|
||||
const appVersion = process.env.VITE_APP_VERSION?.trim() || versionFromFile;
|
||||
const buildLabel = process.env.VITE_APP_BUILD_LABEL?.trim() || "local development build";
|
||||
if (
|
||||
!process.env.VITE_APP_VERSION ||
|
||||
process.env.VITE_APP_VERSION.trim().length === 0
|
||||
) {
|
||||
process.env.VITE_APP_VERSION = versionFromFile;
|
||||
if (!process.env.VITE_APP_BUILD_LABEL) {
|
||||
process.env.VITE_APP_BUILD_LABEL = "local development build";
|
||||
}
|
||||
}
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
define: {
|
||||
'import.meta.env.VITE_APP_VERSION': JSON.stringify(appVersion),
|
||||
'import.meta.env.VITE_APP_BUILD_LABEL': JSON.stringify(buildLabel),
|
||||
},
|
||||
server: {
|
||||
proxy: {
|
||||
"/api": {
|
||||
|
||||
Reference in New Issue
Block a user