Skip to content

Tomasneto404/linkPage

Repository files navigation

LinkPage

A modern, self-hosted WebUI designed for enterprises to centralize and publish internal or external links/URLs for employees, collaborators, and clients.

Instead of scattering important resources across emails, chats, and shared drives, LinkPage provides a single branded landing page (e.g. linkpage.company.com) where teams can quickly access everything they need — from internal tools and documentation to public-facing resources.


Screenshots

Public Page

Light Mode Dark Mode
Public page – light mode Public page – dark mode

The public page greets end users with a clean card grid, sticky group tabs for filtering, a live search bar, and auto-fetched favicons — no sign-in required.


Admin Panel

Light Mode Dark Mode
Admin panel – light mode Admin panel – dark mode

The admin panel is protected by a secret token. The left sidebar lists all groups; the main area shows link cards with drag-to-reorder, bulk selection, import/export, and one-click link creation.


Click Analytics

Click analytics modal

Per-link analytics show total clicks, unique visitors, today and this-week counts, a recent-click log with IP and device type, and a bar chart of top visitor IPs.


Settings

Settings modal

The settings modal covers branding (custom site title, separate logos for light and dark mode), an icon library (manage reusable icons, opt-in to saving fetched favicons), public access (optional password gate, default group), security (one-click admin token rotation), and an About section with an on-demand "Check for updates" button.


Features

  • Public page — clean, searchable landing page for end users with group tabs and multi-keyword search
  • Admin panel — full link and group management behind a secure token gate
  • Multiple groups per link — assign a link to as many groups as needed via a checkbox multi-select
  • Sections & subsections — organize cards under named section headings, and nest one level of subsections inside them; drag to reorder in the sidebar (reflected on the public page), or drag a link straight onto a group/section/subsection
  • Live updates on the public page — opened tabs fade-refresh within a fraction of a second when an admin adds, edits, reorders, or deletes anything (Server-Sent Events)
  • Reusable icon library — every uploaded icon goes into a shared, searchable library; pick an existing icon for new links, bulk-select and delete, and export/import the whole library as a single file; the Settings favicon appears here too
  • Stock icon picker — choose from a built-in line-icon set and a colour, instead of uploading; file links get a coloured file-type icon, and links with no icon fall back to the Settings favicon
  • In-place file editor — edit attached text files (HTML, XML, JSON, TXT, CSV, MD, SVG) in the admin with an expand view, line numbers, syntax highlighting, and a find bar; saves go live instantly
  • File attachments — link cards can point to an uploaded file (PDF, Office docs, archives, images, text, HTML/XML/JSON) instead of a URL, with a 100 MB cap and an extension whitelist
  • Password-protected groups — gate sensitive groups with a password; per-group unlock behaviour: auto-lock after 30 s (kiosk-safe) or stay unlocked for the browser session
  • Admin audit log — every admin change and every link click is recorded with the entity name, time, IP and device; live auto-refresh, search/type filters, and CSV/JSON export
  • Hide links from the public page — keep a link in the admin without exposing it publicly (eye-toggle on every card)
  • Custom group colors — pick any color via the native picker swatch
  • Pinned default group — choose which group the public page opens on (new visitors only — returning visitors keep the tab they were last viewing)
  • Copy link — one-click copy button on every public card, with a toast confirmation
  • Click analytics — per-link stats: total clicks, unique visitors, today/week counts, top IPs
  • Broken link checker — automatic health check every 6 hours, flags dead links in the admin
  • Custom branding — upload separate logos for light and dark mode, a custom browser tab favicon, and set a custom site title
  • Smart favicon fetching — three-tier strategy (parse the page for <link rel="icon">, then /favicon.ico, then Google as fallback) so favicons work for intranet sites too; fetched in the background so saving a link is never blocked, and cached server-side
  • Drag-to-reorder — reorder links, groups, sections, and subsections by dragging
  • Bulk actions — multi-select (shift-click for ranges, Select all, Esc to clear) to delete, move into a section, hide, or show multiple links at once
  • In-app update checker — Settings shows the current build, and a one-click "Check for updates" button polls GitHub Releases for newer versions and shows the release notes inline
  • Versioned database migrations — transactional, idempotent schema upgrades run on every startup; logs what changed so any past version upgrades cleanly
  • Mobile-friendly UI — slide-over sidebar, full-width modal sheets, larger tap targets, and a kebab overflow menu on the admin top bar
  • Import / Export — backup and restore links, groups, sections and subsections as JSON (idempotent re-import)
  • Public password gate — optionally protect the public page with a password
  • Light & dark mode — Apple-style design with OS preference detection
  • Docker ready — single docker compose up to deploy with persistent storage

How It Works

Architecture

linkPage/
├── src/
│   ├── server.js      # Express API + static file serving
│   └── database.js    # SQLite setup and all data access functions
├── public/
│   ├── index.html     # Public page (end users)
│   ├── app.js         # Public page logic
│   ├── style.css      # Shared Apple-style theme (CSS variables)
│   └── admin/
│       ├── index.html # Admin panel
│       ├── admin.js   # Admin panel logic
│       └── admin.css  # Admin-specific styles
├── Dockerfile
└── docker-compose.yml

Data Storage

All persistent data lives in /app/data inside the container, mapped to ./linkpage_data on the host:

Path Purpose
data/links.db SQLite database — links, groups, sections & subsections, link↔group mappings, icon library, settings, click analytics, and the admin audit log. The schema is auto-migrated on startup.
data/admin-token.txt Admin token generated on first startup
data/uploads/ Uploaded images, logos, favicons, and link file attachments

Authentication

There is no login system. On first startup the server generates a 64-character hex token and saves it to data/admin-token.txt. This token must be entered in the admin panel to unlock management features. It is stored in your browser's localStorage and can be rotated from the admin settings at any time.

Click Tracking

Every link card points to /r/:id (a server-side redirect) instead of the destination URL directly. The server records the click — IP address, user agent, timestamp — in SQLite before redirecting. This works without JavaScript and is not blocked by ad blockers.


Deployment with Docker

Prerequisites


Option A — Pull from Docker Hub (recommended)

The easiest way to run LinkPage. No need to clone the repository or build anything.

docker-compose.prod.yml

services:
  linkpage:
    image: tomasneto26/linkpage:latest
    ports:
      - "3000:3000"
    volumes:
      - ./linkpage_data:/app/data
    restart: unless-stopped
    environment:
      - PORT=3000
      - DATA_DIR=/app/data
      - UPLOADS_DIR=/app/data/uploads

1. Create the file

curl -O https://raw.githubusercontent.com/Tomasneto404/linkPage/main/docker-compose.prod.yml

Or create it manually with the contents above.

2. Start the app

docker compose -f docker-compose.prod.yml up -d

Docker will pull the image from Docker Hub automatically and start the container.

3. Get your admin token

docker compose -f docker-compose.prod.yml logs linkpage

Look for a line like:

│  🔑 Admin Token: 5c5665cfeaf662e95f98e832e2cdbaa3c2ab5d0b...

Copy the full token. It is saved to linkpage_data/admin-token.txt and reused across restarts.

4. Open the app

Page URL
Public page http://localhost:3000
Admin panel http://localhost:3000/admin

Paste the token into the admin panel to unlock link management.

Updating to the latest image

docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d

Option B — Build from source

Clone the repository and build the image locally.

docker-compose.yml

services:
  linkpage:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - ./linkpage_data:/app/data
    restart: unless-stopped
    environment:
      - PORT=3000
      - DATA_DIR=/app/data
      - UPLOADS_DIR=/app/data/uploads

volumes:
  linkpage_data:

Steps

git clone https://github.com/Tomasneto404/linkPage.git
cd linkPage
docker compose up -d

Updating

git pull
docker compose down
docker compose up -d --build

Docker Compose fields explained

Field What it does
image Pulls the pre-built image from Docker Hub
build: . Builds the image from the local Dockerfile (Option B only)
ports: "3000:3000" Exposes the app on port 3000. Change the left side for a different host port (e.g. "80:3000")
volumes: ./linkpage_data:/app/data Maps a local folder to the container so the database and uploads survive restarts and image updates
restart: unless-stopped Auto-restarts the container on crash or host reboot
DATA_DIR / UPLOADS_DIR Tell the app where to store the database and uploaded files inside the container

Full reset (wipes all data)

docker compose down -v
docker compose up -d

A new admin token will be generated on the next startup.


Running Without Docker

Prerequisites

Steps

npm install
npm start

The app starts on port 3000. On first run it creates data/links.db and data/admin-token.txt in the project root.

For development with auto-restart on file changes:

npm run dev

Configuration

All configuration is done via environment variables:

Variable Default Description
PORT 3000 Port the server listens on
DATA_DIR ./data Directory for the database and admin token file
UPLOADS_DIR ./data/uploads Directory for uploaded images and cached favicons
RELEASES_REPO Tomasneto404/linkPage GitHub owner/repo polled by the in-app update checker. Override to point at a fork or private mirror.

API Overview

Read endpoints are public. All write endpoints require the X-Admin-Token header.
If a public password is configured, read endpoints also require an X-Public-Password header.

Method Endpoint Auth Description
POST /api/auth/verify Verify admin token
POST /api/auth/verify-public Verify public password
POST /api/auth/rotate-token Admin Generate a new admin token
GET /api/settings Get site title, logos, favicon, pinned group, password status
POST /api/settings/site-title Admin Set the site title
POST /api/settings/logo/:variant Admin Upload logo (light or dark)
DELETE /api/settings/logo/:variant Admin Remove logo
POST /api/settings/favicon Admin Upload custom browser-tab favicon
DELETE /api/settings/favicon Admin Remove custom favicon
POST /api/settings/pinned-group Admin Pin a default group for the public page
POST /api/settings/public-password Admin Set public password
DELETE /api/settings/public-password Admin Remove public password
POST /api/settings/save-favicons Admin Toggle auto-save of fetched favicons into the icon library
GET /api/icons Admin List every icon in the reusable library with usage counts
POST /api/icons Admin Upload a new icon directly into the library
DELETE /api/icons/:id Admin Delete an icon (also clears it from any link using it)
POST /api/icons/bulk-delete Admin Delete multiple icons at once
GET /api/icons/export Admin Export the whole icon library as a self-contained JSON bundle
POST /api/icons/import Admin Import an icon-library bundle (idempotent, de-duped by content)
GET /api/favicon-preview Admin Server-side favicon fetch used by the link form's live preview
GET /api/version Admin Check current build against the latest GitHub release
GET /api/audit Admin List audit-log entries (search, type filter, pagination)
DELETE /api/audit Admin Clear the audit log (or prune with ?days=N)
GET /api/audit/export Admin Export the audit log as CSV or JSON
GET /api/events Server-Sent Events stream — fires whenever data changes, so the public page can fade-refresh in real time
GET /api/links Public List all links (honors is_hidden, group locks, and public password)
POST /api/links Admin Create a link (URL or file upload)
PUT /api/links/:id Admin Update a link (URL or file upload)
POST /api/links/:id/visibility Admin Toggle a link's public visibility
DELETE /api/links/:id Admin Delete a link
GET /api/links/:id/file Admin Read an attached text file's contents (for the editor)
PUT /api/links/:id/file Admin Save edited contents back to an attached text file
POST /api/links/reorder Admin Save new link order
POST /api/links/bulk-delete Admin Delete multiple links
GET /api/links/export Admin Export links, groups, sections & subsections as JSON
POST /api/links/import Admin Import from JSON (idempotent — creates only what's missing)
GET /api/links/:id/clicks Admin Click detail for a link
GET /api/groups Public List all groups (with nested sections/subsections)
POST /api/groups Admin Create a group
PUT /api/groups/:id Admin Update a group (name, color, password, unlock mode)
DELETE /api/groups/:id Admin Delete a group
POST /api/groups/reorder Admin Save new group order
POST /api/groups/:id/unlock Unlock a password-protected group (issues a signed cookie)
POST /api/groups/:id/lock Lock a group (clears the unlock cookie)
POST /api/groups/:id/sections Admin Create a section (or subsection, via parent_section_id) in a group
PUT /api/sections/:id Admin Rename a section
DELETE /api/sections/:id Admin Delete a section (and its subsections)
POST /api/sections/reorder Admin Save new section/subsection order
GET /api/stats Admin Aggregate click stats for all links
GET /r/:id Redirect (or stream a file attachment) and record the click

About

OpenSource Link Management System

Resources

License

Stars

2 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors