@galadrim/galadmin

中文文档
0.3.7发布于 2026/11/12ISCTypeScriptCommonJS近 30 天下载 3177

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

中文使用文档
文档目录18 节
  1. 快开始
  2. 融入 NestJS 应用程序
  3. 在 Express 应用程序中的整合
  4. 行政主管是什么?
  5. 最小示例
  6. 配置
  7. 双要素认证( TOTP)
  8. 密码更改和过期
  9. 警卫
  10. 边栏文件夹
  11. S3 上传文件
  12. Google 云平台上的文件上传( GCP)
  13. 文件上传到远程服务器( 通过 SFTP)
  14. 本地设置
  15. 测试
  16. 实际数据库整合测试
  17. 已知的稳健性限制
  18. 前端测试

加拉德明语Name

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

快开始

npm install @galadrim/galadmin

融入 NestJS 应用程序

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

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

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

带着 MySQL

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

在 Express 应用程序中的整合

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

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

行政主管是什么?

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

最小示例

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",
        },
      ],
    },
  ],
};

配置

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,没有什么变化.

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。 。 。 .

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 视图与根部所申报的完全相同.

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 。 基本配置如下:

...
  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 配置如下 :

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

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

GOOGLE_APPLICATION_CREDENTIALS=service-account-file.json

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

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

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

...
  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 显示每个特性的演示配置 :

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)、进出口和 登录/会话中间软件。 路由级别行为与真实数据库相悖 引擎(见下文);单元套件覆盖纯逻辑和配置接线 没有任何数据库.

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服务器 开始于 测试容器它声称 持续状态, 不仅仅是释放 SQL, 而且需要一个运行中的 Docker 守护进程 .

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 渲染:

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

版本与发布记录

@galadrim/galadmin 共发布 334 个版本,首次发布于 2021/08/11;过去 12 个月发布了 21 个版本。

版本发布日期距今
0.3.7latest2026/11/124 天前
0.3.62026/10/2620 天前
0.3.52026/10/2323 天前
0.3.42026/10/2323 天前
0.3.32026/10/2324 天前
0.3.22026/10/2224 天前

其他发布通道:alpha → 0.3.0-alpha.2.1。

关于 @galadrim/galadmin 的常见问题

@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 和提交记录判断。