An open API service indexing awesome lists of open source software.

https://github.com/sctg-development/techno-viewer


https://github.com/sctg-development/techno-viewer

age-encryption dxf gerber react vite

Last synced: 8 days ago
JSON representation

Awesome Lists containing this project

README

          

# Techno-Viewer

**Techno-Viewer** is an open-source, client-side secure portal for browsing, decrypting and viewing encrypted engineering assets — DXF drawings, PDF documentation, XLSX spreadsheets, and source code — directly in the browser. No private key ever leaves the client.

[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue)](https://www.typescriptlang.org/)
[![React](https://img.shields.io/badge/React-19-blue)](https://react.dev/)
[![Vite](https://img.shields.io/badge/Vite-8-646cff)](https://vitejs.dev/)
[![AGE encryption](https://img.shields.io/badge/AGE-v1-green)](https://age-encryption.org/)
[![Code](https://tokeisrv.sctg.eu.org/b1/github.com/sctg-development/fufuni?type=Rust,TypeScript,TSX,html&category=code)]()
[![Comments](https://tokeisrv.sctg.eu.org/b1/github.com/sctg-development/fufuni?type=Rust,ypeScript,TSX,html&category=comments)]()

---

## Why Techno-Viewer?

Engineering teams routinely share sensitive assets — circuit schematics, mechanical drawings, firmware source — with a limited set of partners or customers. Techno-Viewer lets you:

- **Publish encrypted assets on any static host** (Cloudflare Pages, GitHub Pages, S3…)
- **Grant access per-user via AGE key pairs** — revoke by re-encrypting without the user's key
- **View DXF, PDF, XLSX and source code inline**, with AI-assisted code explanation and translation
- **Never expose plaintext files** to the server or CDN — decryption happens 100% in the browser

---

## Features

| Feature | Details |
|---|---|
| AGE decryption | Client-side, browser-native, zero server key handling |
| DXF viewer | Full CAD rendering via `@mlightcad/cad-viewer` |
| PDF viewer | Inline PDF rendering |
| XLSX viewer | Spreadsheet rendering via `xlsx` |
| Source code viewer | Monaco Editor with syntax highlighting |
| AI translation | Translate code comments to English (requires AI proxy or BYOK) |
| AI explanation | Structured technical explanation of source files |
| AI junior comments | Annotate code for junior developers |
| **AI Settings (BYOK)** | **Configure a custom OpenAI-compatible provider with your own API key; auto-discovers models from the `/models` endpoint; falls back gracefully on 403** |
| Batch download | Download selected files as a ZIP archive |
| Persistent cache | Encrypted IndexedDB cache (LRU, 300 MiB default) |
| i18n | French, English, Chinese UI |
| Analytics | Optional PostHog file-view events (best-effort, backend-enriched) |

---

## Live Demo

> Copy the private key below and paste it into the login screen of your deployed instance.

**Test user 1 key** (decrypts the sample assets in this repository):

```
AGE-SECRET-KEY-1TUTW5YJN0NZ52ZJFVV9QHELJH5MY9ZL00YNG9LTK0QMP0YQJDFUQ5Y8NTH
```

The matching public key is `age102v6m86rmsv2hcqwhdk2zg5u6nsluh9jhrueletsl6zjkvkjg54s4yaj67`.
All five test key pairs are in [keys/private/](keys/private/) and [keys/public/](keys/public/).

The sample encrypted assets include:
- `drawings/` — DXF technical drawing (`test.dxf`)
- `schematics/` — Electronic schematic DXF (`BMS_Buck-Boost.dxf`)
- `doc/` — PDF calculation note (`LM2587_BuckBoost_50W_Note_de_Calcul.pdf`)
- `agro-crypt/` — [agro-crypt](https://github.com/sctg-development/agro-crypt) C source code

---

## Screenshots

- Pseudo Login
image

- DXF Viewer
image

- Code Viewer
image

- Gerber viewer
image

- AI Settings
image

---

## Architecture

```
Browser (React 19 + Vite 8)

├── Auth: AGE private key entered by user, stored in sessionStorage
├── Tree: encrypted manifest (public/files.json.age) fetched and decrypted
├── Files: /encrypted/*.age fetched on demand, decrypted in-browser
└── Viewers: DXF / PDF / XLSX / Monaco rendered from decrypted bytes

(optional)
Cloudflare Pages Function
/api/file-viewed → PostHog
```

**Key source locations:**

| Path | Role |
|---|---|
| [src/config.ts](src/config.ts) | **Single-file template configuration** (branding, languages, theme, AI) |
| [src/main.tsx](src/main.tsx) | App entry point |
| [src/App.tsx](src/App.tsx) | Router shell |
| [src/context/AuthContext.tsx](src/context/AuthContext.tsx) | AGE key session state |
| [src/hooks/useAgeDecrypt.ts](src/hooks/useAgeDecrypt.ts) | File decryption flow |
| [src/hooks/useFileTree.ts](src/hooks/useFileTree.ts) | Encrypted manifest loading |
| [src/services/encryptedFileCache.ts](src/services/encryptedFileCache.ts) | IndexedDB LRU cache |
| [src/services/aiSettings.ts](src/services/aiSettings.ts) | AI provider config & BYOK service |
| [src/pages/AiSettings.tsx](src/pages/AiSettings.tsx) | AI Settings page (/settings) |
| [src/services/fileViewAnalytics.ts](src/services/fileViewAnalytics.ts) | Analytics client |
| [functions/api/file-viewed.js](functions/api/file-viewed.js) | Cloudflare Pages Function |
| [rencrypt/](rencrypt/) | Rust CLI — key gen, encryption, manifest |

---

## Quick Start

### Prerequisites

- Node.js 20+
- Rust toolchain (for `rencrypt`)

### 1. Install dependencies

```bash
npm install
```

### 2. Build rencrypt

```bash
cd rencrypt && cargo build --release && cd ..
```

### 3. Generate key pairs

```bash
rencrypt/target/release/rencrypt -v generate-keys --keys=keys --count=5
```

### 4. Add your assets and encrypt

```bash
# Place your files under drawings/, schematics/, doc/, or any custom path
rencrypt/target/release/rencrypt encrypt \
--path drawings/ \
--path schematics/ \
--path doc/ \
--keep-plaintext-manifest -v
```

This writes encrypted files to `encrypted/` and the manifest to `public/files.json.age`.

### 5. Configure environment

```bash
cp .env.example .env
# Edit .env with your values
```

### 6. Run in development

```bash
npm run dev:env
```

### 7. Build for production

```bash
npm run build:env
```

---

## Template Integration & Reuse

Techno-Viewer is designed to be forked and rebranded with minimal effort.
**All user-facing configuration lives in a single file: [`src/config.ts`](src/config.ts).**
Every constant is JSDoc-documented with its effect, the files that consume it, and an example value.

### Quick-start checklist

1. Fork / clone the repository.
2. Open **`src/config.ts`** and work through the sections below.
3. Replace **`public/favicon.svg`** with your own icon.
4. Update **`src/index.css`** if you change the accent colour.
5. Add or remove locale files under `src/i18n/locales/` to match your language set.
6. Run `npm run dev:env` and verify the result.

---

### 1 — Branding

All text identity of the application is controlled by four constants in `src/config.ts`:

| Constant | Where it appears | Example |
|---|---|---|
| `APP_NAME` | Navbar header strip (between coordinate markers) | `'MY PRODUCT'` |
| `APP_TITLE` | Browser tab caption (`document.title`) | `'MY PRODUCT — Engineering Portal'` |
| `APP_DESCRIPTION` | HTML `` (SEO / share cards) | `'Secure viewer for…'` |
| `APP_FOOTER_LABEL` | Page footer, left side | `'MY PRODUCT — Acme Corp'` |

`APP_TITLE` is applied at runtime by `main.tsx`. Keep the `` tag in `index.html` in sync to avoid a visible flash before React hydrates (a comment in that file reminds you).

**Home page hero content** (title, subtitle, tagline, quick-links labels, spec table values) is fully
translatable — edit the matching keys in `src/i18n/locales/.json` (see §5 below).

---

### 2 — Favicon & app icon

Replace `public/favicon.svg` with your own SVG (or PNG — update the `` tag in `index.html` accordingly).

The default icon uses the same two brand colours as the theme (`#c0392b` accent on `#f4eed7` paper). If you change the accent colour (§3), update the icon too.

```svg


```

---

### 3 — Theme (colours & fonts)

The visual identity is defined in two places that **must stay in sync**:

#### `src/config.ts` — JavaScript side

```ts
// Colours used in inline SVG illustrations (CSS variables are not
// accessible inside SVG stroke/fill attributes).
export const THEME_ACCENT_COLOR = '#c0392b' // primary / accent
export const THEME_PAPER_COLOR = '#f4eed7' // warm paper fill
```

These values are read at runtime by `Home.tsx` (the decorative schematic drawing).

#### `src/index.css` — CSS side

Three places to update when changing the accent colour:

```css
@theme {
--color-tech-red: #c0392b; /* Tailwind utility: text-tech-red, border-tech-red … */
--color-tech-red-dark: #922b21;
--color-tech-red-light: #e74c3c;
}

.light {
--accent: #c0392b; /* HeroUI semantic token */
--primary: #c0392b;
/* primary-foreground should be legible on top of the accent colour */
}
```

**To replace the paper palette** (cream tones), update the `--color-paper-*` variables in `@theme` and the `--background`, `--surface`, `--muted` tokens in `.light`.

**To change fonts**, replace the Google Fonts `` in `index.html` and update `--font-sans` / `--font-mono` in `@theme`.

---

### 4 — Languages

The language switcher and the i18n runtime are both driven by `src/config.ts`:

```ts
export const LANGUAGES = [
{ code: 'fr', label: 'FR' },
{ code: 'en', label: 'EN' },
{ code: 'zh', label: '中' },
] as const

export const DEFAULT_LANGUAGE: Language = 'fr'
```

The `Language` type is **automatically derived** from this array — adding an entry widens the type without any manual union edit.

#### Adding a language

```bash
# 1. Append an entry to LANGUAGES in src/config.ts:
# { code: 'de', label: 'DE' }

# 2. Create the locale file (copy English as a starting point):
cp src/i18n/locales/en.json src/i18n/locales/de.json
# … then translate the values

# 3. Register the import in src/i18n/index.ts:
# import de from './locales/de.json'
# resources: { …, de: { translation: de } }
```

#### Removing a language

Remove the entry from `LANGUAGES` and delete (or keep) the locale JSON — unused imports are tree-shaken by Vite.

#### i18n key reference

All page content that can be customised lives in `src/i18n/locales/.json`.
Key sections:

| Key prefix | Content |
|---|---|
| `hero.*` | Home page title, subtitle, tagline |
| `home.*` | Brand label, quick-link labels, spec table, schematic labels |
| `nav.*` | Navigation link labels |
| `contact.*` | Company name, e-mail, address |
| `auth.*` | Login modal text |
| `ai.*` | AI settings page labels |

---

### 5 — AI proxy

```ts
// src/config.ts
export const AI_PROXY_BASE_URL = 'https://ai-proxy.inet.pp.ua'
export const AI_PROXY_DEFAULT_PROVIDER_ID = 'groq'
export const AI_PROXY_DEFAULT_MODEL = 'meta-llama/llama-4-scout-17b-16e-instruct'
```

`AI_PROXY_BASE_URL` can also be set at **build time** via the environment variable `AI_PROXY_URL` (takes precedence over the constant — useful for CI/CD without touching source files).

The provider preset list shown in the AI Settings page UI is defined in `src/services/aiSettings.ts` (`PROVIDER_PRESETS`). Add or remove entries there to control which shortcuts appear.

You can deploy your own AI proxy by reusing the [sctg-development/ai-proxy-cloudflare](https://github.com/sctg-development/ai-proxy-cloudflare) project, or any OpenAI-compatible proxy that supports the `/models` endpoint for live model discovery.

---

### 6 — Storage prefix

```ts
export const STORAGE_PREFIX = 'MY-APP'
```

This single string is prepended to every browser-storage key:

| Storage | Key pattern |
|---|---|
| `localStorage` | `MY-APP_ai_settings_v1` |
| `IndexedDB` | `MY-APP-encrypted-cache` |
| Monaco AI cache | `MY-APP_monaco_ai_v1` |

> **Warning:** changing `STORAGE_PREFIX` after a production deployment invalidates all users' cached data and AI settings in the browser. Users will need to reconfigure their AI provider.

---

### 7 — Navigation & routes

The application route table is in [`src/App.tsx`](src/App.tsx). Each route maps a URL to a page component. To remove a section (e.g. Schematics):

1. Delete the `` entry in `App.tsx`.
2. Remove the matching nav link from the array in `AppNavbar.tsx` (lines 75–82).
3. Remove the `nav.schematics` translation key if desired.

To add a page, reverse the steps: create the component, add a ``, add a nav entry, and add the translation key.

---

## Encryption Model

```
Source file ──► rencrypt encrypt ──► encrypted/.age

└── AGE v1 X25519, encrypted for every public key in keys/public/

Manifest (files.json) ──► rencrypt encrypt ──► public/files.json.age
```

- Each file is encrypted once per recipient public key stored in `keys/public/`
- The manifest maps virtual paths (e.g. `drawings/en/test.dxf`) to encrypted blob hashes
- Revoke access by removing a public key and re-running `rencrypt encrypt`

### Rencrypt CLI reference

```bash
# Generate N key pairs
rencrypt generate-keys --keys=keys --count=5

# Encrypt source trees
rencrypt encrypt --path ./drawings --path ./doc [--keys keys] [--encrypted-dir encrypted]

# Extract DXF strings to glossary CSV
rencrypt extract --path drawings/en --glossary glossaire.csv

# AI-translate glossary
rencrypt translate --lang=en,cn --ai-json-enc --ai-cryptoken
```

See [USAGE.md](USAGE.md) for the full workflow.

---

## Deployment

The project deploys to Cloudflare Pages out of the box. The GitHub Actions workflow in [.github/workflows/](.github/workflows/) injects `POSTHOG_PROJECT_TOKEN` and `POSTHOG_HOST` from repository secrets before running `wrangler pages deploy`.

Any static host with custom headers support works — just ensure:

1. `encrypted/` directory is served with `Cache-Control: no-store`
2. SPA fallback routes all 404s to `index.html`

See [public/_headers](public/_headers) and [public/_redirects](public/_redirects).

---

## AI Features (Optional)

The Monaco viewer can translate, explain and annotate source code using an LLM via a configurable proxy. Set `AI_PROXY_URL` in `.env` (or hardcode the proxy URL in the source). The proxy must be compatible with the OpenAI chat completions API.

A compatible proxy: [sctg-development/ai-proxy-cloudflare](https://github.com/sctg-development/ai-proxy-cloudflare)

Authentication uses the user's AGE private key as a bearer token — no additional credential required.

### BYOK — Bring Your Own Key

If the built-in proxy returns **HTTP 403** (quota exceeded, key revoked, etc.), users can configure their own OpenAI-compatible provider directly in the browser via the **AI Settings** page (gear icon in the navbar):

- **Provider presets**: OpenAI, Google Gemini, Groq, OpenRouter, Mistral AI, Together AI — or any custom URL.
- **Live model discovery**: clicking "Fetch Models" queries the provider's `/models` endpoint with the entered API key and populates the model selector automatically.
- **Proxy key override**: users can also provide a dedicated API key for the built-in proxy without switching to a custom provider.
- All settings are stored in `localStorage` and read at request time — no page reload required.

Settings are managed by `src/services/aiSettings.ts` and the page is at `/settings` (`src/pages/AiSettings.tsx`).

---

## Persistent Encrypted Cache

To reduce repeated network fetches, the app stores encrypted payloads in IndexedDB:

- Backend: IndexedDB with `StorageManager` persistence request
- Eviction: LRU
- Default budget: 300 MiB
- Cache metrics visible in the UI (entries, size, hits/misses, origin quota)

Only encrypted bytes are cached — decrypted content lives only in memory during the active session.

---

## Analytics

Every file opened emits a `File Viewed` event; every ZIP download emits a `Files Downloading` event. Both are sent to a Cloudflare Pages Function at `/api/file-viewed` which enriches them with IP and geo data before posting to PostHog.

The browser never sends the user's private key to the backend — it derives and sends only the public key.

---

## Technology Stack

| Layer | Technology |
|---|---|
| Frontend framework | React 19 + TypeScript |
| Build tool | Vite 8 |
| UI components | HeroUI, Tailwind CSS 4 |
| CAD rendering | `@mlightcad/cad-viewer`, `@sctg/tracespace-view` |
| Code editor | Monaco Editor |
| Encryption | `age-encryption` (AGE v1 X25519) |
| Archiving | JSZip |
| i18n | i18next + react-i18next |
| Backend | Cloudflare Pages Functions |
| Analytics | PostHog |
| Encryption CLI | Rust (`rencrypt`) |

---

## Repository Layout

```
techno-viewer/
├── src/ React application source
│ ├── components/ UI components (viewers, layout…)
│ ├── context/ Auth context
│ ├── hooks/ useAgeDecrypt, useFileTree, …
│ ├── i18n/ Translation resources (fr, en, zh)
│ ├── pages/ Home, Drawings, Documentation, Contact
│ └── services/ Cache, analytics
├── public/ Static assets + files.json.age manifest
├── encrypted/ AGE-encrypted file blobs (generated)
├── drawings/ Source DXF drawings (plaintext, not committed in prod)
├── schematics/ Source electronic schematics
├── doc/ Source documentation (PDF…)
├── agro-crypt/ Sample C source (agro-crypt project)
├── keys/
│ ├── public/ AGE public keys (committed, one per user)
│ └── private/ AGE private keys (NEVER commit to a public repo)
├── rencrypt/ Rust CLI source
├── functions/ Cloudflare Pages Functions
└── tools/ Python helper scripts (DXF→PDF, AI translation…)
```

> **Security note:** The `keys/private/` directory in this repository contains **test-only** keys generated for demonstration purposes. For a production deployment, generate fresh keys, **never commit private keys**, and distribute them to users through a secure channel.

---

## Contributing

Contributions are welcome. Please open an issue to discuss significant changes before submitting a pull request.

---

## License

MIT — see [LICENSE.md](LICENSE.md).

Copyright (c) 2024-2026 Ronan Le Meillat — SCTG Development