Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ docker compose -f docker-compose.dev.yml up -d # PostgreSQL + Redis
pnpm install:all # frontend + backend dependencies
cp apps/api/backend/.env.example apps/api/backend/.env # defaults match the containers above
pnpm db:init # create tables + seed data (first run)
pnpm dev # open http://localhost:8888
pnpm dev # open http://127.0.0.1:8888
```

Sign in as `admin` / `123456`. Switching to SQL Server / MySQL, S3, skipping the captcha and running tests are covered in [CONTRIBUTING.md](./CONTRIBUTING.md#本地开发) (Chinese; the commands are language-independent).
Sign in as `admin` with the password you set during `pnpm db:init`. Switching to SQL Server / MySQL, S3, skipping the captcha and running tests are covered in [CONTRIBUTING.md](./CONTRIBUTING.md#本地开发) (Chinese; the commands are language-independent).

## Quality

Expand Down
4 changes: 2 additions & 2 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ docker compose -f docker-compose.dev.yml up -d # PostgreSQL + Redis
pnpm install:all # 前后端依赖
cp apps/api/backend/.env.example apps/api/backend/.env # 默认配置已对齐上面的容器
pnpm db:init # 建表 + 初始数据(首次)
pnpm dev # 打开 http://localhost:8888
pnpm dev # 打开 http://127.0.0.1:8888
```

账号 `admin` / `123456`。换 SQL Server / MySQL、接 S3、跳过验证码、跑测试,见[本地开发指南](./CONTRIBUTING.md#本地开发)。
账号 `admin`,密码是 `pnpm db:init` 时设的。换 SQL Server / MySQL、接 S3、跳过验证码、跑测试,见[本地开发指南](./CONTRIBUTING.md#本地开发)。

部署到服务器:`main` 每次通过 CI 都会构建好镜像,服务器上 `bash prod.sh pull && bash prod.sh up` 即可更新,见 [`deploy/README.md`](./deploy/README.md)。

Expand Down
13 changes: 12 additions & 1 deletion apps/docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ export default defineConfig({
// 主题和后台同一套:Inter / JetBrains Mono 自托管(不走 Google Fonts —— 国内打不开),品牌紫见 theme.css
customCss: ['@fontsource-variable/inter', '@fontsource-variable/jetbrains-mono', './src/styles/theme.css'],
// 首页 hero 换成登录页那套品牌面板 + 立体榫卯(src/components/Hero.astro)
components: { Hero: './src/components/Hero.astro' },
// 顶栏的主题切换换成太阳 / 月亮两段式(src/components/ThemeSelect.astro)
components: { Hero: './src/components/Hero.astro', ThemeSelect: './src/components/ThemeSelect.astro' },
social: [{ icon: 'github', label: 'GitHub', href: REPO }],
editLink: { baseUrl: `${REPO}/edit/main/apps/docs/` },
// 每页显示「最后更新」—— 读 git 历史,所以 CI 里 checkout 要 fetch-depth: 0,
Expand All @@ -90,11 +91,21 @@ export default defineConfig({
translations: { en: 'Getting started' },
items: [{ autogenerate: { directory: 'getting-started' } }],
},
{
label: '指南',
translations: { en: 'Guides' },
items: [{ autogenerate: { directory: 'guides' } }],
},
{
label: '功能',
translations: { en: 'Features' },
items: [{ autogenerate: { directory: 'features' } }],
},
{
label: '参考',
translations: { en: 'Reference' },
items: [{ autogenerate: { directory: 'reference' } }],
},
{
label: '链接',
translations: { en: 'Links' },
Expand Down
93 changes: 93 additions & 0 deletions apps/docs/src/components/ThemeSelect.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
/**
* 覆盖 Starlight 的 ThemeSelect:顶栏里那个「自动 ▾」下拉换成太阳 / 月亮两段式切换。
*
* 状态沿用 Starlight 自己的约定(localStorage `starlight-theme`、`<html data-theme>`),
* 所以首屏防闪烁的那段内联脚本(ThemeProvider)照常生效。没选过时跟随系统(auto),
* 高亮的是系统当前那一侧;点一下就固定成浅色或深色。
*/
const label = Astro.locals.t('themeSelect.accessibleLabel')
const light = Astro.locals.t('themeSelect.light')
const dark = Astro.locals.t('themeSelect.dark')
---

<fra-theme-toggle class="theme-toggle" role="group" aria-label={label}>
<button type="button" data-value="light" aria-label={light} title={light}>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
</svg>
</button>
<button type="button" data-value="dark" aria-label={dark} title={dark}>
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M20.5 14.3A8.5 8.5 0 0 1 9.7 3.5a8.5 8.5 0 1 0 10.8 10.8z" />
</svg>
</button>
</fra-theme-toggle>

<script>
const KEY = 'starlight-theme'
const system = () => (matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark')
const stored = () => {
try {
const v = localStorage.getItem(KEY)
return v === 'light' || v === 'dark' ? v : 'auto'
} catch {
return 'auto'
}
}
const apply = (pref: string) => {
const theme = pref === 'auto' ? system() : pref
document.documentElement.dataset.theme = theme
for (const btn of document.querySelectorAll<HTMLButtonElement>('fra-theme-toggle button')) {
btn.setAttribute('aria-pressed', String(btn.dataset.value === theme))
}
}
matchMedia('(prefers-color-scheme: light)').addEventListener('change', () => {
if (stored() === 'auto') apply('auto')
})

class FraThemeToggle extends HTMLElement {
connectedCallback() {
apply(stored())
for (const btn of this.querySelectorAll<HTMLButtonElement>('button')) {
btn.addEventListener('click', () => {
const value = btn.dataset.value!
try { localStorage.setItem(KEY, value) } catch {}
apply(value)
})
}
}
}
customElements.define('fra-theme-toggle', FraThemeToggle)
</script>

<style>
.theme-toggle {
display: inline-flex;
gap: 0.15rem;
padding: 0.2rem;
border-radius: 999px;
box-shadow: inset 0 0 0 1px var(--sl-color-gray-5);
}
button {
display: grid;
place-items: center;
width: 1.85rem;
height: 1.85rem;
padding: 0;
border: 0;
border-radius: 999px;
cursor: pointer;
color: var(--sl-color-gray-3);
background: transparent;
transition: background-color 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}
button:hover { color: var(--sl-color-white) }
button[aria-pressed='true'] {
color: var(--sl-color-white);
background: var(--sl-color-black);
box-shadow: 0 1px 3px oklch(0 0 0 / 0.25), 0 0 0 1px var(--sl-color-gray-5);
}
svg { width: 1rem; height: 1rem }
</style>
13 changes: 13 additions & 0 deletions apps/docs/src/content/docs/en/getting-started/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ bash prod.sh pull && bash prod.sh up

Back up the database before the first deployment against an existing one.

## Checklist before going live

- [ ] Every `CHANGE_ME` in `.env.server` is replaced. With `ENVIRONMENT='prod'` the backend checks length and randomness and refuses to start on weak values
- [ ] `TOKEN_SECRET_KEY` is freshly generated (`python -c "import secrets;print(secrets.token_urlsafe(32))"`), not copied from another environment
- [ ] `CORS_ALLOWED_ORIGINS` is your real address; behind nginx or a load balancer, `TRUSTED_PROXIES` is set — otherwise rate limits and IP allow-lists see the proxy's address
- [ ] If you use the AI assistant, object storage, group bots or the open platform, their encryption keys are set (`AI_ENCRYPT_SECRET_KEY`, `STORAGE_ENCRYPT_SECRET_KEY`, `NOTIFICATION_ENCRYPT_SECRET_KEY`, `OPEN_SECRET_KEY`) — without them saving those settings fails instead of storing plaintext
- [ ] `NOTIFICATION_WEB_BASE_URL` is your real address, or links in emails and group messages point at `localhost:8888`
- [ ] The admin password is the strong one set during initialization, not the demo's `123456`
- [ ] The database is backed up before the first deployment against it, and scheduled backups exist
- [ ] TLS is provided by the reverse proxy or load balancer, and HTTP redirects to HTTPS
- [ ] External monitoring probes `/api/health/status`, not `/healthz`
- [ ] You noted the current image tag (`sha-<commit>`) so you know what to roll back to

## How do I update or roll back?

```bash
Expand Down
31 changes: 26 additions & 5 deletions apps/docs/src/content/docs/en/getting-started/quick-start.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
title: Quick start
description: Run fastapi-react-admin locally — PostgreSQL and Redis in Docker, five commands, then sign in at localhost:8888 as admin / 123456.
description: Run fastapi-react-admin locally — PostgreSQL and Redis in Docker, five commands, set the admin password during init, then sign in at 127.0.0.1:8888.
sidebar:
order: 1
---

Five commands bring up the whole stack locally: PostgreSQL and Redis in Docker, the FastAPI backend on port 8088, the React frontend on port 8888 and a Celery worker. Sign in as `admin` / `123456`.
Five commands bring up the whole stack locally: PostgreSQL and Redis in Docker, the FastAPI backend on port 8088, the React frontend on port 8888 and a Celery worker. The account is `admin`, with the password you set during `pnpm db:init`.

## What do I need installed?

Expand All @@ -19,11 +19,32 @@ Five commands bring up the whole stack locally: PostgreSQL and Redis in Docker,
docker compose -f docker-compose.dev.yml up -d # PostgreSQL 16 + Redis
pnpm install:all # JS workspace, Python deps, git hooks, Playwright Chromium
cp apps/api/backend/.env.example apps/api/backend/.env # defaults already match the containers above
pnpm db:init # create tables + seed data (first run only)
pnpm db:init # tables + seed data + a new admin password (first run only)
pnpm dev # api :8088 · web :8888 · celery worker
# open http://127.0.0.1:8888
```

Open <http://localhost:8888> and sign in as `admin` / `123456`.
Open <http://127.0.0.1:8888> and sign in as `admin` with the password you set during `pnpm db:init`.

:::note[Why not admin / 123456?]
The seed's `123456` is not allowed to survive initialization: `pnpm db:init` asks for a new admin password (non-interactively it reads `FBA_INIT_ADMIN_PASSWORD`) and disables the sample `test` account. `admin` / `123456` is only the [live demo](https://fra.wubunan.com/sign-in) account.
:::

:::caution[Use 127.0.0.1, not localhost]
The frontend calls `http://127.0.0.1:8088` directly. Opened from `localhost:8888`, the browser treats that as cross-site and does not keep the refresh-token cookie: sign-in works, but **a page refresh sends you back to the sign-in page**.
:::

### How do I know each step worked?

| Step | Check |
|---|---|
| `docker compose … up -d` | `docker ps` shows `fba_postgres` and `fba_redis` as `healthy` |
| `pnpm install:all` | finishes without errors, and `apps/api/.venv` exists |
| `pnpm db:init` | the last line says the new database was stamped at the migration head; `pnpm db:current` shows a revision marked `(head)` |
| `pnpm dev` | the sign-in page shows a **captcha image** — the backend serves it, so seeing it means frontend and backend are connected |
| Sign in | you land on the dashboard, which starts with "you have N things waiting" |

Stuck on a step? Search the error text in the [FAQ](/en/reference/faq/).

:::caution
`pnpm db:init` drops and recreates every table. Run it on the first setup or when you want a clean database; after that, schema changes go through `pnpm db:upgrade` (Alembic).
Expand All @@ -48,7 +69,7 @@ Everything optional is in the same `docker-compose.dev.yml`; turn it on and chan
| **S3-compatible storage** (RustFS) | `docker compose -f docker-compose.dev.yml --profile s3 up -d rustfs` | Set `STORAGE_ENCRYPT_SECRET_KEY` in `.env` (`openssl rand -hex 32`) before adding a storage under **System → Storage** |

:::caution
Keep `DATABASE_PK_MODE='snowflake'` (the default). Seed data only exists for snowflake IDs; in autoincrement mode the seed step is skipped silently and you end up with tables but no `admin` account.
Keep `DATABASE_PK_MODE='snowflake'` as in `.env.example`. Seed data only exists for snowflake IDs; in autoincrement mode (or if `.env` lacks the line — the code default is `autoincrement`) initialization exits with an error saying the seed has no admin account.
:::

See [Databases](/en/features/databases/) for how the three databases are supported and tested.
Expand Down
Loading
Loading