Synaplan
开源 AI 软件平台,面向企业:自托管 AI 代理,符合 GDPR 的聊天机器人小部件,RAG 知识库,WhatsApp 与 Outlook AI,MCP,多模型路由。支持 Docker、Kubernetes 或在 Germ 托管。
AI 翻译
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗License key authorization server for Turboware products. Built with Next.js 14 App Router, PostgreSQL, and Prisma ORM. Supports hardware-bound seat-limited licenses, client management, and subscription billing tracking.
| Layer | Technology |
|---|---|
| Framework | Next.js 14 (App Router) |
| Language | TypeScript |
| ORM | Prisma |
| Database | PostgreSQL |
| Styling | Tailwind CSS |
| Auth | jose (JWT, HS256) |
License| Field | Type | Description |
|---|---|---|
id | String (cuid) | Primary key |
key | String (unique) | License key (TW-XXXXXX-XXXXXX-XXXXXX-XXXXXX) |
product | String | Product name |
status | LicenseStatus | ACTIVE, SUSPENDED, REVOKED, EXPIRED |
maxSeats | Int | Max concurrent hardware activations |
expiresAt | DateTime? | Optional expiry date |
clientId | String? | FK to owning Client |
Activation| Field | Type | Description |
|---|---|---|
licenseId | String | FK to License |
hardwareId | String | Unique device identifier provided by the client |
label | String? | Optional human-readable device name |
activatedAt | DateTime | First activation timestamp |
lastSeenAt | DateTime | Updated on each validate/activate call |
[licenseId, hardwareId]is unique — one activation record per device per license.
ClientStores end-customer information: name, email, phone, company, CNPJ. Can have many licenses and one subscription.
SubscriptionLinked to a Client and optionally a License. Tracks billing state: TRIAL, PENDING_PAYMENT, ACTIVE, SUSPENDED, CANCELLED. Includes billingDate (day of month), trial/grace period timestamps.
InvoiceLinked to a Subscription. Types: INSTALLATION, MONTHLY, PRORATED, GRACE_FEE. Statuses: PENDING, PAID, OVERDUE, WAIVED.
TW-XXXXXX-XXXXXX-XXXXXX-XXXXXX
Generated using crypto.randomBytes (Node.js). Character set: ABCDEFGHJKLMNPQRSTUVWXYZ23456789 (unambiguous — no I, O, 0, 1).
POST /api/client/validateCheck if a license key is valid and usable.
Request body:
{ "key": "TW-...", "hardwareId": "optional-device-id" }
Behavior:
hardwareId is provided and already activated, that seat is excluded from the seat-count check (idempotent for existing devices).lastSeenAt on the matching activation if already active.Response (valid):
{
"valid": true,
"license": { "key", "product", "expiresAt", "maxSeats", "activatedSeats" }
}
POST /api/client/activateBind a license key to a hardware device.
Request body:
{ "key": "TW-...", "hardwareId": "device-id", "label": "optional-label" }
Behavior:
lastSeenAt and returns alreadyActivated: true.GET /api/client/status/[key]Public metadata for a license key.
Response:
{
"key", "product", "status", "expiresAt",
"maxSeats", "activatedSeats", "createdAt"
}
statusreflects the effective status — anACTIVElicense past itsexpiresAtis returned asEXPIRED.
| Method | Route | Description |
|---|---|---|
GET | /api/admin/licenses | List all licenses (filterable) |
POST | /api/admin/licenses | Create a new license |
GET | /api/admin/licenses/[id] | Get license details |
PATCH | /api/admin/licenses/[id] | Update status, seats, expiry, etc. |
DELETE | /api/admin/licenses/[id] | Delete a license (owner/admin) |
GET/POST | /api/admin/team | List/create helper operators (create is owner/admin) |
PATCH | /api/admin/team/[id] | Update role or deactivate (owner/admin) |
GET | /api/admin/tickets | Support inbox (filter by status/priority/clientId) |
POST | /api/admin/tickets | Open a ticket for a client |
GET/PATCH | /api/admin/tickets/[id] | Ticket thread + status |
POST | /api/admin/tickets/[id]/messages | Reply as ADMIN |
GET | /api/admin/invoices | All invoices |
Protected by JWT middleware (/admin/*).
| Route | Description |
|---|---|
/login | Admin login page |
/admin | Dashboard with stats |
/admin/clients/new | Guided tenant setup (client + plan + license) |
/admin/licenses | Filterable license list |
/admin/licenses/new | Generate a new license key |
/admin/licenses/[id] | Detail page: revoke, suspend, activate, delete |
/admin/tickets | Customer support inbox |
/admin/invoices | Billing invoice list |
/admin/team | Helper admin users |
POST /api/auth/login with { password } checked against ADMIN_PASSWORD env var.HttpOnly JWT cookie (12h expiry, signed with JWT_SECRET).POST /api/auth/logout clears the cookie./admin/* and /api/admin/* routes.| Variable | Description |
|---|---|
TURBOWARE_DB | PostgreSQL connection string (Render, schema turboware) |
ADMIN_PASSWORD | Password for admin login |
JWT_SECRET | Secret used to sign admin JWT cookies |
CRON_SECRET | Bearer token for /api/cron/billing (required in production; Vercel injects it) |
ASAAS_API_KEY | Asaas payments + webhook asaas-access-token |
STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET | Stripe Checkout + webhook signature |
GLOBAL_CONFIG | Global Config connection string. Vercel injects this when the store is connected to the project. Legacy name EDGE_CONFIG is also read. If neither is set, maintenance mode stays off. |
MAINTENANCE_BYPASS_TOKEN | Optional secret for ?bypass= during maintenance. Set it once; changing it needs a redeploy. Use openssl rand -hex 32. |
Kevin can turn maintenance on for a database cutover and turn it off again without another deploy. Middleware reads the Global Config key maintenance on each request (Vercel’s current name for Edge Config). The value is:
{
"enabled": true,
"message": "Migração do banco de dados em andamento.",
"eta": "hoje às 18h (horário de Brasília)",
"allowIps": ["203.0.113.10"]
}
enabled has to be a boolean. message, eta, and allowIps are optional. A missing store, a missing key, a value that is not that shape, or a failed read all fail open: the site keeps serving. The flag is never allowed to take the site down just because it could not be read.
@vercel/global-config is used instead of @vercel/edge-config. Connecting a store in the current Vercel dashboard writes GLOBAL_CONFIG. The new SDK reads that variable and falls back to legacy EDGE_CONFIG. The old package only reads EDGE_CONFIG, so a newly connected store would leave maintenance stuck off.
While enabled is true:
Retry-After, and Cache-Control: no-store. Copy is Portuguese first, with English underneath. message is shown as written. An ISO-8601 eta is shown in Brasília time; any other string is shown as written. Retry-After is the seconds until an ISO eta (between 60 and 86400), or 3600 when eta is missing or not a timestamp./api/* gets JSON 503: { "error": "maintenance", "message": string | null, "eta": string | null }.Maintenance runs before the admin and client auth checks. A bypass does not skip login.
Team kcbsilvas-projects, project turboware-v2. Create the store if this project does not already have one connected.
turboware. Older screens still say Edge Config; it is the same product, and vercel edge-config is an alias of the commands below.GLOBAL_CONFIG on the project. Do not copy that connection string into git.MAINTENANCE_BYPASS_TOKEN for the same environments (openssl rand -hex 32).add if the dashboard create already made the store, and use the update command with "enabled": false instead):vercel global-config add turboware --scope kcbsilvas-projects \
--items '{"maintenance":{"enabled":false}}'
Dashboard: Storage → turboware → maintenance → edit the JSON → Save.
Or, from a machine logged into the team:
vercel global-config update turboware --scope kcbsilvas-projects --patch '{"items":[{"operation":"upsert","key":"maintenance","value":{"enabled":true,"message":"Migração do banco de dados em andamento.","eta":"hoje às 18h (horário de Brasília)"}}]}'
The update propagates to the edge without a deployment. Give it a few seconds, then hard-refresh.
vercel global-config update turboware --scope kcbsilvas-projects --patch '{"items":[{"operation":"upsert","key":"maintenance","value":{"enabled":false}}]}'
Leaving message and eta off the object is fine. "enabled": false is what opens the site.
allowIps is exact addresses (no CIDR). Middleware compares them to x-vercel-forwarded-for (the client IP Vercel sets), then x-real-ip, then the first x-forwarded-for hop. Edit the same Global Config item to add or remove an IP; that also needs no redeploy.https://<domain>/?bypass=<MAINTENANCE_BYPASS_TOKEN>. On GET/HEAD, middleware sets an HttpOnly cookie tw_maintenance_bypass for 12 hours and redirects to the same URL without the query. Later browser requests, including /api/*, send the cookie. A POST that includes a matching ?bypass= is allowed for that request only and does not set the cookie. Auth still applies. The token has to already be deployed; rotating it is an env change, not how maintenance is toggled. The token appears in that first request URL, so prefer the hex secret above and do not share the link.| Request | During maintenance |
|---|---|
/_next/static, /_next/image, /_next/*, /_vercel/* | Served. These are framework and platform files. Blocking them breaks the bypass session. |
/favicon.ico, /favicon.svg, /favicon-16.png, /favicon-32.png, /apple-touch-icon.png, /slides/* | Served. These are the public icon and slide files. The maintenance document inlines the Turboware mark. Other paths that merely end in an image extension, including /api/admin/licenses/<id>.png and /wiki/<slug>.png, still go through middleware so auth and maintenance both apply. |
/api/cron/billing | 503 JSON. The cron writes invoices and subscription state. A failed run is safer mid-migration; Vercel Cron tries again on the next 08:00 UTC schedule. |
/api/webhooks/stripe, /api/webhooks/asaas | 503 JSON. Both mark invoices paid. Stripe and Asaas retry 503s, which is safer than writing payments while the database is moving. |
Every other page and /api/* route | Maintenance response. License validate/activate included, so product clients get a controlled outage instead of a partial database error. |
# Install dependencies
npm install
# Set up the database
npx prisma migrate dev
# Run the development server
npm run dev
.
├── prisma/
│ └── schema.prisma # Database schema (License, Activation, Client, Subscription, Invoice)
├── src/
│ ├── middleware.ts # Maintenance mode, then JWT auth for /admin and /client
│ ├── lib/
│ │ ├── auth.ts # JWT sign/verify helpers
│ │ ├── license.ts # Key generation, status resolution, usability check
│ │ ├── pricing.ts # Subscription pricing logic
│ │ └── prisma.ts # Prisma client singleton
│ ├── components/
│ │ ├── Taskbar.tsx
│ │ ├── ThemeProvider.tsx
│ │ ├── WorktopDesktop.tsx
│ │ └── WorktopWindow.tsx
│ └── app/
│ ├── layout.tsx
│ ├── page.tsx
│ ├── globals.css
│ ├── api/
│ │ ├── auth/
│ │ │ ├── login/route.ts # POST — admin login (sets JWT cookie)
│ │ │ └── logout/route.ts # POST — admin logout (clears cookie)
│ │ ├── client/
│ │ │ ├── auth/
│ │ │ │ ├── login/route.ts # POST — client login
│ │ │ │ └── logout/route.ts # POST — client logout
│ │ │ ├── activate/route.ts # POST — bind key to hardwareId
│ │ │ ├── validate/route.ts # POST — check if key is valid
│ │ │ ├── status/[key]/route.ts # GET — public key metadata
│ │ │ ├── subscription/
│ │ │ │ ├── route.ts # GET — client subscription info
│ │ │ │ ├── billing-date/route.ts # PATCH — update billing date
│ │ │ │ └── grace/route.ts # POST — request grace period
│ │ ├── admin/
│ │ │ ├── licenses/
│ │ │ │ ├── route.ts # GET, POST — list/create licenses
│ │ │ │ └── [id]/route.ts # GET, PATCH, DELETE — single license
│ │ │ └── clients/
│ │ │ ├── route.ts # GET, POST — list/create clients
│ │ │ └── [id]/
│ │ │ ├── route.ts # GET, PATCH, DELETE — single client
│ │ │ ├── notes/route.ts # POST — add a client note
│ │ │ └── subscription/
│ │ │ ├── route.ts # GET, POST, PATCH — client subscription
│ │ │ └── invoices/[invoiceId]/pay/route.ts # POST — mark invoice paid
│ │ └── register/
│ │ ├── route.ts # POST — new client/product registration
│ │ └── ddns/route.ts # POST — DDNS registration
│ ├── admin/ # Admin UI (protected by middleware)
│ │ ├── layout.tsx
│ │ ├── page.tsx # Dashboard
│ │ ├── login/page.tsx
│ │ ├── licenses/
│ │ │ ├── page.tsx # License list
│ │ │ ├── new/
│ │ │ │ ├── page.tsx
│ │ │ │ └── NewLicenseForm.tsx
│ │ │ └── [id]/
│ │ │ ├── page.tsx
│ │ │ └── LicenseActions.tsx
│ │ └── clients/
│ │ ├── page.tsx # Client list
│ │ ├── new/page.tsx
│ │ └── [id]/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── ClientHeader.tsx
│ │ ├── ClientNavBar.tsx
│ │ └── tabs/
│ │ ├── OverviewTab.tsx
│ │ ├── LicensesTab.tsx
│ │ ├── BillingTab.tsx
│ │ ├── TicketsTab.tsx
│ │ ├── NotesTab.tsx
│ │ └── HistoryTab.tsx
│ ├── client/ # Client portal
│ │ ├── login/page.tsx
│ │ ├── dashboard/
│ │ │ ├── layout.tsx
│ │ │ └── page.tsx
│ │ └── components/
│ │ └── clientheader.tsx
│ └── turboisp/ # TurboISP product pages
│ ├── register/
│ │ ├── layout.tsx
│ │ └── page.tsx
│ └── site/
│ ├── layout.tsx
│ └── page.tsx