├── .dockerignore ├── .env ├── .eslintrc.json ├── .github └── workflows │ └── push.yml ├── .gitignore ├── .prettierrc ├── .sequelizerc ├── Dockerfile ├── LICENSE ├── Procfile ├── README.md ├── docker-compose.yaml ├── nodemon.json ├── package.json ├── prisma ├── migrations │ ├── 20220713101214_init │ │ └── migration.sql │ └── migration_lock.toml └── schema.prisma ├── public ├── custom.css ├── favicon.ico ├── gtm.js └── locales │ ├── en │ └── translation.json │ └── mk │ └── translation.json ├── src ├── admin │ ├── admin.utils.ts │ ├── components.bundler.ts │ ├── components │ │ ├── debug.tsx │ │ ├── design-system-examples │ │ │ ├── index.ts │ │ │ └── modal-example.tsx │ │ ├── detailed-stats.tsx │ │ ├── dont-touch-this-action.tsx │ │ ├── login.tsx │ │ ├── products-list.tsx │ │ ├── sidebar-resource-section.tsx │ │ ├── thumb.tsx │ │ └── top-bar.tsx │ ├── constants │ │ └── authUsers.ts │ ├── features │ │ └── useEnvironmentVariableToDisableActions.ts │ ├── index.ts │ ├── locale │ │ ├── de │ │ │ ├── common.json │ │ │ ├── components.json │ │ │ ├── index.ts │ │ │ └── pages.json │ │ ├── en │ │ │ ├── common.json │ │ │ ├── complicated.json │ │ │ ├── components.json │ │ │ ├── index.ts │ │ │ ├── pages.json │ │ │ └── person.json │ │ └── index.ts │ ├── pages │ │ ├── custom-page.tsx │ │ ├── design-system-examples │ │ │ ├── blog-page.tsx │ │ │ ├── buttons-page.tsx │ │ │ ├── form-page.tsx │ │ │ ├── icons-page.tsx │ │ │ ├── illustrations-page.tsx │ │ │ ├── index.tsx │ │ │ ├── messages-page.tsx │ │ │ ├── modal-page.tsx │ │ │ ├── tabs-page.tsx │ │ │ └── typography-page.tsx │ │ └── index.ts │ ├── router.ts │ └── types │ │ └── index.ts ├── index.ts ├── scripts │ ├── truncate-mongodb.ts │ └── truncate-postgres.ts ├── servers │ ├── express │ │ └── index.ts │ ├── fastify.ts │ ├── hapijs.ts │ └── nestjs │ │ ├── admin │ │ └── admin.setup.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── database.providers.ts │ │ ├── index.ts │ │ ├── mongoose │ │ └── mongoose.module.ts │ │ └── prisma │ │ └── prisma.service.ts ├── sources │ ├── mikroorm │ │ ├── config.ts │ │ ├── migrations │ │ │ └── Migration20220714094312.ts │ │ ├── models │ │ │ ├── car.model.ts │ │ │ ├── index.ts │ │ │ ├── owner.model.ts │ │ │ └── seller.model.ts │ │ ├── resources │ │ │ ├── car.resource.ts │ │ │ ├── index.ts │ │ │ ├── owner.resource.ts │ │ │ └── seller.resource.ts │ │ └── seeds │ │ │ ├── data │ │ │ ├── cars.ts │ │ │ ├── index.ts │ │ │ ├── owners.ts │ │ │ └── sellers.ts │ │ │ └── run.ts │ ├── mongoose │ │ ├── models │ │ │ ├── admin.model.ts │ │ │ ├── article.model.ts │ │ │ ├── category.model.ts │ │ │ ├── comment.model.ts │ │ │ ├── complicated.model.ts │ │ │ ├── index.ts │ │ │ └── user.model.ts │ │ ├── resources │ │ │ ├── admin.resource.ts │ │ │ ├── article.resource.ts │ │ │ ├── category.resource.ts │ │ │ ├── comment.resource.ts │ │ │ ├── complicated.resource.ts │ │ │ ├── index.ts │ │ │ └── user.resource.ts │ │ └── seeds │ │ │ ├── data │ │ │ ├── articles.ts │ │ │ ├── categories.ts │ │ │ ├── comments.ts │ │ │ ├── index.ts │ │ │ └── users.ts │ │ │ └── run.ts │ ├── objectionjs │ │ ├── knexfile.cjs │ │ ├── migrations │ │ │ └── 20220826123456_initial_schema.cjs │ │ ├── models │ │ │ ├── index.ts │ │ │ ├── manager.entity.ts │ │ │ └── office.entity.ts │ │ ├── resources │ │ │ ├── index.ts │ │ │ ├── manager.resource.ts │ │ │ └── office.resource.ts │ │ └── utils │ │ │ └── base-model.ts │ ├── prisma │ │ ├── config.ts │ │ ├── migrations │ │ │ ├── 20220218111814_init │ │ │ │ └── migration.sql │ │ │ └── migration_lock.toml │ │ ├── resources │ │ │ ├── index.ts │ │ │ ├── post.resource.ts │ │ │ ├── profile.resource.ts │ │ │ └── publisher.resource.ts │ │ └── seeds │ │ │ ├── data │ │ │ ├── index.ts │ │ │ ├── posts.ts │ │ │ ├── profiles.ts │ │ │ └── publishers.ts │ │ │ └── run.ts │ ├── rest │ │ └── crypto-database.ts │ ├── sequelize │ │ ├── config.js │ │ ├── functions │ │ │ └── get-sum.function.ts │ │ ├── hooks │ │ │ ├── get-products.hook.ts │ │ │ └── get-sum.hook.ts │ │ ├── index.ts │ │ ├── interfaces.ts │ │ ├── migrations │ │ │ └── 20220214101918-init.cjs │ │ ├── models │ │ │ ├── cart.model.ts │ │ │ ├── category.model.ts │ │ │ ├── index.ts │ │ │ ├── order.model.ts │ │ │ └── product.model.ts │ │ ├── resources │ │ │ ├── cart.resource.ts │ │ │ ├── category.resource.ts │ │ │ ├── index.ts │ │ │ ├── order.resource.ts │ │ │ └── product.resource.ts │ │ └── seeds │ │ │ ├── data │ │ │ ├── carts.ts │ │ │ ├── categories.ts │ │ │ ├── index.ts │ │ │ ├── orders.ts │ │ │ └── products.ts │ │ │ └── run.ts │ └── typeorm │ │ ├── config.ts │ │ ├── enums │ │ └── country.enum.ts │ │ ├── handlers │ │ └── validate-email.handler.ts │ │ ├── interfaces.ts │ │ ├── migrations │ │ └── 1644569575919-init.ts │ │ ├── models │ │ ├── index.ts │ │ ├── organization.entity.ts │ │ └── person.entity.ts │ │ ├── resources │ │ ├── index.ts │ │ ├── organization.resource.ts │ │ └── person.resource.ts │ │ └── seeds │ │ ├── data │ │ ├── index.ts │ │ ├── organizations.ts │ │ └── persons.ts │ │ └── run.ts └── themes │ ├── custom-theme │ ├── components │ │ ├── LoggedIn.tsx │ │ └── SidebarFooter.tsx │ ├── index.ts │ ├── overrides.ts │ ├── style.css │ └── theme.bundle.js │ └── index.ts ├── tsconfig.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | SERVER="EXPRESS" # EXPRESS HAPIJS FASTIFY NESTJS 3 | SESSION_SECRET=s3sS10n_secr3t 4 | LOCALE=en 5 | 6 | MONGO_DATABASE_URL=mongodb://localhost:27017/adminjs-example-app 7 | POSTGRES_DATABASE_URL=postgres://adminjs:adminjs@localhost:5435/adminjs 8 | MYSQL_DATABASE_URL=mysql://root:adminjs@localhost:3308/adminjs?schema=public 9 | DATABASE_LOGGING=true 10 | DATABASE_SYNC=false 11 | DISABLE_ADMINJS_ACTIONS=false 12 | 13 | GITHUB_URL=https://github.com/SoftwareBrothers/adminjs/issues 14 | SLACK_URL=https://adminjs.page.link/slack 15 | DOCUMENTATION_URL=https://adminjs.co 16 | STORYBOOK_URL=https://adminjs-storybook-beta.web.app/ 17 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "jest": true, 5 | "node": true 6 | }, 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { 9 | "project": "tsconfig.json", 10 | "sourceType": "module", 11 | "ecmaFeatures": { 12 | "jsx": true 13 | }, 14 | "ecmaVersion": 2022 15 | }, 16 | "plugins": [ 17 | "@typescript-eslint", 18 | "react", 19 | "prettier" 20 | ], 21 | "extends": [ 22 | "eslint:recommended", 23 | "plugin:@typescript-eslint/eslint-recommended", 24 | "plugin:@typescript-eslint/recommended", 25 | "plugin:react/recommended", 26 | "plugin:react/jsx-runtime", 27 | "plugin:react-hooks/recommended", 28 | "prettier" 29 | ], 30 | "rules": { 31 | "prettier/prettier": "error", 32 | "@typescript-eslint/no-var-requires": "off", 33 | "@typescript-eslint/ban-ts-comment": "off", 34 | "@typescript-eslint/no-explicit-any": "off", 35 | "react/jsx-uses-react": "error" 36 | }, 37 | "settings": { 38 | "react": { 39 | "version": "detect" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | on: push 3 | 4 | jobs: 5 | test: 6 | name: test 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | - name: Setup 12 | uses: actions/setup-node@v2 13 | with: 14 | node-version: "18.x" 15 | - uses: actions/cache@v2 16 | id: yarn-cache 17 | with: 18 | path: node_modules 19 | key: ${{ runner.os }}-node_modules-${{ hashFiles('**/yarn.lock') }} 20 | restore-keys: | 21 | ${{ runner.os }}-node_modules- 22 | - name: Install 23 | if: steps.yarn-cache.outputs.cache-hit != 'true' 24 | run: yarn install 25 | - name: Lint 26 | run: yarn lint 27 | - name: Build 28 | run: yarn build 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # parcel-bundler cache (https://parceljs.org/) 72 | .cache 73 | .parcel-cache 74 | 75 | # Next.js build output 76 | .next 77 | out 78 | 79 | # Nuxt.js build / generate output 80 | .nuxt 81 | dist 82 | 83 | # Gatsby files 84 | .cache/ 85 | # Comment in the public line in if your project uses Gatsby and not Next.js 86 | # https://nextjs.org/blog/next-9-1#public-directory-support 87 | # public 88 | 89 | # vuepress build output 90 | .vuepress/dist 91 | 92 | # Serverless directories 93 | .serverless/ 94 | 95 | # FuseBox cache 96 | .fusebox/ 97 | 98 | # DynamoDB Local files 99 | .dynamodb/ 100 | 101 | # TernJS port file 102 | .tern-port 103 | 104 | # Stores VSCode versions used for testing VSCode extensions 105 | .vscode-test 106 | 107 | # yarn v2 108 | .yarn/cache 109 | .yarn/unplugged 110 | .yarn/build-state.yml 111 | .yarn/install-state.gz 112 | .pnp.* 113 | 114 | node_modules 115 | .nyc_output 116 | coverage 117 | .DS_store 118 | .adminjs 119 | .adminbro 120 | .idea 121 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "singleQuote": true, 4 | "printWidth": 120, 5 | "trailingComma": "all" 6 | } 7 | -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | require('dotenv').config(); 3 | 4 | module.exports = { 5 | 'config': path.resolve('src', 'sources', 'sequelize', 'config.js'), 6 | 'models-path': path.resolve('src', 'sources', 'sequelize', 'models'), 7 | 'seeders-path': path.resolve('src', 'sources', 'sequelize', 'seeders'), 8 | 'migrations-path': path.resolve('src', 'sources', 'sequelize', 'migrations') 9 | } 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | WORKDIR /admin 4 | 5 | ENV NODE_ENV="production" 6 | ENV TZ="UTC" 7 | 8 | COPY package.json ./ 9 | COPY yarn.lock ./ 10 | 11 | RUN yarn install --frozen-lockfile --production 12 | COPY . . 13 | 14 | RUN npm i -g typescript 15 | RUN yarn build 16 | RUN npx prisma generate 17 | RUN rm -rf src 18 | 19 | ENV ADMIN_JS_SKIP_BUNDLE="true" 20 | 21 | EXPOSE 3000 22 | CMD yarn start 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 SoftwareBrothers.co 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | release: yarn migration:up:production 2 | web: yarn start 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AdminJS-example-app 2 | 3 | Example application using [adminjs](https://github.com/SoftwareBrothers/adminjs) 4 | 5 | ## Demo 6 | 7 | You can check out the current demo version at: https://demo.adminjs.co 8 | 9 | Login: admin@example.com 10 | Password: password 11 | 12 | ## Prerequisites 13 | 14 | Install Docker if you don't have it: https://docs.docker.com/desktop/#download-and-install 15 | 16 | Run: 17 | ```bash 18 | $ docker-compose up -d 19 | ``` 20 | to setup databases. 21 | 22 | Make sure your `.env` file is configured. If you didn't do any changes to `docker-compose.yml` file, 23 | the default contents of the `.env` file should work for you. 24 | 25 | ## Starting the app 26 | 27 | First, install all dependencies 28 | 29 | ```bash 30 | yarn install --frozen-lockfile 31 | ``` 32 | 33 | Make sure you have all environmental variables set up (read the previous paragraph). 34 | 35 | Then create postgres database and run migrations: 36 | 37 | ```bash 38 | $ npx prisma generate # # this sets up Prisma Client in your node_modules 39 | $ yarn migration:up 40 | ``` 41 | 42 | Note: If you see the error below when Prisma MySQL migration is run: 43 | ``` 44 | Error: P1017: Server has closed the connection. 45 | ``` 46 | Please wait a minute or two for the MySQL server to start and retry. 47 | 48 | In the end, you can launch the app 49 | 50 | ```bash 51 | $ yarn build:watch # keep it running if developing 52 | $ yarn start:dev # in a separate terminal tab, concurrently 53 | ``` 54 | 55 | By default the app will be available under: `http://localhost:3000/admin` 56 | 57 | ## Developing the app 58 | 59 | The best way of developing the app is to do this via https://github.com/SoftwareBrothers/adminjs-dev. 60 | 61 | Alternatively, you can fork and clone each repository separately and link them using: 62 | 63 | * `yarn link` 64 | * `npm link` 65 | 66 | to see your local changes. 67 | 68 | #### Sequelize 69 | ##### migrations 70 | - `yarn sequelize migration:generate --name init` 71 | - `yarn sequelize db:migrate` 72 | - `yarn sequelize db:migrate:undo` 73 | 74 | #### Typeorm 75 | ##### migrations 76 | - `yarn typeorm migration:generate -n init` 77 | - `yarn typeorm migration:run` 78 | - `yarn typeorm migration:revert` 79 | 80 | 81 | #### mikro-orm 82 | ##### migrations 83 | - `yarn mikro-orm migration:create` 84 | - `yarn mikro-orm migration:up` 85 | - `yarn mikro-orm migration:down` 86 | 87 | #### prisma 88 | - `npx prisma migrate dev --schema prisma/schema.prisma` 89 | 90 | ## License 91 | 92 | AdminJS is copyrighted © 2023 rst.software. It is a free software, and may be redistributed under the terms specified in the [LICENSE](LICENSE.md) file. 93 | 94 | ## About rst.software 95 | 96 | 97 | 98 | We’re an open, friendly team that helps clients from all over the world to transform their businesses and create astonishing products. 99 | 100 | * We are available for [hire](https://www.rst.software/estimate-your-project). 101 | * If you want to work for us - check out the [career page](https://www.rst.software/join-us). 102 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | mongo_db: 5 | container_name: adminjs-example-mongo 6 | image: mongo 7 | ports: 8 | - "27017:27017" 9 | volumes: 10 | - mongo_db_example_app:/data/db 11 | 12 | postgres_db: 13 | container_name: adminjs-example-postgres 14 | image: postgres 15 | environment: 16 | - POSTGRES_DB=adminjs 17 | - POSTGRES_USER=adminjs 18 | - POSTGRES_PASSWORD=adminjs 19 | ports: 20 | - "5435:5432" 21 | volumes: 22 | - postgres_db_example_app:/var/lib/postgresql/data 23 | 24 | mysql_db: 25 | container_name: adminjs-example-mysql 26 | image: mysql 27 | environment: 28 | MYSQL_USER: adminjs 29 | MYSQL_PASSWORD: adminjs 30 | MYSQL_ROOT_PASSWORD: adminjs 31 | MYSQL_DATABASE: adminjs 32 | ports: 33 | - "3308:3306" 34 | volumes: 35 | - mysql_db_example_app:/var/lib/mysql 36 | 37 | volumes: 38 | mongo_db_example_app: 39 | postgres_db_example_app: 40 | mysql_db_example_app: 41 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "dist" 4 | ], 5 | "ext": "js,json", 6 | "ignore": [ 7 | "*.spec.*", 8 | "test" 9 | ], 10 | "exec": "node ./dist/index.js" 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "type": "module", 7 | "license": "ISC", 8 | "engines": { 9 | "node": ">=18" 10 | }, 11 | "scripts": { 12 | "lint": "eslint src --ext .ts,.tsx", 13 | "typeorm": "./node_modules/typeorm/cli.js --dataSource dist/sources/typeorm/config.js", 14 | "sequelize": "yarn sequelize-cli --config src/sources/sequelize/config.js", 15 | "knex": "dotenv -c '.env' -- npx knex --knexpath dist/sources/objectionjs/index.js --knexfile dist/sources/objectionjs/knexfile.cjs", 16 | "mikroorm": "node --experimental-specifier-resolution=node --loader ts-node/esm ./node_modules/.bin/mikro-orm", 17 | "migration:up": "yarn sequelize db:migrate && yarn typeorm migration:run && yarn mikroorm migration:up && yarn prisma migrate dev --schema prisma/schema.prisma && yarn knex migrate:latest", 18 | "migration:up:production": "yarn sequelize db:migrate && yarn typeorm migration:run && yarn mikro-orm migration:up && yarn prisma migrate deploy --schema prisma/schema.prisma", 19 | "docker:up": "docker-compose -f docker-compose.yaml up -d", 20 | "start:dev": "nodemon", 21 | "start": "node dist/index.js", 22 | "build": "yarn clean && tsc && yarn copy-theme", 23 | "build:watch": "tsc -w", 24 | "postgres:clean": "node dist/scripts/truncate-postgres", 25 | "mysql:clean": "prisma migrate reset --force && prisma migrate deploy", 26 | "mongodb:clean": "node dist/scripts/truncate-mongodb", 27 | "db:clean": "yarn postgres:clean && yarn mysql:clean && yarn mongodb:clean", 28 | "seed:typeorm": "node dist/sources/typeorm/seeds/run", 29 | "seed:sequelize": "node dist/sources/sequelize/seeds/run", 30 | "seed:mongoose": "node dist/sources/mongoose/seeds/run", 31 | "seed:mikro-orm": "node dist/sources/mikroorm/seeds/run", 32 | "seed:prisma": "node dist/sources/prisma/seeds/run", 33 | "seed:all": "yarn seed:typeorm && yarn seed:sequelize && yarn seed:mongoose && yarn seed:mikro-orm && yarn seed:prisma", 34 | "clean": "rimraf dist", 35 | "copy-theme": "copyfiles -u 1 src/themes/**/theme.bundle.js src/themes/**/*.css dist/" 36 | }, 37 | "mikro-orm": { 38 | "useTsNode": true, 39 | "configPaths": [ 40 | "./dist/sources/mikroorm/config.js", 41 | "./src/sources/mikroorm/config.ts" 42 | ] 43 | }, 44 | "prisma": { 45 | "schema": "prisma/schema.prisma" 46 | }, 47 | "dependencies": { 48 | "@adminjs/express": "^6.0.0", 49 | "@adminjs/fastify": "^4.0.0", 50 | "@adminjs/hapi": "^7.0.0", 51 | "@adminjs/koa": "^4.0.0", 52 | "@adminjs/mikroorm": "^3.0.0", 53 | "@adminjs/mongoose": "^4.0.0", 54 | "@adminjs/objection": "^2.0.0", 55 | "@adminjs/passwords": "^4.0.0", 56 | "@adminjs/prisma": "^4.0.0", 57 | "@adminjs/sequelize": "^4.0.0", 58 | "@adminjs/themes": "^1.0.0", 59 | "@adminjs/typeorm": "^5.0.0", 60 | "@faker-js/faker": "^7.6.0", 61 | "@hapi/boom": "^10.0.1", 62 | "@hapi/cookie": "^12.0.1", 63 | "@hapi/hapi": "^21.3.0", 64 | "@koa/router": "^12.0.0", 65 | "@mikro-orm/cli": "^5.6.15", 66 | "@mikro-orm/core": "^5.6.15", 67 | "@mikro-orm/migrations": "^5.6.15", 68 | "@mikro-orm/nestjs": "^5.1.7", 69 | "@mikro-orm/postgresql": "^5.6.15", 70 | "@nestjs/common": "^9.3.12", 71 | "@nestjs/core": "^9.3.12", 72 | "@nestjs/mongoose": "^9.2.2", 73 | "@nestjs/platform-express": "^9.3.12", 74 | "@nestjs/typeorm": "^9.0.1", 75 | "@prisma/client": "^4.11.0", 76 | "adminjs": "^7.0.9", 77 | "ajv-formats": "^2.1.1", 78 | "argon2": "^0.30.3", 79 | "class-transformer": "^0.5.1", 80 | "class-validator": "^0.14.0", 81 | "connect-pg-simple": "^8.0.0", 82 | "cors": "^2.8.5", 83 | "dotenv": "^16.0.3", 84 | "express": "^4.18.2", 85 | "express-formidable": "^1.2.0", 86 | "express-session": "^1.17.3", 87 | "fastify-static": "^4.7.0", 88 | "i18next": "^22.4.13", 89 | "knex": "^2.4.2", 90 | "koa": "^2.14.2", 91 | "koa2-formidable": "^1.0.3", 92 | "mongoose": "^7.0.2", 93 | "nodemon": "^2.0.21", 94 | "objection": "^3.0.1", 95 | "pg": "^8.10.0", 96 | "prisma": "^4.11.0", 97 | "reflect-metadata": "^0.1.13", 98 | "rxjs": "^7.8.0", 99 | "sequelize": "^6.29.3", 100 | "sequelize-cli": "^6.6.0", 101 | "tslib": "^2.5.0", 102 | "typeorm": "^0.3.12", 103 | "uuid": "^9.0.0" 104 | }, 105 | "devDependencies": { 106 | "@types/argon2": "^0.15.0", 107 | "@types/connect-pg-simple": "^7.0.0", 108 | "@types/cors": "^2.8.13", 109 | "@types/express-session": "^1.17.7", 110 | "@types/lodash": "^4.14.191", 111 | "@types/node": "^18.15.5", 112 | "@types/uuid": "^9.0.1", 113 | "@typescript-eslint/eslint-plugin": "^5.56.0", 114 | "@typescript-eslint/parser": "^5.56.0", 115 | "copyfiles": "^2.4.1", 116 | "dotenv-cli": "^7.1.0", 117 | "eslint": "^8.36.0", 118 | "eslint-config-prettier": "^8.8.0", 119 | "eslint-plugin-prettier": "^4.2.1", 120 | "eslint-plugin-react": "^7.32.2", 121 | "eslint-plugin-react-hooks": "^4.6.0", 122 | "prettier": "^2.8.6", 123 | "rimraf": "^4.4.1", 124 | "ts-node": "^10.9.1", 125 | "tsconfig-paths": "^4.1.2", 126 | "typescript": "^5.0.2" 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /prisma/migrations/20220713101214_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateTable 2 | CREATE TABLE `Post` ( 3 | `id` INTEGER NOT NULL AUTO_INCREMENT, 4 | `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), 5 | `updatedAt` DATETIME(3) NOT NULL, 6 | `title` VARCHAR(255) NOT NULL, 7 | `content` VARCHAR(191) NULL, 8 | `someJson` JSON NULL, 9 | `status` ENUM('ACTIVE', 'REMOVED') NOT NULL DEFAULT 'ACTIVE', 10 | `published` BOOLEAN NOT NULL DEFAULT false, 11 | `publisherId` INTEGER NOT NULL, 12 | 13 | PRIMARY KEY (`id`) 14 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 15 | 16 | -- CreateTable 17 | CREATE TABLE `Profile` ( 18 | `id` INTEGER NOT NULL AUTO_INCREMENT, 19 | `bio` VARCHAR(191) NULL, 20 | `publisherId` INTEGER NOT NULL, 21 | 22 | UNIQUE INDEX `Profile_publisherId_key`(`publisherId`), 23 | PRIMARY KEY (`id`) 24 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 25 | 26 | -- CreateTable 27 | CREATE TABLE `Publisher` ( 28 | `id` INTEGER NOT NULL AUTO_INCREMENT, 29 | `email` VARCHAR(191) NOT NULL, 30 | `name` VARCHAR(191) NULL, 31 | 32 | UNIQUE INDEX `Publisher_email_key`(`email`), 33 | PRIMARY KEY (`id`) 34 | ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 35 | 36 | -- AddForeignKey 37 | ALTER TABLE `Post` ADD CONSTRAINT `Post_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 38 | 39 | -- AddForeignKey 40 | ALTER TABLE `Profile` ADD CONSTRAINT `Profile_publisherId_fkey` FOREIGN KEY (`publisherId`) REFERENCES `Publisher`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; 41 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "mysql" -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | datasource db { 2 | provider = "mysql" 3 | url = env("MYSQL_DATABASE_URL") 4 | } 5 | 6 | generator client { 7 | provider = "prisma-client-js" 8 | } 9 | 10 | enum Status { 11 | ACTIVE 12 | REMOVED 13 | } 14 | 15 | model Post { 16 | id Int @id @default(autoincrement()) 17 | createdAt DateTime @default(now()) 18 | updatedAt DateTime @updatedAt 19 | title String @db.VarChar(255) 20 | content String? 21 | someJson Json? @db.Json 22 | status Status @default(ACTIVE) 23 | published Boolean @default(false) 24 | publisher Publisher @relation(fields: [publisherId], references: [id]) 25 | publisherId Int 26 | } 27 | 28 | model Profile { 29 | id Int @id @default(autoincrement()) 30 | bio String? 31 | publisher Publisher @relation(fields: [publisherId], references: [id]) 32 | publisherId Int @unique 33 | } 34 | 35 | model Publisher { 36 | id Int @id @default(autoincrement()) 37 | email String @unique 38 | name String? 39 | posts Post[] 40 | profile Profile? 41 | } 42 | -------------------------------------------------------------------------------- /public/custom.css: -------------------------------------------------------------------------------- 1 | html { 2 | scroll-behavior: smooth; 3 | } 4 | 5 | a { 6 | text-decoration: none; 7 | color: inherit; 8 | } 9 | 10 | a.adminjs_Header { 11 | display: block; 12 | } 13 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoftwareBrothers/adminjs-example-app/09a6efdb27d584a67fc82e08945ea570a014b7c1/public/favicon.ico -------------------------------------------------------------------------------- /public/gtm.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const headGtm = ` 3 | 4 | (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': 5 | new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], 6 | j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= 7 | 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); 8 | })(window,document,'script','dataLayer','GTM-K6R3HZ4'); 9 | 10 | `; 11 | const bodyGtm = ` 12 | 13 | 15 | 16 | `; 17 | const headScript = window.document.createElement('script'); 18 | headScript.type = 'text/javascript'; 19 | headScript.text = headGtm; 20 | const bodyScript = window.document.createElement('noscript'); 21 | bodyScript.text = bodyGtm; 22 | 23 | window.document.head.appendChild(headScript); 24 | window.document.body.appendChild(bodyScript); 25 | })(); 26 | -------------------------------------------------------------------------------- /public/locales/en/translation.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /public/locales/mk/translation.json: -------------------------------------------------------------------------------- 1 | { 2 | "actions": { 3 | "new": "Создадете ново", 4 | "edit": "Уредување", 5 | "show": "Прикажи", 6 | "delete": "Избриши", 7 | "bulkDelete": "Избриши ги сите", 8 | "list": "Список" 9 | }, 10 | "buttons": { 11 | "save": "Зачувај", 12 | "addNewItem": "Додадете нова ставка", 13 | "filter": "Филтер", 14 | "applyChanges": "Примени промени", 15 | "resetFilter": "Ресетирај", 16 | "confirmRemovalMany": "Потврдете го отстранувањето на {{count}} рекорд", 17 | "confirmRemovalMany_plural": "Потврдете го отстранувањето на {{count}} рекорди", 18 | "logout": "Одјави се", 19 | "login": "Логирај Се", 20 | "seeTheDocumentation": "Видете: <1>документацијата", 21 | "createFirstRecord": "Создадете прв запис", 22 | "cancel": "Откажи", 23 | "confirm": "Потврди" 24 | }, 25 | "components": { 26 | "DropZone": { 27 | "placeholder": "Спуштете ја вашата датотека овде или кликнете за да пребарувате", 28 | "acceptedSize": "Максимална големина: {{maxSize}}", 29 | "acceptedType": "Поддржува: {{mimeTypes}}", 30 | "unsupportedSize": "Датотека {{fileName}} е преголем", 31 | "unsupportedType": "Датотека {{fileName}} има неподдржан тип: {{fileType}}" 32 | } 33 | }, 34 | "labels": { 35 | "navigation": "Навигација", 36 | "pages": "Страници", 37 | "selectedRecords": "Избрано ({{selected}})", 38 | "filters": "Филтри", 39 | "adminVersion": "Администратор: {{version}}", 40 | "appVersion": "Апликација: {{version}}", 41 | "loginWelcome": "Добредојдовте", 42 | "dashboard": "Контролна табла" 43 | }, 44 | "properties": { 45 | "length": "Должина", 46 | "from": "Од", 47 | "to": "До" 48 | }, 49 | "resources": {}, 50 | "messages": { 51 | "successfullyBulkDeleted": "успешно отстранети {{count}} рекорд", 52 | "successfullyBulkDeleted_plural": "успешно отстранети {{count}} рекорди", 53 | "successfullyDeleted": "Дадениот запис е успешно избришан", 54 | "successfullyUpdated": "Дадениот запис е успешно ажуриран", 55 | "thereWereValidationErrors": "Има грешки при валидација - проверете ги подолу", 56 | "forbiddenError": "Не можете да извршите дејство {{actionName}} на {{resourceId}}", 57 | "anyForbiddenError": "Не можете да извршите дадено дејство", 58 | "successfullyCreated": "Успешно создаде нов рекорд", 59 | "bulkDeleteError": "Настана грешка при бришење записи. Проверете ја конзолата за да видите повеќе информации", 60 | "errorFetchingRecords": "Настана грешка при преземањето на записите. Проверете ја конзолата за да видите повеќе информации", 61 | "errorFetchingRecord": "Настана грешка при преземањето на записот. Проверете ја конзолата за да видите повеќе информации", 62 | "noRecordsSelected": "Не избравте ниедна запис", 63 | "theseRecordsWillBeRemoved": "Следниот запис ќе биде отстранет", 64 | "theseRecordsWillBeRemoved_plural": "Следните записи ќе бидат отстранети", 65 | "pickSomeFirstToRemove": "За да ги отстраните записите, прво треба да ги изберете", 66 | "error404Resource": "Ресурс на дадениот ид: {{resourceId}} не може да се најде", 67 | "error404Action": "Ресурс на дадениот ид: {{resourceId}} нема дејство со име: {{actionName}} или не сте овластени да го користите!", 68 | "error404Record": "Ресурс на дадениот ид: {{resourceId}} нема запис со ид: {{recordId}} или не сте овластени да го користите!", 69 | "seeConsoleForMore": "Погледнете ја развојната конзола за повеќе детали...", 70 | "noActionComponent": "Мора да имплементирате акциона компонента за вашата акција", 71 | "noRecordsInResource": "Нема записи во овој ресурс", 72 | "noRecords": "Нема записи", 73 | "confirmDelete": "Дали навистина сакате да ја отстраните оваа ставка?", 74 | "welcomeOnBoard_title": "Добредојдовте на одборот!", 75 | "welcomeOnBoard_subtitle": "Сега сте еден од нас! ", 76 | "loginWelcome": "до AdminJS – водечки светски административен панел генериран автоматски со отворен код за вашата апликација Node.js кој ви овозможува да управувате со сите ваши податоци на едно место", 77 | "addingResources_title": "Додавање ресурси", 78 | "addingResources_subtitle": "Како да додадете нови ресурси на страничната лента", 79 | "customizeResources_title": "Прилагодете ги ресурсите", 80 | "customizeResources_subtitle": "Дефинирање на однесување, додавање својства и повеќе...", 81 | "customizeActions_title": "Приспособете ги акциите", 82 | "customizeActions_subtitle": "Изменување на постоечки дејства и додавање нови", 83 | "writeOwnComponents_title": "Напиши компоненти", 84 | "writeOwnComponents_subtitle": "Како да го измените изгледот и чувството на AdminJS", 85 | "customDashboard_title": "Прилагодена контролна табла", 86 | "customDashboard_subtitle": "Како да го измените овој приказ и да додадете нови страници на страничната лента", 87 | "roleBasedAccess_title": "Контрола на пристап заснована на улоги", 88 | "roleBasedAccess_subtitle": "Креирајте кориснички улоги и дозволи во AdminJS", 89 | "community_title": "Придружете се на опуштената заедница", 90 | "community_subtitle": "Разговарајте со креаторите на AdminJS и другите корисници на AdminJS", 91 | "foundBug_title": "Најдовте бубачка? ", 92 | "foundBug_subtitle": "Поставете проблем на нашето складиште на GitHub", 93 | "needMoreSolutions_title": "Ви требаат понапредни решенија?", 94 | "needMoreSolutions_subtitle": "Ние сме тука да ви обезбедиме прекрасен дизајн на UX/UI и прилагоден софтвер базиран (не само) на AdminJS", 95 | "invalidCredentials": "Погрешна е-пошта и/или лозинка", 96 | "keyPlaceholder": "КЛУЧ", 97 | "valuePlaceholder": "ВРЕДНОСТ", 98 | "initialKey": "клуч-{{number}}", 99 | "keyInUse": "Копчињата за објекти мора да бидат единствени.", 100 | "keyValuePropertyDefaultDescription": "Сите вредности се зачувани како текст. " 101 | } 102 | } -------------------------------------------------------------------------------- /src/admin/admin.utils.ts: -------------------------------------------------------------------------------- 1 | import { ActionRequest } from 'adminjs'; 2 | 3 | export const isPOSTMethod = ({ method }: ActionRequest): boolean => method.toLowerCase() === 'post'; 4 | 5 | export const isGETMethod = ({ method }: ActionRequest): boolean => method.toLowerCase() === 'get'; 6 | 7 | export const isNewAction = ({ params: { action } }: ActionRequest): boolean => action === 'new'; 8 | 9 | export const isEditAction = ({ params: { action } }: ActionRequest): boolean => action === 'edit'; 10 | -------------------------------------------------------------------------------- /src/admin/components.bundler.ts: -------------------------------------------------------------------------------- 1 | import { ComponentLoader, OverridableComponent } from 'adminjs'; 2 | import path from 'path'; 3 | import * as url from 'url'; 4 | 5 | const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); 6 | export const componentLoader = new ComponentLoader(); 7 | 8 | export const add = (url: string, componentName: string): string => 9 | componentLoader.add(componentName, path.join(__dirname, url)); 10 | 11 | export const override = (url: string, componentName: OverridableComponent): string => 12 | componentLoader.override(componentName, path.join(__dirname, url)); 13 | 14 | /** 15 | * Overridable components 16 | */ 17 | override('components/top-bar', 'Version'); 18 | override('components/login', 'Login'); 19 | override('components/sidebar-resource-section', 'SidebarResourceSection'); 20 | 21 | /** 22 | * Common components 23 | */ 24 | export const PRODUCTS_LIST = add('components/products-list', 'ProductList'); 25 | export const DONT_TOUCH_THIS_ACTION = add('components/dont-touch-this-action', 'CustomAction'); 26 | export const DETAILED_STATS = add('components/detailed-stats', 'DetailedStats'); 27 | export const THUMB = add('components/thumb', 'Thumb'); 28 | 29 | /** 30 | * Pages 31 | */ 32 | export const CUSTOM_PAGE = add('pages/custom-page', 'CustomPage'); 33 | export const DESIGN_SYSTEM_PAGE = add('pages/design-system-examples/index', 'DesignSystemPage'); 34 | -------------------------------------------------------------------------------- /src/admin/components/debug.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Debug = (props) =>
{JSON.stringify(props, null, 4)}
; 4 | 5 | export default Debug; 6 | -------------------------------------------------------------------------------- /src/admin/components/design-system-examples/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ModalExample } from './modal-example.js'; 2 | -------------------------------------------------------------------------------- /src/admin/components/design-system-examples/modal-example.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Modal, ModalProps } from '@adminjs/design-system'; 2 | import React, { FC, useCallback, useState } from 'react'; 3 | 4 | const ModalExample: FC = () => { 5 | const [showModal, setShowModal] = useState(false); 6 | const handleButtonClick = useCallback(() => setShowModal(true), []); 7 | 8 | const modalProps: ModalProps = { 9 | variant: 'primary', 10 | label: 'Modal header', 11 | icon: 'Bookmark', 12 | title: 'Modal title', 13 | subTitle: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 14 | buttons: [{ label: 'Cancel' }, { label: 'Delete', color: 'danger' }], 15 | onClose: () => setShowModal(false), 16 | onOverlayClick: () => setShowModal(false), 17 | }; 18 | 19 | return ( 20 | 21 | 22 | {showModal && } 23 | 24 | ); 25 | }; 26 | 27 | export default ModalExample; 28 | -------------------------------------------------------------------------------- /src/admin/components/detailed-stats.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { H5, Text, DrawerContent } from '@adminjs/design-system'; 3 | import { ActionHeader, ActionHeaderProps } from 'adminjs'; 4 | 5 | const DetailedStats = (props: ActionHeaderProps) => { 6 | return ( 7 | 8 | 9 |
Custom action example
10 | Where you can do whatever you like... 11 |
12 | ); 13 | }; 14 | 15 | export default DetailedStats; 16 | -------------------------------------------------------------------------------- /src/admin/components/dont-touch-this-action.tsx: -------------------------------------------------------------------------------- 1 | import { Box, H3, Text } from '@adminjs/design-system'; 2 | import { BasePropertyProps } from 'adminjs'; 3 | import React from 'react'; 4 | 5 | const DontTouchThis = (props: BasePropertyProps) => { 6 | const { record } = props; 7 | 8 | return ( 9 | 10 | 11 |

Example of a simple page

12 | Where you can put almost everything like this: 13 | 14 | stupid cat 15 | 16 |
17 | 18 | Or (more likely), operate on a returned record: 19 | 20 |
{JSON.stringify(record, null, 2)}
21 |
22 |
23 |
24 | ); 25 | }; 26 | 27 | export default DontTouchThis; 28 | -------------------------------------------------------------------------------- /src/admin/components/login.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { useSelector } from 'react-redux'; 4 | import { 5 | Box, 6 | BoxProps, 7 | H5, 8 | H2, 9 | Label, 10 | Illustration, 11 | Input, 12 | FormGroup, 13 | Button, 14 | Text, 15 | MessageBox, 16 | MadeWithLove, 17 | themeGet, 18 | } from '@adminjs/design-system'; 19 | import { styled } from '@adminjs/design-system/styled-components'; 20 | import { ReduxState, useTranslation } from 'adminjs'; 21 | import { AuthUser, AuthUsers } from '../constants/authUsers.js'; 22 | 23 | const Wrapper = styled(Box)` 24 | align-items: center; 25 | justify-content: center; 26 | flex-direction: column; 27 | height: 100%; 28 | `; 29 | 30 | const StyledLogo = styled.img` 31 | max-width: 200px; 32 | margin: ${themeGet('space', 'md')} 0; 33 | `; 34 | 35 | const IllustrationsWrapper = styled(Box)` 36 | display: flex; 37 | flex-wrap: wrap; 38 | align-items: center; 39 | justify-content: center; 40 | & svg [stroke='#3B3552'] { 41 | stroke: rgba(255, 255, 255, 0.5); 42 | } 43 | & svg [fill='#3040D6'] { 44 | fill: rgba(255, 255, 255, 1); 45 | } 46 | `; 47 | 48 | export type LoginProps = { 49 | credentials: Pick; 50 | action: string; 51 | errorMessage?: string; 52 | children?: any; 53 | }; 54 | 55 | export const Login: React.FC = (props) => { 56 | const { action, errorMessage } = props; 57 | const { translateComponent, translateMessage } = useTranslation(); 58 | const [defaultUser] = AuthUsers; 59 | const branding = useSelector((state: ReduxState) => state.branding); 60 | const message = `Email: ${defaultUser.email}\nPassword: ${defaultUser.password}`; 61 | 62 | return ( 63 | 64 | 65 | 66 | 75 |

{translateComponent('Login.welcomeHeader')}

76 | 77 | {translateComponent('Login.welcomeMessage')} 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 |
91 | 92 |
93 | {branding.logo ? : branding.companyName} 94 |
95 | 96 | {errorMessage && ( 97 | 1 ? errorMessage : translateMessage(errorMessage)} 100 | variant="danger" 101 | /> 102 | )} 103 | 104 | 105 | 110 | 111 | 112 | 113 | 120 | 121 | 122 | 123 | 124 |
125 |
126 | {branding.withMadeWithLove ? ( 127 | 128 | 129 | 130 | ) : null} 131 |
132 |
133 | ); 134 | }; 135 | 136 | export default Login; 137 | -------------------------------------------------------------------------------- /src/admin/components/products-list.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Box, FormGroup, Label, Table, TableBody, TableCell, TableHead, TableRow } from '@adminjs/design-system'; 3 | import { flat, useTranslation, BasePropertyProps } from 'adminjs'; 4 | 5 | import { ProductListInterface } from '../../sources/sequelize/interfaces.js'; 6 | 7 | const ProductsList = (props: BasePropertyProps) => { 8 | const { translateLabel } = useTranslation(); 9 | const params = flat.unflatten(props.record.params); 10 | 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | {translateLabel('ID')} 19 | {translateLabel('Name')} 20 | {translateLabel('Quantity')} 21 | {translateLabel('Unit price')} 22 | {translateLabel('Sum')} 23 | 24 | 25 | 26 | {!params.products.length && ( 27 | 28 | 29 | No records 30 | 31 | 32 | )} 33 | {params.products.length > 0 && 34 | params.products.map(({ quantity, product }) => ( 35 | 36 | {product.id} 37 | {product.name} 38 | {quantity} 39 | {product.price / 100} 40 | {(product.price * quantity) / 100} 41 | 42 | ))} 43 | 44 |
45 |
46 |
47 | ); 48 | }; 49 | 50 | export default ProductsList; 51 | -------------------------------------------------------------------------------- /src/admin/components/sidebar-resource-section.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/jsx-one-expression-per-line */ 2 | import React, { FC } from 'react'; 3 | import { Navigation } from '@adminjs/design-system'; 4 | import { useTranslation, type SidebarResourceSectionProps, useNavigationResources } from 'adminjs'; 5 | 6 | const SidebarResourceSection: FC = ({ resources }) => { 7 | const elements = useNavigationResources(resources); 8 | const { translateLabel } = useTranslation(); 9 | 10 | const openUrl = (url: string) => () => { 11 | window.open(url, '_blank'); 12 | }; 13 | 14 | elements.unshift({ 15 | icon: 'Truck', 16 | label: translateLabel('kanbanBoard'), 17 | onClick: openUrl('https://github.com/orgs/SoftwareBrothers/projects/5'), 18 | }); 19 | 20 | elements.unshift({ 21 | icon: 'PieChart', 22 | label: translateLabel('stats'), 23 | onClick: openUrl('https://stats.adminjs.co'), 24 | }); 25 | 26 | return ; 27 | }; 28 | 29 | export default SidebarResourceSection; 30 | -------------------------------------------------------------------------------- /src/admin/components/thumb.tsx: -------------------------------------------------------------------------------- 1 | import { Icon } from '@adminjs/design-system'; 2 | import type { BasePropertyProps } from 'adminjs'; 3 | import React, { FC } from 'react'; 4 | 5 | const Thumb: FC = (props: BasePropertyProps) => { 6 | const { record, property } = props; 7 | const value = record.params[property.name]; 8 | 9 | return ; 10 | }; 11 | 12 | export default Thumb; 13 | -------------------------------------------------------------------------------- /src/admin/components/top-bar.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Icon, Text } from '@adminjs/design-system'; 2 | import { ReduxState } from 'adminjs'; 3 | import React, { FC } from 'react'; 4 | import { useSelector } from 'react-redux'; 5 | 6 | const TopBar: FC = () => { 7 | const versions = useSelector((state: ReduxState) => state.versions); 8 | const GITHUB_URL = (window as any).AdminJS.env.GITHUB_URL; 9 | const SLACK_URL = (window as any).AdminJS.env.SLACK_URL; 10 | const DOCUMENTATION_URL = (window as any).AdminJS.env.DOCUMENTATION_URL; 11 | 12 | return ( 13 | 14 | 15 | {versions.admin} 16 | 17 | 21 | 25 | 29 | 30 | ); 31 | }; 32 | 33 | export { TopBar }; 34 | export default TopBar; 35 | -------------------------------------------------------------------------------- /src/admin/constants/authUsers.ts: -------------------------------------------------------------------------------- 1 | export type AuthUser = { 2 | email: string; 3 | password: string; 4 | title: string; 5 | theme: string; 6 | }; 7 | 8 | export const AuthUsers: AuthUser[] = [ 9 | { 10 | email: 'admin@example.com', 11 | password: 'password', 12 | title: 'Admin', 13 | theme: 'light', 14 | }, 15 | { 16 | email: 'dark@example.com', 17 | password: 'password', 18 | title: 'AdminJS dark theme', 19 | theme: 'dark', 20 | }, 21 | { 22 | email: 'no-sidebar@example.com', 23 | password: 'password', 24 | title: 'AdminJS no-sidebar theme', 25 | theme: 'no-sidebar', 26 | }, 27 | { 28 | email: 'custom@example.com', 29 | password: 'password', 30 | title: 'AdminJS custom theme', 31 | theme: 'custom-theme', 32 | }, 33 | ]; 34 | -------------------------------------------------------------------------------- /src/admin/features/useEnvironmentVariableToDisableActions.ts: -------------------------------------------------------------------------------- 1 | import { buildFeature, FeatureType } from 'adminjs'; 2 | 3 | export const useEnvironmentVariableToDisableActions = (): FeatureType => { 4 | if (process.env.DISABLE_ADMINJS_ACTIONS === 'true') { 5 | return buildFeature({ 6 | actions: { 7 | edit: { isAccessible: false }, 8 | delete: { isAccessible: false }, 9 | bulkDelete: { isAccessible: false }, 10 | new: { isAccessible: false }, 11 | }, 12 | }); 13 | } 14 | return buildFeature({}); 15 | }; 16 | -------------------------------------------------------------------------------- /src/admin/index.ts: -------------------------------------------------------------------------------- 1 | // Adapters 2 | import { Database as MikroormDatabase, Resource as MikroormResource } from '@adminjs/mikroorm'; 3 | import { Database as MongooseDatabase, Resource as MongooseResource } from '@adminjs/mongoose'; 4 | import { Database as ObjectionDatabase, Resource as ObjectionResource } from '@adminjs/objection'; 5 | import { Database as PrismaDatabase, Resource as PrismaResource } from '@adminjs/prisma'; 6 | import { Database as SequelizeDatabase, Resource as SequelizeResource } from '@adminjs/sequelize'; 7 | import { dark, light, noSidebar } from '@adminjs/themes'; 8 | import { Database as TypeormDatabase, Resource as TypeormResource } from '@adminjs/typeorm'; 9 | 10 | import AdminJS, { AdminJSOptions, ResourceOptions } from 'adminjs'; 11 | import argon2 from 'argon2'; 12 | import { CreateCarResource, CreateOwnerResource, CreateSellerResource } from '../sources/mikroorm/resources/index.js'; 13 | import { AdminModel } from '../sources/mongoose/models/index.js'; 14 | import { 15 | CreateAdminResource, 16 | CreateArticleResource, 17 | CreateCategoryResource, 18 | CreateCommentResource, 19 | CreateComplicatedResource, 20 | CreateUserResource, 21 | } from '../sources/mongoose/resources/index.js'; 22 | import { CreateManagerResource, CreateOfficeResource } from '../sources/objectionjs/resources/index.js'; 23 | import { 24 | CreatePostResource, 25 | CreateProfileResource, 26 | CreatePublisherResource, 27 | } from '../sources/prisma/resources/index.js'; 28 | import { CryptoDatabase } from '../sources/rest/crypto-database.js'; 29 | import { 30 | CreateCartResource, 31 | CreateOrderResource, 32 | CreateProductResource, 33 | CreateCategoryResource as CreateSequelizeCategoryResource, 34 | } from '../sources/sequelize/resources/index.js'; 35 | import { CreateOrganizationResource, CreatePersonResource } from '../sources/typeorm/resources/index.js'; 36 | import './components.bundler.js'; 37 | import { componentLoader } from './components.bundler.js'; 38 | import { AuthUsers } from './constants/authUsers.js'; 39 | import { locale } from './locale/index.js'; 40 | import pages from './pages/index.js'; 41 | import { customTheme } from '../themes/index.js'; 42 | 43 | AdminJS.registerAdapter({ Database: MikroormDatabase, Resource: MikroormResource }); 44 | AdminJS.registerAdapter({ Database: MongooseDatabase, Resource: MongooseResource }); 45 | AdminJS.registerAdapter({ Database: ObjectionDatabase, Resource: ObjectionResource }); 46 | AdminJS.registerAdapter({ Database: PrismaDatabase, Resource: PrismaResource }); 47 | AdminJS.registerAdapter({ Database: SequelizeDatabase, Resource: SequelizeResource }); 48 | AdminJS.registerAdapter({ Database: TypeormDatabase, Resource: TypeormResource }); 49 | 50 | export const menu: Record = { 51 | mongoose: { name: 'Mongoose', icon: 'Folder' }, 52 | sequelize: { name: 'Sequelize', icon: 'Folder' }, 53 | typeorm: { name: 'Typeorm', icon: 'Folder' }, 54 | mikroorm: { name: 'Mikroorm', icon: 'Folder' }, 55 | prisma: { name: 'Prisma', icon: 'Folder' }, 56 | objection: { name: 'Objection', icon: 'Folder' }, 57 | rest: { name: 'REST', icon: 'Link' }, 58 | }; 59 | 60 | export const generateAdminJSConfig: () => AdminJSOptions = () => ({ 61 | version: { admin: true, app: '2.0.0' }, 62 | rootPath: '/admin', 63 | locale, 64 | assets: { 65 | styles: ['/custom.css'], 66 | scripts: process.env.NODE_ENV === 'production' ? ['/gtm.js'] : [], 67 | }, 68 | branding: { 69 | companyName: 'AdminJS demo page', 70 | favicon: '/favicon.ico', 71 | theme: { 72 | colors: { primary100: '#4D70EB' }, 73 | }, 74 | }, 75 | defaultTheme: 'light', 76 | availableThemes: [light, dark, noSidebar, customTheme], 77 | componentLoader, 78 | pages, 79 | env: { 80 | STORYBOOK_URL: process.env.STORYBOOK_URL, 81 | GITHUB_URL: process.env.GITHUB_URL, 82 | SLACK_URL: process.env.SLACK_URL, 83 | DOCUMENTATION_URL: process.env.DOCUMENTATION_URL, 84 | }, 85 | resources: [ 86 | // mongo 87 | CreateAdminResource(), 88 | CreateUserResource(), 89 | CreateCategoryResource(), 90 | CreateArticleResource(), 91 | CreateCommentResource(), 92 | CreateComplicatedResource(), 93 | // sequelize 94 | CreateSequelizeCategoryResource(), 95 | CreateProductResource(), 96 | CreateOrderResource(), 97 | CreateCartResource(), 98 | // typeorm 99 | CreateOrganizationResource(), 100 | CreatePersonResource(), 101 | // mikroorm 102 | CreateCarResource(), 103 | CreateSellerResource(), 104 | CreateOwnerResource(), 105 | // prisma 106 | CreatePublisherResource(), 107 | CreateProfileResource(), 108 | CreatePostResource(), 109 | // objectionjs 110 | CreateOfficeResource(), 111 | CreateManagerResource(), 112 | // custom 113 | new CryptoDatabase(), 114 | ], 115 | }); 116 | 117 | export const createAuthUsers = async () => 118 | Promise.all( 119 | AuthUsers.map(async ({ email, password }) => { 120 | const admin = await AdminModel.findOne({ email }); 121 | if (!admin) { 122 | await AdminModel.create({ email, password: await argon2.hash(password) }); 123 | } 124 | }), 125 | ); 126 | -------------------------------------------------------------------------------- /src/admin/locale/de/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": { 3 | "CustomPage": { 4 | "message": "Ich wurde angeklickt !!!", 5 | "messageWithInterpolation": "Übersetzte Nachrichten mit Interpolation: {{someParams}}" 6 | }, 7 | "I am fetched from the backend": "Ich werde aus dem Backend geholt" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/admin/locale/de/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "components": { 3 | "CustomPage": { 4 | "header": "Hier können Sie eine vollständig benutzerdefinierte Seite angeben", 5 | "introduction": "Mit einigen Daten, die vom Backend abgerufen wurden:", 6 | "ending": "und andere Interaktionen wie Toast :)", 7 | "button": "Klick mich" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/admin/locale/de/index.ts: -------------------------------------------------------------------------------- 1 | import type { LocaleTranslations } from 'adminjs'; 2 | 3 | import common from './common.json' assert { type: 'json' }; 4 | import components from './components.json' assert { type: 'json' }; 5 | import pages from './pages.json' assert { type: 'json' }; 6 | 7 | const deLocale: LocaleTranslations = { 8 | ...common, 9 | ...components, 10 | ...pages, 11 | }; 12 | 13 | export default deLocale; 14 | -------------------------------------------------------------------------------- /src/admin/locale/de/pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": { 3 | "customPage": "Benutzerdefinierte Seite" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/admin/locale/en/common.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages": { 3 | "loginWelcome": "to the demo application made with AdminJS - the best admin framework for Node.js apps, based on React.", 4 | "Has to be filled": "Has to be filled", 5 | "CustomPage": { 6 | "message": "I was clicked !!!", 7 | "messageWithInterpolation": "Translated messages with interpolation {{someParams}}" 8 | } 9 | }, 10 | "properties": { 11 | "id": "#", 12 | "Products": { 13 | "name": "Product's name", 14 | "price": "Price" 15 | } 16 | }, 17 | "labels.navigation": "", 18 | "labels.dashboard": "Home" 19 | } 20 | -------------------------------------------------------------------------------- /src/admin/locale/en/complicated.json: -------------------------------------------------------------------------------- 1 | { 2 | "properties": { 3 | "nestedDetails.age": "Person age", 4 | "nestedDetails.height": "Person height", 5 | "nestedDetails.nested.extremelyNested": "This nesting is crazy" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/admin/locale/en/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "components": { 3 | "CustomPage": { 4 | "header": "Here you can specify a totally custom page", 5 | "introduction": "With some data fetched from the backend:", 6 | "ending": "and other interactions like toast :)", 7 | "button": "Click me" 8 | }, 9 | "LanguageSelector": { 10 | "availableLanguages": { 11 | "mk": "Macedonian" 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/admin/locale/en/index.ts: -------------------------------------------------------------------------------- 1 | import type { LocaleTranslations } from 'adminjs'; 2 | 3 | import common from './common.json' assert { type: 'json' }; 4 | import Complicated from './complicated.json' assert { type: 'json' }; 5 | import components from './components.json' assert { type: 'json' }; 6 | import pages from './pages.json' assert { type: 'json' }; 7 | import Person from './person.json' assert { type: 'json' }; 8 | 9 | const enLocale: LocaleTranslations = { 10 | ...common, 11 | ...components, 12 | ...pages, 13 | resources: { 14 | Complicated, 15 | Person, 16 | products: { 17 | properties: { 18 | categoryId: 'Category', 19 | }, 20 | }, 21 | }, 22 | }; 23 | 24 | export default enLocale; 25 | -------------------------------------------------------------------------------- /src/admin/locale/en/pages.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": { 3 | "customPage": "Custom Page example" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/admin/locale/en/person.json: -------------------------------------------------------------------------------- 1 | { 2 | "labels": { 3 | "Person": "Staff" 4 | }, 5 | "actions": { 6 | "dontTouchThis": "Don't touch this!!!" 7 | }, 8 | "properties": { 9 | "organizationId": "Organization" 10 | }, 11 | "messages": { 12 | "youCanSetupGuards": "You can setup guards before an action - just in case." 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/admin/locale/index.ts: -------------------------------------------------------------------------------- 1 | import { Locale, locales as AdminJSLocales } from 'adminjs'; 2 | 3 | import de from './de/index.js'; 4 | import en from './en/index.js'; 5 | 6 | const localeKey = process.env.LOCALE || 'en'; 7 | const customLanguage = 'mk'; 8 | 9 | export const locale: Locale = { 10 | language: localeKey, 11 | availableLanguages: [...Object.keys(AdminJSLocales), customLanguage].sort(), 12 | localeDetection: true, 13 | withBackend: true, 14 | translations: { 15 | de, 16 | en, 17 | [customLanguage]: { 18 | components: { 19 | LanguageSelector: { 20 | availableLanguages: { 21 | de: 'германски', 22 | en: 'Англиски', 23 | es: 'шпански', 24 | it: 'италијански', 25 | pl: 'полски', 26 | mk: 'македонски', 27 | 'pt-BR': 'португалски (Бразил)', 28 | ua: 'украински', 29 | 'zh-CN': 'кинески', 30 | }, 31 | }, 32 | }, 33 | }, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /src/admin/pages/custom-page.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, H3, Link, Placeholder, Text } from '@adminjs/design-system'; 2 | import { ApiClient, useNotice, useTranslation } from 'adminjs'; 3 | import React, { FC, useEffect, useState } from 'react'; 4 | 5 | const api = new ApiClient(); 6 | 7 | type ApiGetPageResponse = { text: string }; 8 | 9 | const CustomPage: FC = () => { 10 | const [text, setText] = useState(); 11 | const addNotice = useNotice(); 12 | const { 13 | tc, 14 | tm, 15 | i18n: { language }, 16 | } = useTranslation(); 17 | 18 | useEffect(() => { 19 | api.getPage({ pageName: 'customPage' }).then((res) => { 20 | setText(tm(res.data.text, { defaultValue: res.data.text })); 21 | }); 22 | // eslint-disable-next-line react-hooks/exhaustive-deps 23 | }, [language]); 24 | 25 | const sendSimpleNotice = () => 26 | addNotice({ 27 | message: 'CustomPage.message', 28 | type: 'success', 29 | }); 30 | 31 | const sendTranslatedNotice = () => 32 | addNotice({ 33 | message: 'CustomPage.messageWithInterpolation', 34 | options: { 35 | someParams: ['param 1', 'param2'].join(', '), 36 | }, 37 | body: ( 38 | <> 39 | {tm('CustomPage.message')} {tc('CustomPage.button')} 40 | 41 | ), 42 | } as any); 43 | 44 | return ( 45 | 46 | 47 |

{tc('CustomPage.header')}

48 | 49 | {tc('CustomPage.introduction')} 50 | {text ? JSON.stringify(text, null, 2) : } 51 | {tc('CustomPage.ending')} 52 | 53 | 54 | 57 | 60 | 61 |
62 |
63 | ); 64 | }; 65 | 66 | export default CustomPage; 67 | -------------------------------------------------------------------------------- /src/admin/pages/design-system-examples/blog-page.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | Button, 4 | Drawer, 5 | DrawerContent, 6 | DropZone, 7 | Header, 8 | Icon, 9 | Input, 10 | Label, 11 | RichTextEditor, 12 | } from '@adminjs/design-system'; 13 | import React, { useState } from 'react'; 14 | 15 | const BlogPage: React.FC = () => { 16 | const [isDrawerVisible, setIsDrawerVisible] = useState(false); 17 | 18 | const handler = (html) => { 19 | console.log(html); 20 | }; 21 | 22 | return ( 23 | 24 |
25 | Blog 26 |
27 | 28 | {isDrawerVisible && ( 29 | 30 | 31 | 32 | 35 | Article settings 36 | 37 | 38 | 39 | 40 | 41 | )} 42 | 43 | 44 | 47 | 51 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 | ); 65 | }; 66 | 67 | export default BlogPage; 68 | -------------------------------------------------------------------------------- /src/admin/pages/design-system-examples/buttons-page.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Header, Icon, Text } from '@adminjs/design-system'; 2 | import React, { FC, PropsWithChildren } from 'react'; 3 | 4 | const Wrapper: FC = ({ children, title }) => ( 5 | 6 | {title} 7 | 8 | {children} 9 | 10 | 11 | ); 12 | 13 | const colors = ['primary', 'secondary', 'success', 'info', 'danger', 'text'] as const; 14 | const [primary] = colors; 15 | 16 | const ButtonsPage = () => ( 17 | 18 |
19 | Buttons 20 |
21 | 22 | 23 | {colors.map((color) => ( 24 | 27 | ))} 28 | 29 | 30 | {colors.map((color) => ( 31 | 34 | ))} 35 | 36 | 37 | {colors.map((color) => ( 38 | 41 | ))} 42 | 43 | 44 | {colors.map((color) => ( 45 | 48 | ))} 49 | 50 | 51 | {colors.map((color) => ( 52 | 55 | ))} 56 | 57 | 58 | {colors.map((color) => ( 59 | 62 | ))} 63 | 64 | 65 | {colors.map((color) => ( 66 | 69 | ))} 70 | 71 | 72 | {colors.map((color) => ( 73 | 76 | ))} 77 | 78 | 79 | 82 | 85 | 88 | 89 | 90 | {colors.map((color) => ( 91 | 95 | ))} 96 | 97 | 98 | {colors.map((color) => ( 99 | 102 | ))} 103 | 104 | 105 | {colors.map((color) => ( 106 | 109 | ))} 110 | 111 | 112 |
113 | ); 114 | 115 | export default ButtonsPage; 116 | -------------------------------------------------------------------------------- /src/admin/pages/design-system-examples/form-page.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Box, 3 | CheckBox, 4 | CurrencyInput, 5 | DatePicker, 6 | DropZone, 7 | DropZoneProps, 8 | Header, 9 | Input, 10 | Label, 11 | PhoneInput, 12 | Select, 13 | TextArea, 14 | } from '@adminjs/design-system'; 15 | import { useTranslation } from 'adminjs'; 16 | import React, { useState } from 'react'; 17 | 18 | const FormPage = () => { 19 | const [value, setValue] = useState(); 20 | const [date, setDate] = useState('2021-06-17'); 21 | const options = [ 22 | { value: '1', label: 'Office 1' }, 23 | { value: '2', label: 'Office 2' }, 24 | ]; 25 | const { translateComponent } = useTranslation(); 26 | 27 | return ( 28 | 29 |
30 | Form 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 |