diff --git a/ai-tools-dashboard/.dockerignore b/ai-tools-dashboard/.dockerignore deleted file mode 100644 index f80b7cc..0000000 --- a/ai-tools-dashboard/.dockerignore +++ /dev/null @@ -1,6 +0,0 @@ -node_modules -*/node_modules -*/dist -data -.git -*.log diff --git a/ai-tools-dashboard/Dockerfile b/ai-tools-dashboard/Dockerfile deleted file mode 100644 index bb37eaf..0000000 --- a/ai-tools-dashboard/Dockerfile +++ /dev/null @@ -1,53 +0,0 @@ -# ── Stage 1: Build client ─────────────────────────────────────────────────── -FROM node:20-alpine AS client-builder -WORKDIR /build - -COPY package.json ./ -COPY client/package.json ./client/ -COPY server/package.json ./server/ - -RUN npm install --workspace=client - -COPY client/ ./client/ -RUN npm run build --workspace=client - -# ── Stage 2: Build server ─────────────────────────────────────────────────── -FROM node:20-alpine AS server-builder -WORKDIR /build - -COPY package.json ./ -COPY server/package.json ./server/ - -RUN npm install --workspace=server - -COPY server/ ./server/ -RUN npm run build --workspace=server - -# ── Stage 3: Runtime ──────────────────────────────────────────────────────── -FROM node:20-alpine AS runtime -WORKDIR /app - -ENV NODE_ENV=production -ENV PORT=3000 -ENV DATA_DIR=/data -ENV MAX_UPLOAD_MB=50 -ENV ADMIN_USERNAME=admin -ENV ADMIN_PASSWORD=codedump2024 -ENV JWT_SECRET=changeme-use-a-long-random-string - -# Install production deps only -COPY package.json ./ -COPY server/package.json ./server/ -RUN npm install --workspace=server --omit=dev - -# Copy built artifacts -COPY --from=server-builder /build/server/dist ./server/dist -COPY --from=client-builder /build/client/dist ./client/dist - -VOLUME ["/data"] -EXPOSE 3000 - -HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \ - CMD wget -qO- http://localhost:3000/api/settings || exit 1 - -CMD ["node", "server/dist/index.js"] diff --git a/ai-tools-dashboard/INSTALL.md b/ai-tools-dashboard/INSTALL.md deleted file mode 100644 index 8a3ded7..0000000 --- a/ai-tools-dashboard/INSTALL.md +++ /dev/null @@ -1,185 +0,0 @@ -# Unraid Install Guide — CODEDUMP - -## Prerequisites - -- Unraid 6.10 or later -- Docker enabled (default on all modern Unraid installs) -- Community Applications plugin installed (recommended, not required) - ---- - -## Option A — Community Applications (Easiest) - -> Use this if you have the CA plugin installed. - -1. Open the **Apps** tab in the Unraid web UI. -2. Search for **CODEDUMP**. -3. Click **Install** and fill in the variables described in the [Variables](#variables) section below. -4. Click **Apply**. - ---- - -## Option B — Manual Docker Container (No CA Plugin) - -### Step 1 — Open the Docker tab - -In the Unraid web UI, click **Docker** in the top navigation bar. - -### Step 2 — Add a new container - -Click **Add Container** at the bottom of the page. - -### Step 3 — Fill in Basic Settings - -| Field | Value | -|---|---| -| **Name** | `codedump` | -| **Repository** | `your-registry/codedump:latest` *(or build locally — see below)* | -| **Network Type** | `bridge` | -| **Console shell command** | `sh` | - -> **Building locally:** If you cloned this repo to Unraid, open a terminal and run: -> ```bash -> cd /path/to/ai-tools-dashboard -> docker build -t codedump:local . -> ``` -> Then set **Repository** to `codedump:local`. - -### Step 4 — Port Mapping - -Click **Add another Path, Port, Variable, Label or Device** → **Port**. - -| Field | Value | -|---|---| -| **Name** | `WebUI` | -| **Container Port** | `3000` | -| **Host Port** | `3000` *(or any open port on your Unraid server)* | -| **Connection Type** | `TCP` | - -### Step 5 — Volume (Persistent Data) - -Click **Add another Path, Port, Variable, Label or Device** → **Path**. - -| Field | Value | -|---|---| -| **Name** | `Data` | -| **Container Path** | `/data` | -| **Host Path** | `/mnt/user/appdata/codedump` | -| **Access Mode** | `Read/Write` | - -> This single volume stores the **SQLite database**, all **uploaded files**, and **user accounts**. -> Unraid creates the directory automatically on first run. - -### Step 6 — Environment Variables {#variables} - -For each variable, click **Add another Path, Port, Variable, Label or Device** → **Variable**. - -| Variable | Default | Required | Description | -|---|---|---|---| -| `PORT` | `3000` | No | Port the app listens on inside the container. Match to your Container Port above. | -| `DATA_DIR` | `/data` | No | Path inside the container for persistent data. Do not change unless you remapped the volume. | -| `MAX_UPLOAD_MB` | `50` | No | Maximum upload size in MB for documents and logos. | -| `ADMIN_USERNAME` | `admin` | **Yes** | Username for the bootstrap admin account. Set this before first launch. | -| `ADMIN_PASSWORD` | `codedump2024` | **Yes** | Password for the bootstrap admin account. **Change this to something strong.** | -| `JWT_SECRET` | *(insecure default)* | **Yes** | Secret used to sign login tokens. Set to a long random string (32+ chars). | - -> **Security note:** `ADMIN_PASSWORD` and `JWT_SECRET` must be changed from their defaults before exposing CODEDUMP to your network. The bootstrap admin is only created once — changing `ADMIN_PASSWORD` after first launch has no effect on the existing account (use the Admin → Users page instead). - -### Step 7 — WebUI Link (Optional but Recommended) - -| Field | Value | -|---|---| -| **WebUI** | `http://[IP]:[PORT:3000]` | - -This adds a clickable **WebUI** button on the Docker tab. - -### Step 8 — Apply - -Click **Apply**. Unraid will pull/build the image and start the container. - ---- - -## First Launch & Login - -1. Open `http://YOUR-UNRAID-IP:3000` in your browser. -2. You will be redirected to the **login page**. -3. Click **Admin Login** at the bottom of the login screen. -4. Enter the `ADMIN_USERNAME` and `ADMIN_PASSWORD` you set in the environment variables. -5. Once in, navigate to **Admin → Users** to create user accounts for your team. - -### User Account Types - -| Type | Login Method | Access | -|---|---|---| -| **Admin** | Username + Password | Full access including Settings and User Management | -| **User** | Select name → 4-digit PIN | Projects, Tools, Documents — all read/write | - -### Creating Users (Admin only) - -1. Click **Users** in the sidebar (Admin section). -2. Click **New User**. -3. Enter a username, select role **User**, and assign a **4-digit PIN**. -4. The user's name will appear on the login screen — they select it and enter their PIN. - ---- - -## Updating - -### Registry image: -1. Go to **Docker** tab → click the container icon → **Update** (or Force Update). -2. The `/data` volume is preserved — database, uploads, and user accounts are safe. - -### Locally built image: -```bash -cd /path/to/ai-tools-dashboard -git pull -docker build -t codedump:local . -``` -Then restart the container from the Unraid Docker tab. - ---- - -## Backup - -Everything lives in one directory: - -``` -/mnt/user/appdata/codedump/ -├── dashboard.db ← SQLite database (projects, tools, settings, users) -└── uploads/ ← Uploaded files (logos, markdown docs) -``` - -Back this up with Unraid's **Appdata Backup** plugin or any solution that covers `/mnt/user/appdata`. - ---- - -## Generating a Strong JWT_SECRET - -Run this in an Unraid terminal or any Linux shell: - -```bash -openssl rand -hex 32 -``` - -Paste the output as the value of `JWT_SECRET`. - ---- - -## Port Conflicts - -Change **Host Port** to an unused port (e.g. `3100`) and update the WebUI link to match. - ---- - -## Troubleshooting - -| Symptom | Fix | -|---|---| -| Stuck on login / "Session expired" | Clear browser localStorage and reload. | -| "No user accounts yet" on login | Log in as admin first, then create users via Admin → Users. | -| Admin can't log in after reinstall | If `/data` was wiped, bootstrap re-runs on next start using the current env vars. | -| Container exits immediately | Check Docker logs — usually a permissions issue on `/data`. | -| Can't upload files | Verify `MAX_UPLOAD_MB` and that the host path is writable. | -| Blank page / 404 | Wait 15 seconds after start, then refresh. | - -View container logs: **Docker tab** → click the container icon → **Logs**. diff --git a/ai-tools-dashboard/client/index.html b/ai-tools-dashboard/client/index.html deleted file mode 100644 index 63ceef9..0000000 --- a/ai-tools-dashboard/client/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - CODEDUMP - - - - - -
- - - diff --git a/ai-tools-dashboard/client/package.json b/ai-tools-dashboard/client/package.json deleted file mode 100644 index e398276..0000000 --- a/ai-tools-dashboard/client/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "ai-tools-dashboard-client", - "version": "1.0.0", - "private": true, - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview" - }, - "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-router-dom": "^6.22.3", - "react-markdown": "^9.0.1", - "remark-gfm": "^4.0.0", - "lucide-react": "^0.364.0" - }, - "devDependencies": { - "@types/react": "^18.3.1", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.2.1", - "autoprefixer": "^10.4.19", - "postcss": "^8.4.38", - "tailwindcss": "^3.4.3", - "typescript": "^5.4.2", - "vite": "^5.2.6" - } -} diff --git a/ai-tools-dashboard/client/postcss.config.js b/ai-tools-dashboard/client/postcss.config.js deleted file mode 100644 index 2aa7205..0000000 --- a/ai-tools-dashboard/client/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/ai-tools-dashboard/client/src/api/index.ts b/ai-tools-dashboard/client/src/api/index.ts deleted file mode 100644 index 96f6dca..0000000 --- a/ai-tools-dashboard/client/src/api/index.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { Project, Tool, Settings } from '../types'; - -const BASE = '/api'; -const TOKEN_KEY = 'codedump_token'; - -function getToken(): string | null { - return localStorage.getItem(TOKEN_KEY); -} - -async function req(path: string, options: RequestInit = {}): Promise { - const token = getToken(); - const headers: Record = { - ...(options.headers as Record || {}), - }; - if (token) headers['Authorization'] = `Bearer ${token}`; - if (options.body && typeof options.body === 'string') headers['Content-Type'] = 'application/json'; - - const res = await fetch(`${BASE}${path}`, { ...options, headers }); - - if (res.status === 401) { - // Token expired — clear storage and reload to login - localStorage.removeItem(TOKEN_KEY); - localStorage.removeItem('codedump_user'); - window.location.href = '/login'; - throw new Error('Session expired'); - } - - if (!res.ok) { - const err = await res.json().catch(() => ({ error: res.statusText })); - throw new Error(err.error || res.statusText); - } - if (res.status === 204) return undefined as T; - return res.json(); -} - -// Auth -export const pinLogin = (pin: string) => - req<{ token: string; user: any }>('/auth/pin', { method: 'POST', body: JSON.stringify({ pin }) }); -export const adminLogin = (username: string, password: string) => - req<{ token: string; user: any }>('/auth/login', { method: 'POST', body: JSON.stringify({ username, password }) }); - -// Users (admin only) -export const getUsers = () => req('/users'); -export const createUser = (data: any) => - req('/users', { method: 'POST', body: JSON.stringify(data) }); -export const updateUser = (id: string, data: any) => - req(`/users/${id}`, { method: 'PUT', body: JSON.stringify(data) }); -export const deleteUser = (id: string) => req(`/users/${id}`, { method: 'DELETE' }); - -// Projects -export const getProjects = () => req('/projects'); -export const getProject = (id: string) => req(`/projects/${id}`); -export const createProject = (data: Partial) => - req('/projects', { method: 'POST', body: JSON.stringify(data) }); -export const updateProject = (id: string, data: Partial) => - req(`/projects/${id}`, { method: 'PUT', body: JSON.stringify(data) }); -export const deleteProject = (id: string) => req(`/projects/${id}`, { method: 'DELETE' }); - -// Tools -export const getTools = () => req('/tools'); -export const createTool = (data: Partial) => - req('/tools', { method: 'POST', body: JSON.stringify(data) }); -export const updateTool = (id: string, data: Partial) => - req(`/tools/${id}`, { method: 'PUT', body: JSON.stringify(data) }); -export const deleteTool = (id: string) => req(`/tools/${id}`, { method: 'DELETE' }); - -// Uploads -export const uploadDocument = (projectId: string, file: File) => { - const form = new FormData(); - form.append('file', file); - const token = getToken(); - return fetch(`${BASE}/uploads/projects/${projectId}`, { - method: 'POST', - headers: token ? { Authorization: `Bearer ${token}` } : {}, - body: form, - }).then((r) => r.json()); -}; -export const deleteDocument = (id: string) => req(`/uploads/documents/${id}`, { method: 'DELETE' }); -export const getFileUrl = (filename: string) => `${BASE}/uploads/${filename}`; - -// Settings -export const getSettings = () => req('/settings'); -export const updateSettings = (data: Partial) => - req('/settings', { method: 'PUT', body: JSON.stringify(data) }); -export const uploadLogo = (file: File) => { - const form = new FormData(); - form.append('logo', file); - const token = getToken(); - return fetch(`${BASE}/settings/logo`, { - method: 'POST', - headers: token ? { Authorization: `Bearer ${token}` } : {}, - body: form, - }).then((r) => r.json()); -}; diff --git a/ai-tools-dashboard/client/src/components/layout/Layout.tsx b/ai-tools-dashboard/client/src/components/layout/Layout.tsx deleted file mode 100644 index 196037e..0000000 --- a/ai-tools-dashboard/client/src/components/layout/Layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Outlet } from 'react-router-dom'; -import Sidebar from './Sidebar'; - -export default function Layout() { - return ( -
- -
- -
-
- ); -} diff --git a/ai-tools-dashboard/client/src/components/layout/Sidebar.tsx b/ai-tools-dashboard/client/src/components/layout/Sidebar.tsx deleted file mode 100644 index de004a5..0000000 --- a/ai-tools-dashboard/client/src/components/layout/Sidebar.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import { NavLink, useNavigate } from 'react-router-dom'; -import { LayoutDashboard, FolderKanban, Wrench, Settings, Users, LogOut, Shield } from 'lucide-react'; -import { useSettings } from '../../hooks/useSettings'; -import { useAuth } from '../../hooks/useAuth'; - -const userLinks = [ - { to: '/', icon: LayoutDashboard, label: 'Dashboard' }, - { to: '/projects', icon: FolderKanban, label: 'Projects' }, - { to: '/tools', icon: Wrench, label: 'Tools' }, -]; - -const adminLinks = [ - { to: '/settings', icon: Settings, label: 'Settings' }, - { to: '/admin/users', icon: Users, label: 'Users' }, -]; - -export default function Sidebar() { - const { settings } = useSettings(); - const { user, logout } = useAuth(); - const navigate = useNavigate(); - - const handleLogout = () => { - logout(); - navigate('/login', { replace: true }); - }; - - const isAdmin = user?.role === 'admin'; - - return ( - - ); -} diff --git a/ai-tools-dashboard/client/src/components/projects/ProjectCard.tsx b/ai-tools-dashboard/client/src/components/projects/ProjectCard.tsx deleted file mode 100644 index f072800..0000000 --- a/ai-tools-dashboard/client/src/components/projects/ProjectCard.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { useNavigate } from 'react-router-dom'; -import { ExternalLink, FolderOpen, FileText, Percent } from 'lucide-react'; -import type { Project } from '../../types'; -import ProgressBar from '../ui/ProgressBar'; -import Badge from '../ui/Badge'; - -interface Props { - project: Project; -} - -export default function ProjectCard({ project }: Props) { - const navigate = useNavigate(); - - return ( -
navigate(`/projects/${project.id}`)} - className="group relative bg-card border border-border rounded-xl p-5 cursor-pointer hover:border-[var(--accent)]/40 hover:shadow-lg hover:shadow-black/20 transition-all duration-200 animate-fade-in" - > - {/* Accent top bar */} -
- - {/* Header */} -
-
-
- {project.is_new && New} - {project.status} -
-

{project.name}

-

{project.category}

-
-
- -
-
- - {/* Description */} - {project.description && ( -

{project.description}

- )} - - {/* Progress */} -
-
- - Completion - - {project.completion}% -
- -
- - {/* Footer */} - - - {/* Tags */} - {project.tags.length > 0 && ( -
- {project.tags.map((tag) => ( - - {tag} - - ))} -
- )} -
- ); -} diff --git a/ai-tools-dashboard/client/src/components/projects/ProjectForm.tsx b/ai-tools-dashboard/client/src/components/projects/ProjectForm.tsx deleted file mode 100644 index c9a5bd5..0000000 --- a/ai-tools-dashboard/client/src/components/projects/ProjectForm.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import { useState } from 'react'; -import type { Project } from '../../types'; -import Button from '../ui/Button'; - -const ACCENT_PRESETS = [ - '#6366f1', '#8b5cf6', '#ec4899', '#f97316', - '#10b981', '#06b6d4', '#eab308', '#ef4444', -]; - -const CATEGORIES = ['General', 'AI/ML', 'Automation', 'Data', 'DevOps', 'Frontend', 'Backend', 'Research', 'Other']; - -interface Props { - initial?: Partial; - onSubmit: (data: Partial) => Promise; - onCancel: () => void; -} - -export default function ProjectForm({ initial, onSubmit, onCancel }: Props) { - const [form, setForm] = useState({ - name: initial?.name || '', - description: initial?.description || '', - category: initial?.category || 'General', - status: initial?.status || 'active', - completion: initial?.completion ?? 0, - external_url: initial?.external_url || '', - drive_url: initial?.drive_url || '', - tags: initial?.tags?.join(', ') || '', - accent_color: initial?.accent_color || '#6366f1', - is_new: initial?.is_new ?? true, - }); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (!form.name.trim()) { setError('Name is required'); return; } - setLoading(true); - setError(''); - try { - await onSubmit({ - ...form, - tags: form.tags.split(',').map((t) => t.trim()).filter(Boolean), - completion: Number(form.completion), - external_url: form.external_url || null, - drive_url: form.drive_url || null, - }); - } catch (err: any) { - setError(err.message || 'Something went wrong'); - setLoading(false); - } - }; - - const field = 'w-full bg-surface border border-border rounded-lg px-3 py-2 text-sm text-white placeholder:text-muted focus:outline-none focus:border-[var(--accent)] transition-colors'; - - return ( -
- {error &&

{error}

} - -
-
- - setForm({ ...form, name: e.target.value })} placeholder="My AI Project" /> -
- -
- - -
- -
- - -
- -
- -