# @galadrim/galadmin 中文文档

规范地址：https://npmcn.com/package/%40galadrim/galadmin/

版本：0\.3\.7
npm 元数据更新时间：2026-11-11T18:49:55.552Z
中文译文更新时间：2026-11-15T21:34:00.148Z
软件包许可：ISC

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40galadrim/galadmin
原文对照：https://npmcn.com/package/%40galadrim/galadmin/?lang=original
项目主页：https://github.com/galadrimteam/galadmin#readme
源码仓库：https://github.com/galadrimteam/galadmin

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：5.1 MB，144 个文件
- 模块格式：CommonJS
- TypeScript 类型：包内自带
- Node.js 要求：\>=22
- 运行依赖：29 个
- 首次发布：2021-08-11T12:23:24.894Z
- 最新版本发布：2026-11-11T18:49:55.386Z
- 近 30 天下载量：3177（2026-10-16 至 2026-11-14，npm 官方下载统计）
- 已发布版本数：334（近 12 个月 21 个）

## 中文简介

加拉德明是一个工具 由 [加来德林](https://galadrim.fr/) 让你在几分钟内设置一个漂亮的后台 它与任何 MySQL 数据库合作.

## 安装

```sh
npm install @galadrim/galadmin
```

也可以使用：`pnpm add @galadrim/galadmin`、`yarn add @galadrim/galadmin`、`bun add @galadrim/galadmin`

## README

# 加拉德明语Name

加拉德明是一个工具 由 [加来德林](https://galadrim.fr/) 让你在几分钟内设置一个漂亮的后台 有用吗 与 MySQL 数据库连接.

## 快开始

```bash
npm install @galadrim/galadmin
```

### 融入 NestJS 应用程序

把这个密码放进你的 `src/main.ts` 文件 :

```js
import galadmin from '@galadrim/galadmin';
import { Config } from '@galadrim/galadmin/dist/types/config';

const adminConfig: Config = { ... }

app.getHttpAdapter().use('/admin', galadmin(adminConfig));
```

如果您正在使用 Prisma ORM, 请在您的 `schema.prisma`。 。 。 . 加拉德明会在你的数据库里创建一个表格,而Prisma将无法 如果我们不指定,则正确执行迁移.

#### 使用 PostgreSQL

```prisma
model galadmin_sessions {
  sid    String   @id @db.VarChar(128)
  sess   Json
  expire DateTime
}
```

#### 带着 MySQL

```prisma
model galadmin_sessions {
  session_id String  @id @db.VarChar(128)
  expires    Int     @db.UnsignedInt
  data       String? @db.MediumText
}
```

### 在 Express 应用程序中的整合

```js
const galadmin = require("@galadrim/galadmin").default;

app.use("/admin", galadmin(adminConfig));
```

### 行政主管是什么?

_管理员配置_ 是包含全部配置信息的对象 关于你的后台。 其属性列于下.

### 最小示例

```js
const adminConfig = {
  name: "Mon projet",
  // Secrets come from the environment, never from the code:
  // generate the session secret with `openssl rand -hex 32`.
  sessionSecret: process.env.GALADMIN_SESSION_SECRET,
  auth: {
    username: "admin",
    password: process.env.GALADMIN_ADMIN_PASSWORD,
  },
  postgresql: {
    host: "abc.example.com",
    user: "monprojet",
    password: process.env.DB_PASSWORD,
    database: "monprojet",
  },
  // mysql: {
  //   host: "abc.example.com",
  //   user: "root",
  //   password: "password",
  //   database: "monprojet",
  // },
  views: [
    {
      type: "table",
      label: "Fruits",
      tableName: "fruits",
      columns: [
        {
          label: "Nom",
          name: "fruitName",
        },
        {
          label: "Acidité",
          name: "acidity",
        },
      ],
    },
  ],
};
```

## 配置

```js
const adminConfig = {
  /** Nom du projet (OBLIGATOIRE) */
  name: "Mon projet",

  /**
   * Chaîne de caractères aléatoires qui permet de chiffrer les cookies de session (OBLIGATOIRE).
   * À générer (`openssl rand -hex 32`) et à lire depuis l'environnement, jamais en dur dans le code.
   */
  sessionSecret: process.env.GALADMIN_SESSION_SECRET,

  /** Identifiants permettant d'accéder au back-office (OBLIGATOIRE) */
  auth: {
    /** Nom d'utilisateur */
    username: "admin",

    /** Mot de passe, long et unique, lu depuis l'environnement */
    password: process.env.GALADMIN_ADMIN_PASSWORD,
  },

  /** Identifiants de connexion à la base de données (OBLIGATOIRE) */
  postgresql: {
    host: "abc.example.com",
    user: "monprojet",
    password: process.env.DB_PASSWORD,
    database: "monprojet",
  },
  // mysql: {
  //   host: "abc.example.com",
  //   user: "root",
  //   password: "password",
  //   database: "monprojet",
  // },

  /** Clé API Google Maps pour les champs adresses et l'affichage de cartes */
  googleMapsKey: "______",

  /**
   * Origine autorisée à appeler l'API avec les cookies de session depuis un autre domaine
   * (en-têtes CORS). Aucune par défaut ; "http://localhost:3000" pour le serveur de dev du front.
   */
  corsOrigin: undefined,

  /** Taille maximale, en octets, des fichiers d'un upload ou d'un import (illimitée par défaut) */
  uploadMaxFileSize: 50 * 1024 * 1024,

  /** Désactiver la sélection multiple dans les vues de type table */
  hideMultiselect: true,

  /** Cache l'inscription Made with love by Galadrim */
  hideBranding: true,

  views: [
    {
      /** Vue de type "table" */
      type: "table",

      /** Nom affiché de la vue */
      label: string,

      /** Icône FontAwesome de la vue dans le menu. user affiche l'icône fa-user. */
      icon: "user",

      /** Nom de la table MySQL */
      tableName: "users",

      /** Nombre de résultats par page */
      limit: 100,

      /**
       * Ces champs permettent de personnaliser la requête MySQL qui gère l'affichage de la table.
       * Ils sont tous optionnels.
       *
       * SELECT {selectExpression} FROM {tableName} {join} WHERE {filters} GROUP BY {groupBy} ORDER BY {orderBy} {desc ? "DESC" : ""}
       */
      selectExpression: "*, MAX(age) AS maxAge",
      join: "JOIN activities ON users.activity_id = activities.id",
      groupBy: "activity_id",
      orderBy: "maxAge",
      isDesc: true,
      filters: "age < 50",

      /** Lorsque notClickable est à true, les résultats ne sont pas cliquables. (default: false) */
      notClickable: true,

      /** Rendre possible de créer des enregistrements (default: false) */
      canCreate: true,

      /** Groupe nominal "un nouvel élément" */
      aNewSingular: "un nouvel utilisateur",

      /** Rendre possible de supprimer des enregistrements (default: true) */
      canDelete: true,

      /** Rendre possible de modifier des enregistrements (default: true) */
      canEdit: true,

      /** URL appelée en POST lors de la création d'un nouvel élément, avec l'ID de l'élément créé */
      createCallback: "https://myapi.com/createCallback",

      editCallback: "https://myapi.com/editCallback",
      deleteCallback: "https://myapi.com/deleteCallback",

      columns: [
        {
          /** Nom affiché de la colonne (OBLIGATOIRE) */
          label: "Poids",

          /** Nom de la colonne dans la table MySQL */
          name: "weight",

          /** Formatter la valeur avant de l'afficher */
          formatValue: (user) => `${user.weight} kg`,

          /** Masquer la colonne dans la liste des résultats et ne l'afficher que dans le détail (default: false) */
          hideInTable: true,
        },
      ],
      subviews: [],
      recordViewPanels: [],
    },
  ],
};
```

### 双要素认证( TOTP)

报表 `authRoles` 可以使用认证器应用程序(Google认证器, Authy...). 词根 `auth` 账户和账户 `trustedAuth` 永远不会受到影响。 无 `twoFactor`,没有什么变化.

```js
authRoles: [
  {
    role: "admin",
    table: "Admin",
    usernameField: "username",
    passwordField: "password",
    idField: "id",
    twoFactor: {
      /** Colonne texte nullable qui stocke le secret TOTP ; null = 2FA désactivée pour ce compte (OBLIGATOIRE) */
      secretField: "twoFactorSecret",

      /** Oblige chaque compte à configurer la 2FA à sa prochaine connexion, sans pouvoir la désactiver (default: false) */
      enforce: false,

      /** Nom affiché dans l'application d'authentification (default: config.name) */
      issuer: "Mon projet",

      /** Chiffre les secrets en base (AES-256-GCM). Valeur aléatoire longue, stockée hors de la base. La changer rend les secrets existants illisibles */
      encryptionKey: process.env.GALADMIN_2FA_KEY,

      /** Colonnes nullables entier + date/heure : verrouille le compte 15 minutes après 5 codes erronés */
      failedAttemptsField: "twoFactorFailedAttempts",
      lockedUntilField: "twoFactorLockedUntil",

      /** Colonne bigint nullable : refuse un code déjà utilisé */
      lastUsedStepField: "twoFactorLastUsedStep",
    },
  },
],
```

当2FA是可选时,用户从侧边栏菜单"双认证"中打开或关闭. 要重置用户的 2FA( 丢失的电话), 设置 `secretField` 恢复为无效。 从不显示 `secretField` 在风景。 这个角色的 `table` 必须是可写入的(不是 SQL 视图).

没有锁出栏,只将一个登录在5个错误代码后发回密码步骤,所以重复的尝试不会被每个账户节制.

### 密码更改和过期

报表 `authRoles` 可以更改自己的密码,密码也可以过期. 词根 `auth` 账户和账户 `trustedAuth` 永远不会受到影响。 无 `passwordPolicy`,没有什么变化。 不管有没有用 `twoFactor`。 。 。 .

```js
authRoles: [
  {
    role: "admin",
    table: "Admin",
    usernameField: "username",
    passwordField: "password",
    idField: "id",
    useArgon2: true,
    passwordPolicy: {
      /** Ajoute "Changer mon mot de passe" au menu (default: false, toujours actif avec expiry) */
      allowChangeOwn: true,

      /** Vérifie un nouveau mot de passe : renvoie le message affiché à l'utilisateur, ou null s'il est accepté (sync ou async) */
      validate: (password) => (password.length >= 12 ? null : "Le mot de passe doit contenir au moins 12 caractères"),

      expiry: {
        /** Colonne date/heure nullable : date du dernier changement ; null = changement demandé à la prochaine connexion (OBLIGATOIRE) */
        changedAtField: "passwordChangedAt",

        /** Durée de validité d'un mot de passe, en jours (OBLIGATOIRE) */
        maxAgeDays: 365,

        /** Un bandeau invite à changer de mot de passe pendant les N derniers jours (default: 30) */
        warnDays: 30,
      },
    },
  },
],
```

更改表请求当前密码, 拒绝当前密码作为新密码, 运行 `validate`,然后在 `useArgon2` 已经设定, bcrypt 否则。 当前密码必须存储散列: 密码被存储的账户不能从galadmin中更改 .

与 `expiry`,登录密码比 `maxAgeDays` (或 `changedAtField` 无效)在打开后台前,在2FA代码存在后,请求新的密码。 允许在一张空的桌子上 `changedAtField` 使每个账户在下次登录时选择新的密码;首先用当前日期填入列来避免.

`validate` 财务报告和审定财务报表 `changedAtField` 仅适用于从galadmin更改的密码。 在视图中输入的密码是写成的 : 散列并在您一边验证, 然后设定 `changedAtField` (例如,在下次登录时强制更改无效).

### 警卫

-   **密码 `authRoles` 账户** 须以散列( bcrypt, 或 argon2) 存储 `useArgon2`) (中文(简体) ). 保存所需密码的遗留表格 `allowPlaintextPasswords: true` ;即使如此,看起来像散列的存储值也从未像现在这样比较,空密码也从未登录。 尽快取消密码.
-   **登录尝试** 限制:在一个地址的用户名失败5次后,其登录被拒绝15分钟(包括root账户)。 计数器在过程内存中直播.
-   **`filtersFromUser` 财务报告和审定财务报表 `filters`** 绑定每个读取或修改一个记录的路由(`/record`, (中文(简体) ), `/updateRecord`, (中文(简体) ), `/deleteRecord(s)`, (中文(简体) ), `/customAction`,面板行):列表中不显示答案的行404. 以 `customQuery` 不受限制,因为其列表既不适用过滤器.
-   **`/api/config`** 从不发送数据库证书, `auth`, (中文(简体) ), `authRoles`, (中文(简体) ), `trustedAuth`,或者 `s3Config` 编号 : `gcpConfig` 编号 : `sftpConfig` 编号 : `publicPath` 页:1.
-   **上传** 保持他们的 `public-read` S3上的ACL, 但 HTML, SVG, XML 和 JavaScript 文件被存储 `Content-Disposition: attachment` 在S3和GCP上,这样打开他们的URL下载. 本地或超过 SFTP 写入的文件由您自己的服务器服务: 不服务后台域的内置这些类型 .
-   **会议**饼干是 `HttpOnly`, (中文(简体) ), `SameSite=Lax`,以及 `Secure` 当请求通过 HTTPS (设置) `app.set("trust proxy", 1)` 在 TLS 终止代理后面). 每个登录获得新的会话编号; `POST /api/logout` 关闭日志 。 答复包括: `X-Frame-Options: SAMEORIGIN` 财务报告和审定财务报表 `X-Content-Type-Options: nosniff`。 。 。 .

从0.3.6升级: `GET /api/logout` 成为 `POST`, (中文(简体) ), `/api/adjacentRecordId` (未被正面使用)不见了, CORS 信头需要 `corsOrigin`,并按需要存储密码 `allowPlaintextPasswords`。 。 。 .

### 边栏文件夹

视图可在侧边栏中组合为 `type: "folder"` 视图。 一个文件夹是 从来没有页面本身: 它只分组其他视图, 所以组的 URL 视图与根部所申报的完全相同.

```js
views: [
  {
    type: "folder",

    /** Nom affiché du dossier */
    label: "Catalogue",

    /** Ouvrir le dossier par défaut (default: false).
        Un dossier contenant la vue active s'ouvre automatiquement. */
    defaultOpen: true,

    /** Vues regroupées dans le dossier (un seul niveau de dossiers) */
    views: [
      {
        type: "table",
        label: "Fruits",
        tableName: "fruits",
        columns: [
          { label: "Nom", name: "fruitName" },
          { label: "Acidité", name: "acidity" },
        ],
      },
    ],
  },
]
```

注释:

-   文件夹只嵌入一等深处( S)`views` 无法包含其它文件夹 .
-   `roles` 编号 : `readRoles` 在一个文件夹门上 整个小组的能见度, 就像他们在景色上一样.
-   `hidden: true` 在一个文件夹中隐藏整个组从侧边栏 分组视图仍可按其URL访问,如隐藏视图.

### S3 上传文件

存在不同的列类型,其中之一是FileColumn,允许 上传一个文件到 AWS S3 。 基本配置如下:

```js
...
  columns: [{
    label: 'Image',
    name: 'imageUrl',
    dataType: 'file',
    s3Config: {
      host: process.env.AWS_S3_HOST,
      /** Optional. When omitted, the AWS SDK default credential chain is used. */
      accessKeyId: process.env.AWS_ACCESS_KEY_ID,
      secretAccessKey: process.env.AWS_SECRET_KEY,
      bucket: process.env.AWS_S3_PODCAST_BUCKET_NAME,
      /** Optional, only needed when your bucket isn't public and you need to generate a signed URL */
      signedUrlNeeded: true,
      /** Optional, only needed when your bucket isn't public and you need to generate a signed URL */
      region: 'eu-west-3',
    },
  }]
```

何时 `accessKeyId` aWS SDK 将尝试解决证书本身( 第一局胜出) :

1.  Env vers AWS ACESS KEY ID + AWS SECRET ACESS KEY (+ AWS SESSION TOKEN),如果设定了AWS PROFILE,则跳过
2.  配置文件 AWS PROFILE(或"默认"),来源于~/.aws/config和~/.aws/证书(SSO,静态密钥,所扮演的角色,证书 进程)
3.  网络身份标志: AWS WEB IDENTITY Token FILE + AWS ROLE ARN(例如EKS) 互联网档案馆的存檔,存档日期2011-12-20
4.  ECS 容器作用: AWS Condenter Credentials relatical URI 或 AWS Contenterials FullL URI
5.  EC2 实例角色通过实例元数据服务, 除非 AWS EC2 METADATA DISABLED 被设定为任何除外的选项 `false`

建议依赖角色而不是在服务器上添加长寿的AWS证书:EC2/Elastic Beanstalk上的实例配置角色,或ECS/Fargate上的任务角色(不是执行角色). 为该角色添加以下权限 :

```
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "s3:PutObject",
      "s3:PutObjectAcl"
    ],
    "Resource": "arn:aws:s3:::<bucket>/<subfolder>/*"
  }]
}
```

-   无 `subfolder`,使用时 `arn:aws:s3:::<bucket>/*` 作为资源.
-   默认情况下,文件上传为 `public-read` ACL,所以桶必须允许ACLs:其对象所有权不能是"被执行的"(否则) `AccessControlListNotSupported`),以及 `BlockPublicAcls` 必须关闭( 否则 `AccessDenied`) (中文(简体) ).
-   与 `signedUrlNeeded: true`,没有设置ACL:仅限 `s3:PutObject` 需要,而桶可以保持隐私.
-   如果桶被 SSE- KMS 加密, 也允许 `kms:GenerateDataKey` 它的 KMS 密钥.

### Google 云平台上的文件上传( GCP)

您也可以使用FileColumn上传文件到Google Cloud存储. 要使用GCP而不是AWS S3, 将 gcpConfig 配置如下 :

```js
...
  columns: [{
    label: 'Image',
    name: 'imageUrl',
    dataType: 'file',
    gcpConfig: {
      host: process.env.GCP_STORAGE_HOST,
      bucket: process.env.GCP_BUCKET_NAME,
    },
  }]
```

您还需要在您的服务账户中设置 `.env` 文件 :

```bash
GOOGLE_APPLICATION_CREDENTIALS=service-account-file.json
```

确保服务账户JSON文件在您的项目目录中,并拥有写入您Google Cloud存储桶的必要权限.

### 文件上传到远程服务器( 通过 SFTP)

可以使用FileColumn允许通过SFTP上传到远程服务器. 基本配置如下:

```js
...
  columns: [{
    label: 'Image',
    name: 'imageUrl',
    dataType: 'file',
    publicPath: '/home/ubuntu/project/public',
    subdirectory: 'img',
    sftpConfig: {
      host: process.env.SFTP_HOST,
      user: process.env.SFTP_USER,
      password: process.env.SFTP_PASSWORD,
    },
  }]
```

## 本地设置

本地游乐场,内有真实的 MySQL + PostgreSQL 容器,种子数据和 a 显示每个特性的演示配置 :

```bash
npm run playground:db && npm run playground:build
npm run playground:schema && npm run playground:seed
npm run playground          # http://localhost:8080/admin/ (admin / admin)
```

见 `playground/README.md` 、涵盖的特征和 `DB=pg|mysql|mysql2` 后端开关 .

## 测试

后端有一个回归测试套件(Jest + ts-jest + supertest)覆盖 aPI 路由、授权矩阵、 MySQL 的 SQL 生成以及 PostgreSQL 方言、文件上传(S3/GCP/SFTP/local)、进出口和 登录/会话中间软件。 路由级别行为与真实数据库相悖 引擎(见下文);单元套件覆盖纯逻辑和配置接线 没有任何数据库.

```bash
npm run typecheck       # check backend TypeScript without emitting build files
npm test                # run the unit suite (no database required)
npm run test:watch      # watch mode
npm run test:coverage   # unit + integration merged report (Docker required)
```

### 实际数据库整合测试

第二套套间(`tests/integration/`) 运行相同的路线,以对抗真实的 (原始内容存档于2017-06-31) (中文(简体) ). MySQL 8 (简体) `mysql` 财务报告和审定财务报表 `mysql2` postgreSQL 16服务器 开始于 [测试容器](https://node.testcontainers.org/)它声称 持续状态, 不仅仅是释放 SQL, 而且需要一个运行中的 Docker 守护进程 .

```bash
npm run test:integration   # TEST_INTEGRATION=1, jest.integration.config.js
npm run test:all           # unit suite + integration suite
```

`npm test` 从不启动容器. `npm run test:integration` 需要一个 可达的 Docker 守护进程, 且没有它就失败了, 因此在 CI 中一个损坏的 Docker 设置 不能无声地通过.

这个 `CI` GitHub 动作工作流程运行排入、后端单元和集成 测试,以及牵引请求的前端测试。 部署工作流程重用 工作流程相同,只有在工作流程过后才能建立和出版。 要求这些 合并前的检查, 请按照目标中的要求配置 CI 测试检查 分支的保护规则.

### 已知的稳健性限制

前者 `tests/security-known-gaps.test.ts` 套件编码的未固定安全 作为预期的安全性测试,还存在强大的差距。 从那时起,它的所有差距都是 固定的,其测试被折叠成普通套房(`routes.*.test.ts`, (中文(简体) ), `dbManager.test.ts`因此不需要特别跑步了 `npm test` 覆盖 他们.

仍然有记录表明存在一些强性限制(不能对HTTP进行断言) 因为错误作为未处理的拒绝在 express 4 中逃脱:

-   扔东西 `parseImportFile` 内 `/recordsImport` 或者说 `/subrecordsImport` 实值 等待着多党召回的尝试/捕捉——这500人从不 发送和进程会崩溃.
-   `formatValueForInsertion(false)` 返回时 `"NULL"`,所以布尔 `false` 无法导入 .
-   本地用户上传后端需要 `subdirectory` (公开Path +) 未定义 concat 生成一个不存在的上传目录 .
-   扔东西 `editCallbackFunction` 内 `/updateRecord` 正在等待外边的任何 尝试/捕获——更新是承诺的,但请求将永远搁置.

### 前端测试

反应战线( R)`src/front`)有一个小型烟雾套房(CRA / 反应标记) ) 覆盖纯的 utils , 外部点击处理器, 第一视图 重定向和未经认证的 App 渲染:

```bash
cd src/front && npm test -- --watchAll=false
```

## 常见问题

### @galadrim/galadmin 是什么？

加拉德明是一个工具 由 加来德林 让你在几分钟内设置一个漂亮的后台 它与任何 MySQL 数据库合作\.

### 如何安装 @galadrim/galadmin？

在项目目录运行 npm install @galadrim/galadmin，或使用 pnpm add / yarn add / bun add。

### @galadrim/galadmin 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @galadrim/galadmin 支持 ESM 还是 CommonJS？

以 CommonJS 形式发布，可以直接 require\(\)；在 ES Module 项目中也可以用 import 引入（Node\.js 会把 module\.exports 作为默认导出）。

### @galadrim/galadmin 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=22。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @galadrim/galadmin 可以商用吗？

@galadrim/galadmin 使用 ISC 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @galadrim/galadmin 的下载量有多少？

根据 npm 官方下载统计，@galadrim/galadmin 在 2026/10/16 – 2026/11/14 这 30 天内被下载 3,177 次，日均约 106 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @galadrim/galadmin 还在维护吗？

最新版本 0\.3\.7 发布于 2026/11/12（4 天前）；至今共发布 334 个版本，其中过去 12 个月发布了 21 个；首次发布于 2021/08/11。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

