├── racksdb
├── web
│ └── __init__.py
├── dtypes
│ ├── __init__.py
│ ├── rack_height.py
│ ├── netif_type.py
│ ├── storage_type.py
│ ├── angle.py
│ ├── dimension.py
│ ├── watts.py
│ ├── bits.py
│ ├── bytes.py
│ └── rack_width.py
├── generic
│ ├── __init__.py
│ ├── errors.py
│ ├── definedtype.py
│ └── dumpers
│ │ ├── console.py
│ │ ├── __init__.py
│ │ ├── _common.py
│ │ └── json.py
├── tests
│ ├── __init__.py
│ ├── drawers
│ │ ├── __init__.py
│ │ ├── test_parameters.py
│ │ └── test_axonometric_infrastructure.py
│ ├── generic
│ │ ├── __init__.py
│ │ ├── lib
│ │ │ ├── __init__.py
│ │ │ ├── bases.py
│ │ │ └── views.py
│ │ ├── dumpers
│ │ │ ├── __init__.py
│ │ │ ├── test_factory.py
│ │ │ ├── test_dumper_console.py
│ │ │ ├── test_dumper_json.py
│ │ │ └── test_dumper_yaml.py
│ │ ├── test_openapi.py
│ │ ├── test_schema_defined_type_loader.py
│ │ ├── test_db_list.py
│ │ ├── test_views.py
│ │ ├── test_schema_file_loader.py
│ │ └── test_db_dict.py
│ ├── lib
│ │ ├── __init__.py
│ │ ├── web.py
│ │ ├── common.py
│ │ └── reference.py
│ ├── web
│ │ └── __init__.py
│ ├── test_defined_type_hexcolor.py
│ └── test_defined_type_watts.py
├── drawers
│ ├── dtypes
│ │ ├── __init__.py
│ │ └── hexcolor.py
│ ├── coordinates
│ │ ├── base.py
│ │ ├── json.py
│ │ ├── yaml.py
│ │ └── __init__.py
│ ├── __init__.py
│ └── parameters.py
├── version.py
└── errors.py
├── docs
├── modules
│ ├── misc
│ │ ├── nav.adoc
│ │ └── pages
│ │ │ └── releases.adoc
│ ├── install
│ │ ├── nav.adoc
│ │ └── partials
│ │ │ └── pip-deps.adoc
│ ├── usage
│ │ ├── images
│ │ │ ├── raw
│ │ │ └── shadowed
│ │ ├── examples
│ │ │ ├── racksdb.yml
│ │ │ ├── custom.yml
│ │ │ └── custom.json
│ │ ├── nav.adoc
│ │ ├── partials
│ │ │ └── drawing-deftypes.adoc
│ │ └── pages
│ │ │ ├── rest.adoc
│ │ │ ├── racksdb-web.adoc
│ │ │ ├── ui.adoc
│ │ │ ├── integrations.adoc
│ │ │ └── drawparams.adoc
│ ├── overview
│ │ ├── nav.adoc
│ │ ├── images
│ │ │ ├── racksdb_logo.png
│ │ │ ├── racksdb_web_ui_screenshots.webp
│ │ │ ├── racksdb_diagrams.png
│ │ │ ├── racksdb_overview.png
│ │ │ └── racksdb_interfaces.png
│ │ └── pages
│ │ │ ├── start.adoc
│ │ │ └── overview.adoc
│ └── db
│ │ ├── examples
│ │ ├── extensions.yml
│ │ ├── infrastructure.yml
│ │ ├── datacenter.yml
│ │ └── types.yml
│ │ ├── nav.adoc
│ │ ├── pages
│ │ ├── structure.adoc
│ │ ├── def.adoc
│ │ ├── schema.adoc
│ │ └── files.adoc
│ │ └── partials
│ │ ├── symbols.adoc
│ │ ├── deftypes.adoc
│ │ └── prop-types.adoc
├── utils
│ ├── build.yaml
│ ├── gen-openapi.py
│ ├── schema-objs.py
│ ├── drawings-schema-objs.py
│ └── schema-objs.adoc.j2
├── generate-changelog.sh
├── antora.yml
├── man
│ ├── racksdb.adoc
│ └── racksdb-web.adoc
├── README.md
└── update-materials
├── examples
├── extensions
│ ├── db
│ │ ├── datacenters
│ │ ├── types
│ │ │ ├── nodes
│ │ │ ├── network
│ │ │ ├── storage
│ │ │ └── racks.yml
│ │ ├── infrastructures
│ │ └── power.yml
│ └── extensions.yml
├── db
│ ├── types
│ │ ├── misc
│ │ │ └── kvm.yml
│ │ ├── network
│ │ │ └── cisco3650.yml
│ │ ├── storage
│ │ │ └── qnaph1277.yml
│ │ ├── racks.yml
│ │ └── nodes
│ │ │ ├── sm220bt.yml
│ │ │ ├── sm610u.yml
│ │ │ ├── hpesyn480.yml
│ │ │ ├── bullsx440a5.yml
│ │ │ └── dellr550.yml
│ ├── infrastructures
│ │ ├── sharednet.yml
│ │ ├── mercury.yml
│ │ └── jupiter.yml
│ └── datacenters
│ │ ├── paris.yml
│ │ └── london.yml
└── simple
│ └── racksdb.yml
├── frontend
├── env.d.ts
├── public
│ ├── favicon.ico
│ ├── assets
│ │ ├── racksdb_logo.png
│ │ └── racksdb-marker.svg
│ └── config.json
├── src
│ ├── index.css
│ ├── App.vue
│ ├── main.ts
│ ├── views
│ │ ├── HomeView.vue
│ │ ├── InfrastructuresView.vue
│ │ ├── InfrastructureDetailsView.vue
│ │ └── DatacentersView.vue
│ ├── plugins
│ │ ├── http.ts
│ │ └── runtimeConfiguration.ts
│ ├── components
│ │ ├── DatacenterListInfrastructures.vue
│ │ ├── HeaderPage.vue
│ │ ├── BreadCrumbs.vue
│ │ └── HomeViewCards.vue
│ └── router
│ │ └── index.ts
├── postcss.config.js
├── .prettierrc.json
├── tsconfig.vitest.json
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.app.json
├── index.html
├── .eslintrc.cjs
├── tsconfig.node.json
├── vitest.config.ts
├── vite.config.ts
├── tests
│ ├── views
│ │ ├── HomeView.spec.ts
│ │ ├── InfrastructuresView.spec.ts
│ │ ├── InfrastructureDetailsView.spec.ts
│ │ ├── DatacenterDetailsView.spec.ts
│ │ ├── DatacenterRoomView.spec.ts
│ │ └── DatacentersView.spec.ts
│ ├── components
│ │ ├── HeaderPage.spec.ts
│ │ ├── DatacenterListInfrastructures.spec.ts
│ │ ├── ComboBox.spec.ts
│ │ ├── BreadCrumbs.spec.ts
│ │ ├── FiltersBar.spec.ts
│ │ ├── HomeViewCards.spec.ts
│ │ └── InfrastructureFilters.spec.ts
│ └── setup.ts
├── README.md
└── package.json
├── assets
├── bitmaps
│ ├── favicon.ico
│ ├── logo_dark_tiny.png
│ ├── logo_dark_large.png
│ ├── logo_dark_medium.png
│ ├── logo_dark_small.png
│ ├── logo_white_large.png
│ ├── logo_white_small.png
│ ├── logo_white_tiny.png
│ ├── logo_colored_large.png
│ ├── logo_colored_medium.png
│ ├── logo_colored_small.png
│ ├── logo_colored_tiny.png
│ ├── logo_full_dark_tiny.png
│ ├── logo_white_medium.png
│ ├── logo_full_dark_large.png
│ ├── logo_full_dark_medium.png
│ ├── logo_full_dark_small.png
│ ├── logo_full_white_large.png
│ ├── logo_full_white_small.png
│ ├── logo_full_white_tiny.png
│ ├── logo_full_colored_large.png
│ ├── logo_full_colored_medium.png
│ ├── logo_full_colored_small.png
│ ├── logo_full_colored_tiny.png
│ ├── logo_full_white_medium.png
│ ├── logo_full_horizontal_dark_large.png
│ ├── logo_full_horizontal_dark_small.png
│ ├── logo_full_horizontal_dark_tiny.png
│ ├── logo_full_horizontal_white_tiny.png
│ ├── logo_full_horizontal_colored_large.png
│ ├── logo_full_horizontal_colored_small.png
│ ├── logo_full_horizontal_colored_tiny.png
│ ├── logo_full_horizontal_dark_medium.png
│ ├── logo_full_horizontal_white_large.png
│ ├── logo_full_horizontal_white_medium.png
│ ├── logo_full_horizontal_white_small.png
│ └── logo_full_horizontal_colored_medium.png
├── screenshots
│ ├── raw
│ │ ├── home.png
│ │ ├── racks.png
│ │ ├── rooms.png
│ │ ├── datacenters.png
│ │ ├── infrastructure.png
│ │ ├── infrastructures.png
│ │ └── infrastructure_filters.png
│ ├── shadowed
│ │ ├── home.webp
│ │ ├── racks.webp
│ │ ├── rooms.webp
│ │ ├── datacenters.webp
│ │ ├── infrastructure.webp
│ │ ├── infrastructures.webp
│ │ └── infrastructure_filters.webp
│ ├── assemblies
│ │ └── bitmaps
│ │ │ ├── screenshots-small.png
│ │ │ └── screenshots-small.webp
│ └── build.yaml
└── README.md
├── .gitignore
├── .github
└── workflows
│ └── pre-commit.yml
├── .pre-commit-config.yaml
├── LICENSE
└── pyproject.toml
/racksdb/web/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/racksdb/dtypes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/racksdb/generic/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/racksdb/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/racksdb/tests/drawers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/racksdb/tests/lib/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/racksdb/tests/web/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/racksdb/drawers/dtypes/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/lib/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/dumpers/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/modules/misc/nav.adoc:
--------------------------------------------------------------------------------
1 | * xref:releases.adoc[]
2 |
--------------------------------------------------------------------------------
/docs/modules/install/nav.adoc:
--------------------------------------------------------------------------------
1 | * xref:quickstart.adoc[]
2 |
--------------------------------------------------------------------------------
/examples/extensions/db/datacenters:
--------------------------------------------------------------------------------
1 | ../../db/datacenters
--------------------------------------------------------------------------------
/examples/extensions/db/types/nodes:
--------------------------------------------------------------------------------
1 | ../../../db/types/nodes
--------------------------------------------------------------------------------
/docs/modules/usage/images/raw:
--------------------------------------------------------------------------------
1 | ../../../../assets/screenshots/raw
--------------------------------------------------------------------------------
/examples/extensions/db/infrastructures:
--------------------------------------------------------------------------------
1 | ../../db/infrastructures/
--------------------------------------------------------------------------------
/examples/extensions/db/types/network:
--------------------------------------------------------------------------------
1 | ../../../db/types/network
--------------------------------------------------------------------------------
/examples/extensions/db/types/storage:
--------------------------------------------------------------------------------
1 | ../../../db/types/storage
--------------------------------------------------------------------------------
/frontend/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/frontend/public/favicon.ico:
--------------------------------------------------------------------------------
1 | ../../assets/bitmaps/favicon.ico
--------------------------------------------------------------------------------
/docs/modules/usage/images/shadowed:
--------------------------------------------------------------------------------
1 | ../../../../assets/screenshots/shadowed
--------------------------------------------------------------------------------
/examples/db/types/misc/kvm.yml:
--------------------------------------------------------------------------------
1 | model: 19“ KVM Console
2 | height: 1u
3 |
--------------------------------------------------------------------------------
/docs/modules/usage/examples/racksdb.yml:
--------------------------------------------------------------------------------
1 | ../../../../examples/simple/racksdb.yml
--------------------------------------------------------------------------------
/examples/extensions/db/power.yml:
--------------------------------------------------------------------------------
1 | - capacity: 250kW
2 | - capacity: 1.6MW
3 |
--------------------------------------------------------------------------------
/docs/modules/overview/nav.adoc:
--------------------------------------------------------------------------------
1 | * xref:start.adoc[]
2 | * xref:overview.adoc[]
3 |
--------------------------------------------------------------------------------
/frontend/public/assets/racksdb_logo.png:
--------------------------------------------------------------------------------
1 | ../../../assets/bitmaps/logo_full_white_medium.png
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/docs/modules/overview/images/racksdb_logo.png:
--------------------------------------------------------------------------------
1 | ../../../../assets/bitmaps/logo_full_white_medium.png
--------------------------------------------------------------------------------
/assets/bitmaps/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/favicon.ico
--------------------------------------------------------------------------------
/assets/bitmaps/logo_dark_tiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_dark_tiny.png
--------------------------------------------------------------------------------
/assets/screenshots/raw/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/raw/home.png
--------------------------------------------------------------------------------
/assets/screenshots/raw/racks.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/raw/racks.png
--------------------------------------------------------------------------------
/assets/screenshots/raw/rooms.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/raw/rooms.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_dark_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_dark_large.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_dark_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_dark_medium.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_dark_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_dark_small.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_white_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_white_large.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_white_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_white_small.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_white_tiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_white_tiny.png
--------------------------------------------------------------------------------
/frontend/public/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "API_SERVER": "http://localhost:5000/",
3 | "API_VERSION": "v0.6.0"
4 | }
5 |
--------------------------------------------------------------------------------
/assets/bitmaps/logo_colored_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_colored_large.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_colored_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_colored_medium.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_colored_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_colored_small.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_colored_tiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_colored_tiny.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_dark_tiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_dark_tiny.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_white_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_white_medium.png
--------------------------------------------------------------------------------
/assets/screenshots/raw/datacenters.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/raw/datacenters.png
--------------------------------------------------------------------------------
/assets/screenshots/shadowed/home.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/shadowed/home.webp
--------------------------------------------------------------------------------
/assets/screenshots/shadowed/racks.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/shadowed/racks.webp
--------------------------------------------------------------------------------
/assets/screenshots/shadowed/rooms.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/shadowed/rooms.webp
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_dark_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_dark_large.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_dark_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_dark_medium.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_dark_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_dark_small.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_white_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_white_large.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_white_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_white_small.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_white_tiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_white_tiny.png
--------------------------------------------------------------------------------
/docs/modules/overview/images/racksdb_web_ui_screenshots.webp:
--------------------------------------------------------------------------------
1 | ../../../../assets/screenshots/assemblies/bitmaps/screenshots-small.webp
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_colored_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_colored_large.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_colored_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_colored_medium.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_colored_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_colored_small.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_colored_tiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_colored_tiny.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_white_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_white_medium.png
--------------------------------------------------------------------------------
/assets/screenshots/raw/infrastructure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/raw/infrastructure.png
--------------------------------------------------------------------------------
/assets/screenshots/raw/infrastructures.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/raw/infrastructures.png
--------------------------------------------------------------------------------
/assets/screenshots/shadowed/datacenters.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/shadowed/datacenters.webp
--------------------------------------------------------------------------------
/assets/screenshots/shadowed/infrastructure.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/shadowed/infrastructure.webp
--------------------------------------------------------------------------------
/assets/screenshots/shadowed/infrastructures.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/shadowed/infrastructures.webp
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_dark_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_dark_large.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_dark_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_dark_small.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_dark_tiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_dark_tiny.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_white_tiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_white_tiny.png
--------------------------------------------------------------------------------
/assets/screenshots/raw/infrastructure_filters.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/raw/infrastructure_filters.png
--------------------------------------------------------------------------------
/docs/modules/overview/images/racksdb_diagrams.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/docs/modules/overview/images/racksdb_diagrams.png
--------------------------------------------------------------------------------
/docs/modules/overview/images/racksdb_overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/docs/modules/overview/images/racksdb_overview.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_colored_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_colored_large.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_colored_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_colored_small.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_colored_tiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_colored_tiny.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_dark_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_dark_medium.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_white_large.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_white_large.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_white_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_white_medium.png
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_white_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_white_small.png
--------------------------------------------------------------------------------
/docs/modules/overview/images/racksdb_interfaces.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/docs/modules/overview/images/racksdb_interfaces.png
--------------------------------------------------------------------------------
/frontend/postcss.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | module.exports = {
3 | plugins: {
4 | tailwindcss: {},
5 | autoprefixer: {},
6 | },
7 | }
8 |
--------------------------------------------------------------------------------
/assets/bitmaps/logo_full_horizontal_colored_medium.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/bitmaps/logo_full_horizontal_colored_medium.png
--------------------------------------------------------------------------------
/assets/screenshots/shadowed/infrastructure_filters.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/shadowed/infrastructure_filters.webp
--------------------------------------------------------------------------------
/examples/db/types/network/cisco3650.yml:
--------------------------------------------------------------------------------
1 | model: Cisco Catalyst 3650 switch
2 | height: 1u
3 | netifs:
4 | - type: ethernet
5 | bandwidth: 1Gb
6 | number: 48
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__/
2 | *.egg-info/
3 | .venv/
4 | build/
5 | .vite/
6 | dist/
7 | /docs/man/*.1
8 | /generated-doc
9 | node_modules/
10 | .ninja_log
11 |
--------------------------------------------------------------------------------
/assets/screenshots/assemblies/bitmaps/screenshots-small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/assemblies/bitmaps/screenshots-small.png
--------------------------------------------------------------------------------
/assets/screenshots/assemblies/bitmaps/screenshots-small.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rackslab/RacksDB/HEAD/assets/screenshots/assemblies/bitmaps/screenshots-small.webp
--------------------------------------------------------------------------------
/examples/db/types/storage/qnaph1277.yml:
--------------------------------------------------------------------------------
1 | model: QNAP TS-H1277XU-RP
2 | height: 2u
3 | disks:
4 | - type: disk
5 | size: 4TB
6 | model: Seagate IronWolf
7 | number: 12
8 |
--------------------------------------------------------------------------------
/frontend/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/prettierrc",
3 | "semi": false,
4 | "tabWidth": 2,
5 | "singleQuote": true,
6 | "printWidth": 100,
7 | "trailingComma": "none"
8 | }
9 |
--------------------------------------------------------------------------------
/examples/db/types/racks.yml:
--------------------------------------------------------------------------------
1 | - id: standard
2 | height: 1867mm
3 | width: 600mm
4 | depth: 914mm
5 | slots: 42u
6 | - id: half
7 | height: 1198mm
8 | width: 600mm
9 | depth: 914mm
10 | slots: 24u
11 |
--------------------------------------------------------------------------------
/frontend/tsconfig.vitest.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.app.json",
3 | "exclude": [],
4 | "compilerOptions": {
5 | "composite": true,
6 | "lib": [],
7 | "types": ["node", "jsdom"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/docs/modules/usage/nav.adoc:
--------------------------------------------------------------------------------
1 | * CLI manpages
2 | ** xref:racksdb.adoc[`racksdb`]
3 | ** xref:racksdb-web.adoc[`racksdb-web`]
4 | * xref:drawparams.adoc[]
5 | * xref:lib.adoc[]
6 | * xref:rest.adoc[]
7 | * xref:ui.adoc[]
8 | * xref:integrations.adoc[]
9 |
--------------------------------------------------------------------------------
/docs/utils/build.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Define DPI/sizes for which PNG bitmaps versions of SVG diagrams must be
3 | # generated.
4 | diagrams:
5 | modules/overview/images/racksdb_overview.svg: medium
6 | modules/overview/images/racksdb_interfaces.svg: medium
7 |
--------------------------------------------------------------------------------
/frontend/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | /* eslint-env node */
3 | module.exports = {
4 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
5 | plugins: [
6 | require('@tailwindcss/forms'),
7 | ],
8 | }
9 |
--------------------------------------------------------------------------------
/examples/extensions/db/types/racks.yml:
--------------------------------------------------------------------------------
1 | - id: standard
2 | height: 1867mm
3 | width: 600mm
4 | depth: 914mm
5 | slots: 42u
6 | weight: 128
7 | - id: half
8 | height: 1198mm
9 | width: 600mm
10 | depth: 914mm
11 | slots: 24u
12 | weight: 64
13 |
--------------------------------------------------------------------------------
/racksdb/drawers/coordinates/base.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2024 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 |
8 | class CoordinatesDumper:
9 | def dump(self, coordinates):
10 | raise NotImplementedError
11 |
--------------------------------------------------------------------------------
/frontend/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "files": [],
3 | "references": [
4 | {
5 | "path": "./tsconfig.node.json"
6 | },
7 | {
8 | "path": "./tsconfig.app.json"
9 | },
10 | {
11 | "path": "./tsconfig.vitest.json"
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/docs/modules/usage/examples/custom.yml:
--------------------------------------------------------------------------------
1 | margin:
2 | top: 200
3 | colors:
4 | equipments:
5 | - type: sm610u
6 | background: "#348feb" # blue
7 | - tags: ["hpc", "servers"]
8 | border: "#a66321" # orange
9 | racks:
10 | - type: half
11 | pane: "#8d42f5" # purple
12 | frame: "#81a85e" # green
13 |
--------------------------------------------------------------------------------
/examples/extensions/extensions.yml:
--------------------------------------------------------------------------------
1 | _content:
2 | properties:
3 | power:
4 | type: list[:Ups]
5 | optional: true
6 |
7 | _objects:
8 | Ups:
9 | properties:
10 | capacity:
11 | type: ~watts
12 | RackType:
13 | properties:
14 | weight:
15 | type: int
16 | optional: true
17 |
--------------------------------------------------------------------------------
/frontend/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@vue/tsconfig/tsconfig.dom.json",
3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
4 | "exclude": ["src/**/__tests__/*"],
5 | "compilerOptions": {
6 | "composite": true,
7 | "baseUrl": ".",
8 | "paths": {
9 | "@/*": ["./src/*"]
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/docs/modules/db/examples/extensions.yml:
--------------------------------------------------------------------------------
1 | _content:
2 | properties:
3 | power:
4 | type: list[:Ups]
5 | optional: true
6 |
7 | _objects:
8 | Ups:
9 | properties:
10 | capacity:
11 | type: ~watts
12 | RackType:
13 | properties:
14 | weight:
15 | type: int
16 | optional: true
17 |
--------------------------------------------------------------------------------
/docs/modules/db/nav.adoc:
--------------------------------------------------------------------------------
1 | * xref:def.adoc[]
2 | ** xref:concepts.adoc[]
3 | ** xref:files.adoc[]
4 | ** xref:schema.adoc[]
5 | ** xref:structure.adoc[Structure Reference]
6 | ** xref:ext.adoc[]
7 | ** Positioning How-Tos
8 | *** xref:positioning-racks.adoc[Racks Positioning]
9 | *** xref:positioning-equipments.adoc[Equipments Positioning]
10 |
--------------------------------------------------------------------------------
/docs/generate-changelog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | mkdir -p generated-doc/modules/misc/partials
4 | # Replace issue with hyperlinks to github issues
5 | sed 's/\(#\([0-9]\+\)\)/[#\2](https:\/\/github.com\/rackslab\/RacksDB\/issues\/\2\)/g' < CHANGELOG.md | pandoc --from markdown --to asciidoctor --output generated-doc/modules/misc/partials/CHANGELOG.adoc
6 |
--------------------------------------------------------------------------------
/frontend/src/App.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.github/workflows/pre-commit.yml:
--------------------------------------------------------------------------------
1 | name: pre-commit
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches:
7 | - main
8 | workflow_dispatch: {}
9 |
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | pre-commit:
15 | runs-on: ubuntu-latest
16 | steps:
17 | - uses: actions/checkout@v3
18 | - uses: actions/setup-python@v3
19 | - uses: pre-commit/action@v3.0.1
20 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | RacksDB
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/racksdb/drawers/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from .room import RoomDrawer
8 | from .infrastructure import InfrastructureDrawer
9 | from .axonometric_infrastructure import AxonometricInfrastructureDrawer
10 |
11 | __all__ = ["RoomDrawer", "InfrastructureDrawer", "AxonometricInfrastructureDrawer"]
12 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v5.0.0
4 | hooks:
5 | - id: check-yaml
6 | - id: end-of-file-fixer
7 | - id: trailing-whitespace
8 | - repo: https://github.com/astral-sh/ruff-pre-commit
9 | rev: v0.11.0
10 | hooks:
11 | - id: ruff
12 | types: [python]
13 | args: [ --fix ]
14 | # Run the formatter.
15 | - id: ruff-format
16 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | /* eslint-env node */
2 | require('@rushstack/eslint-patch/modern-module-resolution')
3 |
4 | module.exports = {
5 | root: true,
6 | 'extends': [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended',
9 | '@vue/eslint-config-typescript',
10 | '@vue/eslint-config-prettier/skip-formatting'
11 | ],
12 | parserOptions: {
13 | ecmaVersion: 'latest'
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/node18/tsconfig.json",
3 | "include": [
4 | "vite.config.*",
5 | "vitest.config.*",
6 | "cypress.config.*",
7 | "nightwatch.conf.*",
8 | "playwright.config.*"
9 | ],
10 | "compilerOptions": {
11 | "composite": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Bundler",
14 | "types": ["node"]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/racksdb/version.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | try:
8 | from importlib import metadata
9 | except ImportError:
10 | # On Python < 3.8, use external backport library importlib-metadata.
11 | import importlib_metadata as metadata
12 |
13 |
14 | def get_version():
15 | return metadata.version("racksdb")
16 |
--------------------------------------------------------------------------------
/assets/screenshots/build.yaml:
--------------------------------------------------------------------------------
1 | ---
2 | # Define DPI/sizes for which PNG bitmaps version of assemblies must be
3 | # generated.
4 | assemblies:
5 | screenshots.svg:
6 | - small
7 | # List raw screenshots for which versions with dropped shadow must be generated.
8 | shadowed:
9 | - home.png
10 | - datacenters.png
11 | - rooms.png
12 | - racks.png
13 | - infrastructures.png
14 | - infrastructure.png
15 | - infrastructure_filters.png
16 |
--------------------------------------------------------------------------------
/docs/modules/misc/pages/releases.adoc:
--------------------------------------------------------------------------------
1 | = Release notes
2 |
3 | This page contains the releases notes of RacksDB with details about the new
4 | features, modifications and bug fixes coming with all releases.
5 |
6 | NOTE: This page is rendered based on the content of
7 | https://github.com/rackslab/racksdb/blob/main/CHANGELOG.md[`CHANGELOG.md`]
8 | in RacksDB source code.
9 |
10 | include::partial$CHANGELOG.adoc[lines=8..-1,leveloffset=-1]
11 |
--------------------------------------------------------------------------------
/racksdb/dtypes/rack_height.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from racksdb.generic.definedtype import SchemaDefinedType
8 |
9 |
10 | class SchemaDefinedTypeRackHeight(SchemaDefinedType):
11 | pattern = r"(\d+)u"
12 | native = int
13 |
14 | def parse(self, value):
15 | match = self._match(value)
16 | return int(match.group(1))
17 |
--------------------------------------------------------------------------------
/racksdb/generic/errors.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 |
8 | class DBSchemaError(Exception):
9 | pass
10 |
11 |
12 | class DBFormatError(Exception):
13 | pass
14 |
15 |
16 | class DBDumperError(Exception):
17 | pass
18 |
19 |
20 | class DBViewError(Exception):
21 | pass
22 |
23 |
24 | class DBOpenAPIGeneratorError(Exception):
25 | pass
26 |
--------------------------------------------------------------------------------
/racksdb/dtypes/netif_type.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from racksdb.generic.definedtype import SchemaDefinedType
8 |
9 |
10 | class SchemaDefinedTypeNetifType(SchemaDefinedType):
11 | pattern = r"(ethernet|infiniband)"
12 | native = str
13 |
14 | def parse(self, value):
15 | self._match(value) # just check it matches
16 | return value
17 |
--------------------------------------------------------------------------------
/racksdb/dtypes/storage_type.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from racksdb.generic.definedtype import SchemaDefinedType
8 |
9 |
10 | class SchemaDefinedTypeStorageType(SchemaDefinedType):
11 | pattern = r"(ssd|disk|nvme)"
12 | native = str
13 |
14 | def parse(self, value):
15 | self._match(value) # just check it matches
16 | return value
17 |
--------------------------------------------------------------------------------
/examples/db/types/nodes/sm220bt.yml:
--------------------------------------------------------------------------------
1 | model: SuperMicro A+ Server 2124BT-HTR
2 | height: 1u
3 | width: 1/2
4 | specs: https://www.supermicro.com/en/aplus/system/2u/2124/as-2124bt-htr.cfm
5 | cpu:
6 | sockets: 2
7 | model: AMD EPYC 7573X
8 | specs: https://www.amd.com/en/products/cpu/amd-epyc-7573x
9 | cores: 32
10 | ram:
11 | dimm: 8
12 | size: 32GB
13 | storage:
14 | - type: nvme
15 | model: Samsung 980 Pro
16 | size: 256GB
17 | netifs:
18 | - type: ethernet
19 | bandwidth: 10Gb
20 |
--------------------------------------------------------------------------------
/frontend/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath } from 'node:url'
2 | import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
3 | import viteConfig from './vite.config'
4 |
5 | export default mergeConfig(
6 | viteConfig,
7 | defineConfig({
8 | test: {
9 | environment: 'jsdom',
10 | setupFiles: ['./tests/setup.ts'],
11 | exclude: [...configDefaults.exclude, 'e2e/*'],
12 | root: fileURLToPath(new URL('./', import.meta.url))
13 | }
14 | })
15 | )
16 |
--------------------------------------------------------------------------------
/racksdb/drawers/coordinates/json.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2024 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import json
8 |
9 | from .base import CoordinatesDumper
10 |
11 |
12 | class CoordinatesDumperJson(CoordinatesDumper):
13 | def __init__(self):
14 | pass
15 |
16 | def dump(self, coordinates):
17 | return json.dumps(
18 | {key: coordinate._serialized for key, coordinate in coordinates.items()}
19 | )
20 |
--------------------------------------------------------------------------------
/racksdb/drawers/coordinates/yaml.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2024 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import yaml
8 |
9 | from .base import CoordinatesDumper
10 |
11 |
12 | class CoordinatesDumperYaml(CoordinatesDumper):
13 | def __init__(self):
14 | pass
15 |
16 | def dump(self, coordinates):
17 | return yaml.dump(
18 | {key: coordinate._serialized for key, coordinate in coordinates.items()}
19 | )
20 |
--------------------------------------------------------------------------------
/docs/antora.yml:
--------------------------------------------------------------------------------
1 | name: racksdb
2 | title: RacksDB
3 | version: v0.6
4 | start_page: overview:start.adoc
5 | asciidoc:
6 | attributes:
7 | source-language: asciidoc@
8 | table-caption: false
9 | api-version: v0.6.0
10 | nav:
11 | - modules/overview/nav.adoc
12 | - modules/install/nav.adoc
13 | - modules/db/nav.adoc
14 | - modules/usage/nav.adoc
15 | - modules/misc/nav.adoc
16 | ext:
17 | collector:
18 | run:
19 | command: sh docs/generate-changelog.sh
20 | scan:
21 | dir: generated-doc
22 |
--------------------------------------------------------------------------------
/frontend/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath, URL } from 'node:url'
2 |
3 | import { defineConfig } from 'vite'
4 | import vue from '@vitejs/plugin-vue'
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig({
8 | plugins: [
9 | vue(),
10 | ],
11 | resolve: {
12 | alias: {
13 | '@': fileURLToPath(new URL('./src', import.meta.url))
14 | }
15 | },
16 | build: {
17 | target: 'esnext' // required for top-level await used by runtimeConfiguration plugin
18 | }
19 | })
20 |
--------------------------------------------------------------------------------
/examples/db/types/nodes/sm610u.yml:
--------------------------------------------------------------------------------
1 | model: SuperMicro Ultra SYS-610U-TNR
2 | height: 1u
3 | width: full
4 | specs: https://www.supermicro.com/en/products/system/ultra/1u/sys-610u-tnr
5 | cpu:
6 | sockets: 2
7 | model: Intel Xeon Gold 6338
8 | specs: https://ark.intel.com/content/www/us/en/ark/products/212285/intel-xeon-gold-6338-processor-48m-cache-2-00-ghz.html
9 | cores: 20
10 | ram:
11 | dimm: 16
12 | size: 16GB
13 | netifs:
14 | - type: ethernet
15 | bandwidth: 10Gb
16 | - type: ethernet
17 | bandwidth: 10Gb
18 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/lib/bases.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 |
8 | class TestAppleCrateBase:
9 | def _filter(self, quantity_min=None):
10 | if quantity_min and quantity_min > self.quantity:
11 | return False
12 | return True
13 |
14 |
15 | class TestAppleBase:
16 | def _filter(self, color=None):
17 | if color and color != self.color:
18 | return False
19 | return True
20 |
--------------------------------------------------------------------------------
/docs/modules/install/partials/pip-deps.adoc:
--------------------------------------------------------------------------------
1 | [source,console]
2 | ----
3 | $ sudo apt install libcairo2-dev libgirepository-2.0-dev
4 | ----
5 |
6 | [CAUTION]
7 | ====
8 | On Debian _« bookworm »_, `libgirepository-2.0-dev` package is not
9 | available. As a workaround, you can install `libgirepository1.0-dev` instead
10 | with an older version of PyGObject in your virtual environment:
11 |
12 | [source,console]
13 | ----
14 | $ sudo apt install libcairo2-dev libgirepository1.0-dev
15 | $ pip install "PyGObject<3.43.0"
16 | ----
17 | ====
18 |
--------------------------------------------------------------------------------
/docs/man/racksdb.adoc:
--------------------------------------------------------------------------------
1 | = racksdb(1)
2 | Rackslab: https://rackslab.io
3 | :doctype: manpage
4 | :manmanual: racksdb
5 | :man-linkstyle: pass:[blue R < >]
6 |
7 | == Name
8 |
9 | racksdb - Explore Yaml database of datacenter infrastructures
10 |
11 | include::../modules/usage/pages/racksdb.adoc[]
12 |
13 | == Resources
14 |
15 | RacksDB web site: https://github.com/rackslab/racksdb
16 |
17 | == Copying
18 |
19 | Copyright (C) 2022-2023 {author}. +
20 |
21 | RacksDB is distributed under the terms of the GNU General Public License v3.0
22 | or later (GPLv3+).
23 |
--------------------------------------------------------------------------------
/docs/modules/db/pages/structure.adoc:
--------------------------------------------------------------------------------
1 | = Database Structure Reference
2 |
3 | This page contains *the reference documentation of RacksDB database structure*,
4 | with all objects models and properties. The document starts with a legend of
5 | some symbols used for schematic representation of properties. Then, the root
6 | content of the database is described, followed by all subsequents objects. At
7 | the end, the defined types are described.
8 |
9 | include::partial$symbols.adoc[]
10 |
11 | include::partial$objects.adoc[]
12 |
13 | include::partial$deftypes.adoc[]
14 |
--------------------------------------------------------------------------------
/racksdb/drawers/coordinates/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2024 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from .json import CoordinatesDumperJson
8 | from .yaml import CoordinatesDumperYaml
9 |
10 |
11 | class CoordinateDumperFactory:
12 | FORMATS = {"json": CoordinatesDumperJson, "yaml": CoordinatesDumperYaml}
13 |
14 | @staticmethod
15 | def create(format: str):
16 | assert format in CoordinateDumperFactory.FORMATS.keys()
17 | return CoordinateDumperFactory.FORMATS[format]()
18 |
--------------------------------------------------------------------------------
/docs/man/racksdb-web.adoc:
--------------------------------------------------------------------------------
1 | = racksdb-web(1)
2 | Rackslab: https://rackslab.io
3 | :doctype: manpage
4 | :manmanual: racksdb-web
5 | :man-linkstyle: pass:[blue R < >]
6 |
7 | == Name
8 |
9 | racksdb-web - RacksDB web application with REST API
10 |
11 | include::../modules/usage/pages/racksdb-web.adoc[]
12 |
13 | == Resources
14 |
15 | RacksDB web site: https://github.com/rackslab/racksdb
16 |
17 | == Copying
18 |
19 | Copyright (C) 2022-2023 {author}. +
20 |
21 | RacksDB is distributed under the terms of the GNU General Public License v3.0
22 | or later (GPLv3+).
23 |
--------------------------------------------------------------------------------
/examples/db/types/nodes/hpesyn480.yml:
--------------------------------------------------------------------------------
1 | model: HPE Synergy 480 Gen10 Compute Module
2 | height: 5u
3 | width: 1/7
4 | specs: https://www.hpe.com/psnow/doc/PSN1010025863USEN.pdf
5 | cpu:
6 | sockets: 2
7 | model: Intel Xeon Scalable 6148
8 | specs: https://www.intel.com/content/www/us/en/products/sku/120489/intel-xeon-gold-6148-processor-27-5m-cache-2-40-ghz/specifications.html
9 | cores: 20
10 | ram:
11 | dimm: 4
12 | size: 32GB
13 | storage:
14 | - type: nvme
15 | model: Intel Optane
16 | size: 256GB
17 | netifs:
18 | - type: ethernet
19 | bandwidth: 10Gb
20 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/test_openapi.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 |
9 | from racksdb.generic.openapi import OpenAPIGenerator
10 |
11 | from .lib.views import TestDBViews
12 | from .lib.common import valid_schema
13 |
14 |
15 | class TestDBViewSet(unittest.TestCase):
16 | def test_generator(self):
17 | generator = OpenAPIGenerator(
18 | "Test", "1.0", {"Test": valid_schema()}, TestDBViews()
19 | )
20 | generator.generate()
21 |
--------------------------------------------------------------------------------
/frontend/src/main.ts:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2022-2023 Rackslab
2 |
3 | This file is part of RacksDB.
4 |
5 | SPDX-License-Identifier: MIT */
6 |
7 | import './index.css'
8 | import { createApp } from 'vue'
9 |
10 | import App from './App.vue'
11 | import router from './router'
12 | import { initRuntimeConfiguration, runtimeConfiguration } from './plugins/runtimeConfiguration'
13 | import { httpPlugin } from './plugins/http'
14 |
15 | const app = createApp(App)
16 | app.use(router)
17 | app.use(runtimeConfiguration, await initRuntimeConfiguration())
18 | app.use(httpPlugin)
19 | app.mount('#app')
20 |
--------------------------------------------------------------------------------
/docs/modules/usage/examples/custom.json:
--------------------------------------------------------------------------------
1 | {
2 | "margin": {
3 | "top": 200
4 | },
5 | "colors": {
6 | "equipments": [
7 | {
8 | "type": "sm610u",
9 | "background": "#348feb"
10 | },
11 | {
12 | "tags": ["hpc", "servers"],
13 | "border": "#a66321"
14 | }
15 | ],
16 | "racks": [
17 | {
18 | "type": "half",
19 | "pane": "#8d42f5",
20 | "frame": "#81a85e"
21 | }
22 | ]
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/racksdb/errors.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 |
8 | from .generic.errors import DBFormatError, DBSchemaError
9 |
10 |
11 | class RacksDBError(Exception):
12 | pass
13 |
14 |
15 | class RacksDBRequestError(RacksDBError):
16 | pass
17 |
18 |
19 | class RacksDBNotFoundError(RacksDBError):
20 | pass
21 |
22 |
23 | class RacksDBFormatError(DBFormatError):
24 | pass
25 |
26 |
27 | class RacksDBSchemaError(DBSchemaError):
28 | pass
29 |
30 |
31 | class RacksDBDrawingError(RacksDBError):
32 | pass
33 |
--------------------------------------------------------------------------------
/examples/db/types/nodes/bullsx440a5.yml:
--------------------------------------------------------------------------------
1 | model: BullSequana X410 A5
2 | height: 2u
3 | width: full
4 | specs: https://atos.net/wp-content/uploads/2023/04/BullSequana-X400-series_Factsheet.pdf
5 | cpu:
6 | sockets: 2
7 | model: AMD EPYC 7773X
8 | specs: https://www.amd.com/en/products/cpu/amd-epyc-7773x
9 | cores: 64
10 | ram:
11 | dimm: 16
12 | size: 32GB
13 | gpu:
14 | - model: A100
15 | memory: 40GB
16 | - model: A100
17 | memory: 40GB
18 | - model: A100
19 | memory: 40GB
20 | - model: A100
21 | memory: 40GB
22 | netifs:
23 | - type: ethernet
24 | bandwidth: 10Gb
25 | - type: ethernet
26 | bandwidth: 10Gb
27 |
--------------------------------------------------------------------------------
/examples/db/types/nodes/dellr550.yml:
--------------------------------------------------------------------------------
1 | model: Dell PowerEdge R550
2 | height: 2u
3 | width: full
4 | specs: https://www.delltechnologies.com/asset/en-us/products/servers/technical-support/dellemc-poweredge-r550-spec-sheet.pdf
5 | cpu:
6 | sockets: 2
7 | model: Intel Xeon Silver 4316
8 | specs: https://www.intel.com/content/www/us/en/products/sku/215270/intel-xeon-silver-4316-processor-30m-cache-2-30-ghz/specifications.html
9 | cores: 20
10 | ram:
11 | dimm: 4
12 | size: 32GB
13 | storage:
14 | - type: ssd
15 | size: 960GB
16 | netifs:
17 | - type: ethernet
18 | bandwidth: 1Gb
19 | - type: ethernet
20 | bandwidth: 1Gb
21 |
--------------------------------------------------------------------------------
/racksdb/dtypes/angle.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from racksdb.generic.definedtype import SchemaDefinedType
8 | from racksdb.generic.errors import DBFormatError
9 |
10 |
11 | class SchemaDefinedTypeAngle(SchemaDefinedType):
12 | pattern = r"\d+"
13 | native = int
14 |
15 | def parse(self, value):
16 | match = self._match(value)
17 | degrees = int(match.group(0))
18 | if degrees < 0 or degrees > 360:
19 | raise DBFormatError(f"Invalid angle of {degrees} degrees")
20 | return degrees
21 |
--------------------------------------------------------------------------------
/racksdb/dtypes/dimension.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from racksdb.generic.definedtype import SchemaDefinedType
8 |
9 |
10 | class SchemaDefinedTypeDimension(SchemaDefinedType):
11 | pattern = r"(\d+(.\d+)?)(mm|cm|m)"
12 | native = int
13 |
14 | def parse(self, value):
15 | match = self._match(value)
16 | size = float(match.group(1))
17 | unit = match.group(3)
18 | if unit == "cm":
19 | size *= 10
20 | elif unit == "m":
21 | size *= 1000
22 | return int(size)
23 |
--------------------------------------------------------------------------------
/racksdb/tests/lib/web.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import json
8 |
9 | import flask
10 |
11 |
12 | class RacksDBCustomTestResponse(flask.Response):
13 | """Custom flask Response class to backport text property of
14 | werkzeug.test.TestResponse class on werkzeug < 0.15."""
15 |
16 | @property
17 | def text(self):
18 | return self.get_data(as_text=True)
19 |
20 | @property
21 | def json(self):
22 | if self.mimetype != "application/json":
23 | return None
24 | return json.loads(self.text)
25 |
--------------------------------------------------------------------------------
/racksdb/dtypes/watts.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from racksdb.generic.definedtype import SchemaDefinedType
8 |
9 |
10 | class SchemaDefinedTypeWatts(SchemaDefinedType):
11 | pattern = r"(\d+(.\d+)?)(W|kW|MW)"
12 | native = int
13 |
14 | def parse(self, value):
15 | match = self._match(value)
16 | quantity = float(match.group(1))
17 | unit = match.group(3)
18 | if unit == "kW":
19 | quantity *= 10**3
20 | elif unit == "MW":
21 | quantity *= 10**6
22 | return int(quantity)
23 |
--------------------------------------------------------------------------------
/racksdb/generic/definedtype.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import re
8 |
9 | from .errors import DBFormatError
10 |
11 |
12 | class SchemaDefinedType:
13 | def __init__(self):
14 | self.name = self.__class__.__module__
15 |
16 | def __str__(self):
17 | return f"~{self.name}"
18 |
19 | def _match(self, value):
20 | regex = re.compile(self.pattern)
21 | match = regex.match(str(value))
22 | if match is None:
23 | raise DBFormatError(f"Unable to match {self} pattern with value {value}")
24 | return match
25 |
--------------------------------------------------------------------------------
/frontend/tests/views/HomeView.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect } from 'vitest'
10 | import { mount } from '@vue/test-utils'
11 |
12 | import HomeView from '@/views/HomeView.vue'
13 |
14 | describe('HomeView', () => {
15 | test('renders Overview title and stubs children', () => {
16 | const wrapper = mount(HomeView, {
17 | global: {
18 | stubs: {
19 | BreadCrumbs: true,
20 | HomeViewCards: true
21 | }
22 | }
23 | })
24 |
25 | expect(wrapper.text()).toContain('Overview')
26 | })
27 | })
28 |
--------------------------------------------------------------------------------
/frontend/src/views/HomeView.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
Overview
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/frontend/src/plugins/http.ts:
--------------------------------------------------------------------------------
1 | /*Copyright (c) 2022-2023 Rackslab
2 |
3 | This file is part of RacksDB.
4 |
5 | SPDX-License-Identifier: MIT */
6 |
7 | import type { App, Plugin } from 'vue'
8 | import { inject } from 'vue'
9 | import axios from 'axios'
10 | import type { AxiosInstance } from 'axios'
11 |
12 | const injectionKey = Symbol('http')
13 |
14 | export const useHttp = () => inject(injectionKey) as AxiosInstance
15 |
16 | export const httpPlugin: Plugin = {
17 | install(app: App) {
18 | const http = axios.create({
19 | baseURL: `${app.config.globalProperties.$rc.api_server}${app.config.globalProperties.$rc.api_version}`
20 | })
21 | app.provide(injectionKey, http)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/racksdb/dtypes/bits.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from racksdb.generic.definedtype import SchemaDefinedType
8 |
9 |
10 | class SchemaDefinedTypeBits(SchemaDefinedType):
11 | pattern = r"(\d+(.\d+)?)(Tb|Gb|Mb)"
12 | native = int
13 |
14 | def parse(self, value):
15 | match = self._match(value)
16 | quantity = float(match.group(1))
17 | unit = match.group(3)
18 | if unit == "Mb":
19 | quantity *= 10**6
20 | elif unit == "Gb":
21 | quantity *= 10**9
22 | elif unit == "Tb":
23 | quantity *= 10**12
24 | return int(quantity)
25 |
--------------------------------------------------------------------------------
/frontend/public/assets/racksdb-marker.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/racksdb/dtypes/bytes.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from racksdb.generic.definedtype import SchemaDefinedType
8 |
9 |
10 | class SchemaDefinedTypeBytes(SchemaDefinedType):
11 | pattern = r"(\d+(.\d+)?)(TB|GB|MB)"
12 | native = int
13 |
14 | def parse(self, value):
15 | match = self._match(value)
16 | quantity = float(match.group(1))
17 | unit = match.group(3)
18 | if unit == "MB":
19 | quantity *= 1024**2
20 | elif unit == "GB":
21 | quantity *= 1024**3
22 | elif unit == "TB":
23 | quantity *= 1024**4
24 | return int(quantity)
25 |
--------------------------------------------------------------------------------
/examples/db/infrastructures/sharednet.yml:
--------------------------------------------------------------------------------
1 | description: Central network infrastructure
2 | layout:
3 | - rack: R2-A02
4 | network:
5 | - name: netmercurysw[01-02]
6 | type: cisco3650
7 | slot: 18
8 | tags: [network]
9 | - rack: rackE01
10 | network:
11 | - name: netsw01
12 | type: cisco3650
13 | slot: 40
14 | tags: [network, shared]
15 | - rack: rackE02
16 | network:
17 | - name: netsw02
18 | type: cisco3650
19 | slot: 40
20 | tags: [network, shared]
21 | - rack: R5-A01
22 | network:
23 | - name: netsw03
24 | type: cisco3650
25 | slot: 40
26 | tags: [network, shared]
27 | - rack: R5-A02
28 | network:
29 | - name: netsw04
30 | type: cisco3650
31 | slot: 40
32 | tags: [network, shared]
33 |
--------------------------------------------------------------------------------
/racksdb/dtypes/rack_width.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from racksdb.generic.definedtype import SchemaDefinedType
8 |
9 |
10 | class SchemaDefinedTypeRackWidth(SchemaDefinedType):
11 | pattern = r"full|(\d+)(/\d+)?"
12 | native = float
13 |
14 | def parse(self, value):
15 | match = self._match(value)
16 | if value == "full":
17 | return 1.0
18 | else:
19 | dividend = int(match.group(1))
20 | divisor = match.group(2)
21 | if divisor is not None:
22 | divisor = float(divisor[1:])
23 | else:
24 | divisor = 1.0
25 | return dividend / divisor
26 |
--------------------------------------------------------------------------------
/racksdb/drawers/dtypes/hexcolor.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import re
8 | from typing import Tuple
9 |
10 | from racksdb.generic.definedtype import SchemaDefinedType
11 |
12 |
13 | class SchemaDefinedTypeHexcolor(SchemaDefinedType):
14 | pattern = r"^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$"
15 | native = Tuple[float, float, float, float]
16 |
17 | def parse(self, value):
18 | self._match(value)
19 | hexrgb = re.findall("[0-9a-fA-F]{2}", value)
20 | # If optional alpha channel is not defined, set it to 255/ff (ie. fully opaque)
21 | if len(hexrgb) == 3:
22 | hexrgb.append("ff")
23 | rgb = tuple(int(f"0x{_hex}", 16) / 255 for _hex in hexrgb)
24 | return rgb
25 |
--------------------------------------------------------------------------------
/docs/modules/usage/partials/drawing-deftypes.adoc:
--------------------------------------------------------------------------------
1 | == Defined Types
2 |
3 | :url-deftypes: https://github.com/rackslab/racksdb/blob/main/racksdb/drawers/dtypes
4 |
5 | [cols="2a,2a,1l,3a"]
6 | |===
7 | |Name|Examples|Resulting type|Comment
8 |
9 | |[#deftype-hexcolor]`~hexcolor`
10 | {url-deftypes}/hexcolor.py[icon:code[]]
11 | |* `#ffffff`
12 | * `#a66321`
13 | * `#348febaa`
14 | |tuple(float, float, float, float)
15 | |Hexadecimal color code (see
16 | https://en.wikipedia.org/wiki/Web_colors#Hex_triplet[Wikipedia article for more
17 | details]) with optional alpha channel. Default alpha channel value is `ff` (ie.
18 | fully opaque). The resulting value is tuple of 4 floats between 0 and 1
19 | representing the rate of red, green and blue primary colors in the resulting
20 | color and the color opacity (from 0 for fully transparent to 1 for fully
21 | opaque).
22 |
23 | |===
24 |
--------------------------------------------------------------------------------
/racksdb/generic/dumpers/console.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from typing import Any
8 |
9 | from ClusterShell.NodeSet import NodeSet
10 |
11 | from ..errors import DBDumperError
12 |
13 |
14 | class DBDumperConsole:
15 | def __init__(self, show_types=False, objects_map={}, fold=True):
16 | self.objects_map = objects_map
17 | self.fold = fold
18 |
19 | def dump(
20 | self,
21 | obj: Any,
22 | ) -> str:
23 | if not isinstance(obj, list):
24 | raise DBDumperError(f"Unsupported type '{type(obj)}' for DBDumperConsole")
25 | if self.fold:
26 | nodeset = NodeSet()
27 | for item in obj:
28 | nodeset.update(item)
29 | return str(nodeset)
30 | else:
31 | return "\n".join(obj)
32 |
--------------------------------------------------------------------------------
/frontend/src/components/DatacenterListInfrastructures.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
15 |
16 |
17 |
18 |
24 | {{ infrastructure }}
25 | ,
26 |
27 |
28 |
29 | -
30 |
31 |
32 |
--------------------------------------------------------------------------------
/docs/modules/db/pages/def.adoc:
--------------------------------------------------------------------------------
1 | = Database Definition
2 |
3 | The content of RacksDB database is formatted in https://yaml.org/[YAML
4 | language]. The database can be stored in a single YAML file or splitted in
5 | multiple files. The content of the files must match the database schema provided
6 | by RacksDB with its expected objects and properties.
7 |
8 | This section gives all detailed information to define a RacksDB database,
9 | including:
10 |
11 | * The xref:files.adoc[location of the database] on the system and how-to
12 | organize it into multiple files.
13 | * General information of the xref:schema.adoc[schema file],
14 | * The xref:structure.adoc[database structure], with its objects and the involved
15 | defined types.
16 | * How-to xref:ext.adoc[define extensions] of database schema for custom data.
17 | * Guides to help specify the xref:positioning-racks.adoc[positions of racks]
18 | and xref:positioning-equipments.adoc[equipments in racks]
19 |
--------------------------------------------------------------------------------
/racksdb/tests/test_defined_type_hexcolor.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 |
9 | from racksdb.drawers.dtypes.hexcolor import SchemaDefinedTypeHexcolor
10 | from racksdb.generic.errors import DBFormatError
11 |
12 |
13 | class TestDefinedTypeHexcolor(unittest.TestCase):
14 | def test_defined_type_hexcolor(self):
15 | defined_type = SchemaDefinedTypeHexcolor()
16 | self.assertEqual(defined_type.parse("#004080"), (0, 64 / 255, 128 / 255, 1.0))
17 | self.assertEqual(
18 | defined_type.parse("#ffb4c5"), (1.0, 180 / 255, 197 / 255, 1.0)
19 | )
20 |
21 | def test_defined_type_hexcolor_invalid_values(self):
22 | defined_type = SchemaDefinedTypeHexcolor()
23 | for value in ["001122", "fail"]:
24 | with self.assertRaises(DBFormatError):
25 | defined_type.parse(value)
26 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/test_schema_defined_type_loader.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2022-2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 |
9 | from racksdb.generic.schema import SchemaDefinedTypeLoader
10 | from racksdb.generic.definedtype import SchemaDefinedType
11 |
12 |
13 | class TestSchemaDefinedTypeLoader(unittest.TestCase):
14 | def test_schema_defined_type_loader(self):
15 | # Load defined types provided in RacksDB
16 | loader = SchemaDefinedTypeLoader("racksdb.dtypes")
17 | # Verify at least one defined type has been loaded
18 | self.assertGreater(len(loader.content), 0)
19 | # Verify loader content is a dict
20 | self.assertIs(type(loader.content), dict)
21 | # Verify content values are valid SchemaDefinedType
22 | for defined_type in loader.content.values():
23 | self.assertIsInstance(defined_type, SchemaDefinedType)
24 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # RacksDB Documentation
2 |
3 | ## Generate manpage
4 |
5 | To generate the manpage, run this command:
6 |
7 | ```sh
8 | $ ./update-materials man
9 | ```
10 |
11 | ## Generate objects reference documentation
12 |
13 | To generate the schema objects partial of database structure reference
14 | documentation, run this command:
15 |
16 | ```sh
17 | $ ./update-materials schema-ref
18 | ```
19 |
20 | ## Generate objects reference documentation
21 |
22 | To generate the schema objects partial of drawing parameters reference
23 | documentation, run this command:
24 |
25 | ```sh
26 | $ ./update-materials drawing-ref
27 | ```
28 |
29 | ## Update OpenAPI description
30 |
31 | To update reference OpenAPI description of RacksDB REST API, run this command:
32 |
33 | ```sh
34 | $ ./update-materials openapi
35 | ```
36 |
37 | ## Update PNG exports of SVG diagrams
38 |
39 | To update PNG bitmaps exports of SVG diagrams, run this command:
40 |
41 | ```sh
42 | $ ./update-materials png
43 | ```
44 |
--------------------------------------------------------------------------------
/racksdb/tests/test_defined_type_watts.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 |
9 | from racksdb.dtypes.watts import SchemaDefinedTypeWatts
10 | from racksdb.generic.errors import DBFormatError
11 |
12 |
13 | class TestDefinedTypeWatts(unittest.TestCase):
14 | def test_defined_type_watts(self):
15 | defined_type = SchemaDefinedTypeWatts()
16 | self.assertEqual(defined_type.parse("15W"), 15)
17 | self.assertEqual(defined_type.parse("100.4kW"), 100.4 * 10**3)
18 | self.assertEqual(defined_type.parse("3.4MW"), 3.4 * 10**6)
19 | self.assertEqual(defined_type.parse("1.1W"), 1) # rounded to integer below
20 |
21 | def test_defined_type_watts_invalid_values(self):
22 | defined_type = SchemaDefinedTypeWatts()
23 | for value in ["1", "1.4.3W", "3.4K"]:
24 | with self.assertRaises(DBFormatError):
25 | defined_type.parse(value)
26 |
--------------------------------------------------------------------------------
/frontend/src/views/InfrastructuresView.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/racksdb/generic/dumpers/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from .yaml import DBDumperYAML, SchemaDumperYAML
8 | from .json import DBDumperJSON
9 | from .console import DBDumperConsole
10 | from ..errors import DBDumperError
11 |
12 |
13 | class DBDumperFactory:
14 | FORMATS = {"yaml": DBDumperYAML, "json": DBDumperJSON, "console": DBDumperConsole}
15 |
16 | @staticmethod
17 | def get(_format):
18 | if _format not in DBDumperFactory.FORMATS:
19 | raise DBDumperError(f"Unsupported DB dump format {_format}")
20 | return DBDumperFactory.FORMATS[_format]
21 |
22 |
23 | class SchemaDumperFactory:
24 | FORMATS = {"yaml": SchemaDumperYAML}
25 |
26 | @staticmethod
27 | def get(_format):
28 | if _format not in SchemaDumperFactory.FORMATS:
29 | raise DBDumperError(f"Unsupported schema dump format {_format}")
30 | return SchemaDumperFactory.FORMATS[_format]
31 |
--------------------------------------------------------------------------------
/examples/db/infrastructures/mercury.yml:
--------------------------------------------------------------------------------
1 | description: Mercury HPC cluster
2 | tags:
3 | - hpc
4 | - cluster
5 | layout:
6 | - rack: R1-A01
7 | tags: [compute]
8 | nodes:
9 | - name: mecn[0001-0040]
10 | type: sm220bt
11 | slot: 1
12 | - name: mecn0200
13 | type: sm610u
14 | slot: 22
15 | - name: mecn[0041-0060]
16 | type: sm610u
17 | slot: 23
18 | - rack: R1-A02
19 | tags: [compute]
20 | nodes:
21 | - name: mecn[0061-0116]
22 | type: hpesyn480
23 | slot: 3
24 | - rack: R2-A02
25 | network:
26 | - name: mesw[01-04]
27 | type: cisco3650
28 | slot: 9
29 | tags: [network]
30 | storage:
31 | - name: swnas01
32 | type: qnaph1277
33 | slot: 13
34 | tags: [storage]
35 | - rack: R2-A03
36 | nodes:
37 | - name: mesrv[0001-0004]
38 | type: dellr550
39 | slot: 9
40 | tags: [servers]
41 | - name: megpu[0001-0008]
42 | type: bullsx440a5
43 | slot: 21
44 | tags: [ia, gpu]
45 | misc:
46 | - name: mekvm01
47 | type: kvm
48 | slot: 18
49 |
--------------------------------------------------------------------------------
/docs/modules/db/examples/infrastructure.yml:
--------------------------------------------------------------------------------
1 | # tag::name[]
2 | name: {infra}
3 | # end::name[]
4 | # tag::desc[]
5 | description: Tiger cluster
6 | # end::desc[]
7 | # tag::layout[]
8 | layout:
9 | # end::layout[]
10 | # tag::rack-1[]
11 | - rack: R01 # 1^st^ rack
12 | nodes:
13 | - name: cn[001-040] # *set 1*: 40 *denses* nodes starting from slot *2*
14 | type: dense
15 | slot: 3
16 | - name: cn[100-104] # *set 2*: 4 *classic* nodes starting from slot *24*
17 | type: classic
18 | slot: 25
19 | - name: srv[001-002] # *set 3*: 2 *fat* nodes starting from slot *30*
20 | type: fat
21 | slot: 31
22 | # end::rack-1[]
23 | # tag::rack-2[]
24 | - rack: R02 # 2^nd^ rack
25 | nodes:
26 | - name: cn[201-228] # *set 4*: 28 *blade* nodes starting from slot *2*
27 | type: blade
28 | slot: 3
29 | network:
30 | - name: sw[001-004] # *set 5*: 4 rack switches starting from slot *26*
31 | type: racksw
32 | slot: 27
33 | storage:
34 | - name: nas001 # *set 6*: 1 NAS in slot *35*
35 | type: nas
36 | slot: 36
37 | # end::rack-2[]
38 |
--------------------------------------------------------------------------------
/frontend/src/plugins/runtimeConfiguration.ts:
--------------------------------------------------------------------------------
1 | /*Copyright (c) 2022-2023 Rackslab
2 |
3 | This file is part of RacksDB.
4 |
5 | SPDX-License-Identifier: MIT */
6 |
7 | import type { App, InjectionKey, Plugin } from 'vue'
8 | import { inject } from 'vue'
9 |
10 | export interface RuntimeConfiguration {
11 | api_server: string
12 | api_version: string
13 | }
14 |
15 | export const injectionKey = Symbol('rc') as InjectionKey
16 |
17 | export const runtimeConfiguration: Plugin = {
18 | install: (app: App, configuration: RuntimeConfiguration) => {
19 | app.provide(injectionKey, configuration)
20 | app.config.globalProperties.$rc = configuration
21 | }
22 | }
23 |
24 | export const initRuntimeConfiguration = async (): Promise => {
25 | const resp = await fetch('/config.json')
26 | const value = await resp.json()
27 |
28 | return {
29 | api_server: value.API_SERVER,
30 | api_version: value.API_VERSION
31 | } as RuntimeConfiguration
32 | }
33 |
34 | export const loadRuntimeConfiguration = () => inject(injectionKey) as RuntimeConfiguration
35 |
--------------------------------------------------------------------------------
/assets/README.md:
--------------------------------------------------------------------------------
1 | # RacksDB assets
2 |
3 | All assets are generated based on the content of `branding.svg` and
4 | `screenshots/raw/` by running this command:
5 |
6 | ```sh
7 | $ ./update-assets
8 | ```
9 |
10 | This automatically generates:
11 |
12 | - Scalable SVG files of the logo in `scalables/` folder,
13 | - Bitmaps PNG files (in various sizes) of the logo in `bitmaps/` folder,
14 | - Favicon in `bitmaps/` folder,
15 | - Shadowed and Webp compressed versions of raw PNG screenshots.
16 |
17 | The script has some requirements:
18 |
19 | * Python3
20 | * [Ninja build system](https://ninja-build.org/). On Debian/Ubuntu, this can be
21 | installed with:
22 |
23 | ```sh
24 | $ sudo apt install ninja-build
25 | ```
26 | * [Inkscape](https://inkscape.org/)
27 | * [RFL build package](https://github.com/rackslab/RFL/tree/main/src/build)
28 | * [Scour Python package](https://github.com/scour-project/scour/tree/master)
29 | * [ImageMagick](https://imagemagick.org/index.php)
30 |
31 | All the generated files are commited and pushed in Git repository to allow
32 | direct usage by documentation site, main `README.md` file, etc…
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022-2025 Rackslab
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/racksdb/generic/dumpers/_common.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from typing import Dict, Union
8 |
9 |
10 | class MapperDumper:
11 | def __init__(self, objects_map: Dict[str, Union[str, None]]):
12 | self.objects_map = objects_map
13 |
14 | def map(self, obj, prop, value):
15 | found = False
16 | if f"{obj.__class__.__name__}.{prop}" in self.objects_map.keys():
17 | target = self.objects_map[f"{obj.__class__.__name__}.{prop}"]
18 | found = True
19 | if value.__class__.__name__ in self.objects_map.keys():
20 | target = self.objects_map[value.__class__.__name__]
21 | found = True
22 | if found:
23 | # If the object is mapped to None, discard the attribute and
24 | # continue to the next one.
25 | if target is None:
26 | return None
27 | # Else, map the object to one of its attribute.
28 | return getattr(value, target)
29 | # Mapping not found, return value unmodified.
30 | return value
31 |
--------------------------------------------------------------------------------
/docs/modules/overview/pages/start.adoc:
--------------------------------------------------------------------------------
1 | = Getting Started
2 |
3 | image::overview:racksdb_logo.png[RacksDB Overview,150,float=right]
4 |
5 | *RacksDB* is an _open source_ solution for modeling your datacenters
6 | infrastructures in a simple database schema to store information about
7 | the equipments in your datacenters provided with tools and library to request
8 | this database.
9 |
10 | icon:glasses[] Get a xref:overview.adoc[complete overview] of RacksDB purpose,
11 | goals and characteristics.
12 |
13 | icon:download[] For installation procedure, checkout the
14 | xref:install:quickstart.adoc[quickstart guide].
15 |
16 | Advanced detailed informations are available:
17 |
18 | [no-bullet]
19 | * icon:database[] xref:db:def.adoc[Database schema reference documentation]
20 | * icon:terminal[] xref:usage:racksdb.adoc[`racksdb` command manpage]
21 | * icon:code[] xref:usage:lib.adoc[Python Library] and
22 | xref:usage:rest.adoc[REST API] reference documentations
23 | * icon:mouse-pointer[] xref:usage:ui.adoc[Web UI user guide]
24 | * icon:maximize[] xref:db:ext.adoc[Schema extension howto]
25 |
26 | image::overview:racksdb_web_ui_screenshots.webp[RacksDB Web UI]
27 |
--------------------------------------------------------------------------------
/frontend/src/components/HeaderPage.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
16 |
17 |
18 |
19 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/test_db_list.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 |
9 | from racksdb.generic.db import DBList
10 |
11 | from .lib.common import valid_db
12 |
13 |
14 | class TestDBList(unittest.TestCase):
15 | def setUp(self):
16 | self.db = valid_db()
17 | self.assertIsInstance(self.db.stock.content, DBList)
18 |
19 | def test_iter(self):
20 | nb = 0
21 | for item in self.db.stock.content:
22 | nb += 1
23 | self.assertEqual(nb, 20)
24 |
25 | def test_len(self):
26 | self.assertEqual(len(self.db.stock.content), 20)
27 |
28 | def test_itervalues(self):
29 | nb = 0
30 | for item in self.db.stock.content.itervalues():
31 | nb += 1
32 | self.assertEqual(nb, 2)
33 |
34 | def test_filter(self):
35 | selected = self.db.stock.content.filter(quantity_min=15)
36 | self.assertIsInstance(selected, DBList)
37 | self.assertEqual(len(selected), 10)
38 | selected = self.db.stock.content.filter(quantity_min=5)
39 | self.assertEqual(len(selected), 20)
40 |
--------------------------------------------------------------------------------
/examples/db/infrastructures/jupiter.yml:
--------------------------------------------------------------------------------
1 | description: Jupiter data cluster
2 | tags:
3 | - data
4 | - cluster
5 | layout:
6 | - rack: rackE01
7 | storage:
8 | - name: junas[01-04]
9 | type: qnaph1277
10 | slot: 4
11 | tags: [storage]
12 | network:
13 | - name: jusw01
14 | type: cisco3650
15 | slot: 38
16 | tags: [network]
17 | - rack: rackE02
18 | tags: [storage]
19 | storage:
20 | - name: junas[05-08]
21 | type: qnaph1277
22 | slot: 4
23 | tags: [storage]
24 | network:
25 | - name: jusw02
26 | type: cisco3650
27 | slot: 38
28 | tags: [network]
29 | - rack: R5-A01
30 | storage:
31 | - name: junas[09-12]
32 | type: qnaph1277
33 | slot: 4
34 | tags: [storage]
35 | misc:
36 | - name: jukvm01
37 | type: kvm
38 | slot: 18
39 | nodes:
40 | - name: jusrv1
41 | type: dellr550
42 | slot: 19
43 | network:
44 | - name: jusw03
45 | type: cisco3650
46 | slot: 38
47 | tags: [network]
48 | - rack: R5-A02
49 | storage:
50 | - name: junas[13-16]
51 | type: qnaph1277
52 | slot: 4
53 | tags: [storage]
54 | network:
55 | - name: jusw04
56 | type: cisco3650
57 | slot: 38
58 | tags: [network]
59 |
--------------------------------------------------------------------------------
/docs/modules/db/pages/schema.adoc:
--------------------------------------------------------------------------------
1 | = Schema
2 |
3 | RacksDB is provided with a database schema. It is basically a YAML file that
4 | defines the _objects_ represented in the database with their _properties_, and
5 | the relations between these objects.
6 |
7 | The content of this file eventually defines the
8 | xref:structure.adoc[database structure]. This schema can be customized to store
9 | additional data using xref:ext.adoc[schema extensions].
10 |
11 | == Path
12 |
13 | When RacksDB is installed, the schema is available in path
14 | [.path]#`/usr/share/racksdb/schemas/racksdb.yml`#.
15 |
16 | The file is also available in
17 | https://github.com/rackslab/racksdb/blob/main/schemas/racksdb.yml[source code Git
18 | repository].
19 |
20 | == Content
21 |
22 | The schema file is a mapping that contains 3 main keys:
23 |
24 | `_version`:: Version number of the schema as a string.
25 | `_content`:: Database root object definition.
26 | `_objects`:: All other objects definitions.
27 |
28 | The format of `_content` is an xref:#object[object definition]. The format of
29 | `_objects` is a mapping whose keys are objects names and values are
30 | xref:#object[objects definitions].
31 |
32 | include::partial$prop-types.adoc[]
33 |
--------------------------------------------------------------------------------
/frontend/tests/views/InfrastructuresView.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect, vi, beforeEach } from 'vitest'
10 | import { mount, flushPromises } from '@vue/test-utils'
11 | import { nextTick } from 'vue'
12 |
13 | import InfrastructuresView from '@/views/InfrastructuresView.vue'
14 |
15 | const infrastructuresMock = vi.fn()
16 |
17 | vi.mock('@/plugins/http', () => ({
18 | useHttp: () => ({})
19 | }))
20 |
21 | vi.mock('@/composables/RacksDBAPI', () => ({
22 | useRacksDBAPI: () => ({
23 | infrastructures: infrastructuresMock
24 | })
25 | }))
26 |
27 | describe('InfrastructuresView', () => {
28 | beforeEach(() => vi.clearAllMocks())
29 |
30 | test('loads infrastructures and passes them to ComboBox', async () => {
31 | infrastructuresMock.mockResolvedValueOnce([{ name: 'core' }, { name: 'edge' }])
32 |
33 | const wrapper = mount(InfrastructuresView, {
34 | global: { stubs: { BreadCrumbs: true, ComboBox: true } }
35 | })
36 |
37 | await flushPromises()
38 | await nextTick()
39 |
40 | expect(infrastructuresMock).toHaveBeenCalled()
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/test_views.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 |
9 | from racksdb.generic.views import DBView, DBAction
10 | from racksdb.generic.errors import DBViewError
11 |
12 | from .lib.views import TestDBViews
13 |
14 |
15 | class TestDBViewSet(unittest.TestCase):
16 | def setUp(self):
17 | self.views = TestDBViews()
18 |
19 | def test_iter(self):
20 | for view in self.views:
21 | self.assertIsInstance(view, DBView)
22 |
23 | def test_views_actions(self):
24 | actions = self.views.views_actions()
25 | self.assertIsInstance(actions, list)
26 | for action in actions:
27 | self.assertIsInstance(action, DBAction)
28 |
29 | def test_actions(self):
30 | for action in self.views.actions():
31 | self.assertIsInstance(action, DBAction)
32 |
33 | def test_getitem(self):
34 | _ = self.views["apples"]
35 |
36 | def test_getitem_not_found(self):
37 | with self.assertRaisesRegex(
38 | DBViewError, "^Unable to find view for 'fail' content$"
39 | ):
40 | _ = self.views["fail"]
41 |
--------------------------------------------------------------------------------
/docs/modules/usage/pages/rest.adoc:
--------------------------------------------------------------------------------
1 | = REST API
2 |
3 | RacksDB provides a REST API to request database content and draw diagrams of
4 | datacenter rooms and infrastructures racks. This REST API is served by
5 | xref:racksdb-web.adoc[`racksdb-web` command]. This page contains the reference
6 | documentation of this API.
7 |
8 | [subs=attributes]
9 | ++++
10 |
11 |
18 |
34 | ++++
35 |
--------------------------------------------------------------------------------
/frontend/tests/components/HeaderPage.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect } from 'vitest'
10 | import { mount } from '@vue/test-utils'
11 | import { getRouter } from 'vue-router-mock'
12 |
13 | import HeaderPage from '@/components/HeaderPage.vue'
14 |
15 | describe('HeaderPage', () => {
16 | test('renders navigation links', () => {
17 | const router = getRouter()
18 | router.currentRoute.value = {
19 | meta: { entry: 'home' }
20 | }
21 |
22 | const wrapper = mount(HeaderPage)
23 |
24 | expect(wrapper.text()).toContain('Home')
25 | expect(wrapper.text()).toContain('Datacenters')
26 | expect(wrapper.text()).toContain('Infrastructures')
27 | })
28 |
29 | test('highlights active route based on meta entry', () => {
30 | const router = getRouter()
31 | router.currentRoute.value = {
32 | meta: { entry: 'datacenters' }
33 | }
34 |
35 | const wrapper = mount(HeaderPage)
36 |
37 | // Check that the component renders the navigation links
38 | expect(wrapper.text()).toContain('Home')
39 | expect(wrapper.text()).toContain('Datacenters')
40 | expect(wrapper.text()).toContain('Infrastructures')
41 | })
42 | })
43 |
--------------------------------------------------------------------------------
/docs/modules/db/partials/symbols.adoc:
--------------------------------------------------------------------------------
1 | :symbol-seq: icon:list[title=Sequence]
2 | :symbol-ref: icon:arrow-up-right-from-square[title=Reference]
3 | :symbol-obj: icon:cube[title=Object]
4 | :symbol-deftype: icon:shapes[title=Defined type]
5 | :symbol-backref: icon:share-from-square[title=Reference,flip=horizontal]
6 | :symbol-key: icon:key[title=Key]
7 | :symbol-computed: icon:cogs[title=Computed]
8 |
9 | == Symbols legend
10 |
11 | Some icons are used in the tables of this page for symbolic representations of
12 | xref:db:concepts.adoc#advanced[advanced data types]:
13 |
14 | [no-bullet]
15 | - {symbol-ref} xref:db:concepts.adoc#reference[*reference*]
16 | - {symbol-obj} xref:db:concepts.adoc#object[*object*]
17 | - {symbol-deftype} xref:db:concepts.adoc#deftype[*defined types*]
18 | - {symbol-backref} xref:db:concepts.adoc#backref[*back reference*]
19 |
20 | Other symbols are used for xref:db:concepts.adoc#attributes[attributes] of
21 | properties:
22 |
23 | [no-bullet]
24 | - {symbol-seq} xref:db:concepts.adoc#sequence[*sequence*]
25 | - {symbol-key} xref:db:concepts.adoc#key[*key*]
26 | - {symbol-computed} xref:db:concepts.adoc#computed[*computed*]
27 |
28 | The optional/required attribute is indicated in the _Required_ column of
29 | properties definition table. When defined, the default value attribute is
30 | mentioned in the _Description_ column.
31 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/dumpers/test_factory.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 |
9 | from racksdb.generic.dumpers import DBDumperFactory, SchemaDumperFactory
10 | from racksdb.generic.dumpers.json import DBDumperJSON
11 | from racksdb.generic.dumpers.yaml import DBDumperYAML, SchemaDumperYAML
12 | from racksdb.generic.dumpers.console import DBDumperConsole
13 | from racksdb.generic.errors import DBDumperError
14 |
15 |
16 | class TestDBDumperFactory(unittest.TestCase):
17 | def test_get(self):
18 | self.assertIs(DBDumperFactory.get("json"), DBDumperJSON)
19 | self.assertIs(DBDumperFactory.get("yaml"), DBDumperYAML)
20 | self.assertIs(DBDumperFactory.get("console"), DBDumperConsole)
21 |
22 | def test_get_fail(self):
23 | with self.assertRaisesRegex(DBDumperError, "Unsupported DB dump format fail"):
24 | DBDumperFactory.get("fail")
25 |
26 |
27 | class TestSchemaDumperFactor(unittest.TestCase):
28 | def test_get(self):
29 | self.assertIs(SchemaDumperFactory.get("yaml"), SchemaDumperYAML)
30 |
31 | def test_get_fail(self):
32 | with self.assertRaisesRegex(
33 | DBDumperError, "Unsupported schema dump format fail"
34 | ):
35 | SchemaDumperFactory.get("fail")
36 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 | # ui
2 |
3 | ui provides a web interface for simple and efficient access to the database data.
4 |
5 | ## VSCode/IDE
6 | For a better use, we recommend to use [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur)
7 | To make the TypeScript language service aware of `.vue` type we recommend to use [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
8 | To enhance the Tailwind code we recommend to use Tailwind CSS [IntelliSense](https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)
9 |
10 | ## Customize configuration
11 |
12 | See [Vite Configuration Reference](https://vitejs.dev/config/).
13 |
14 | ## Project Setup
15 |
16 | ```sh
17 | npm install
18 | ```
19 |
20 | ### Compile and Hot-Reload for Development
21 |
22 | ```sh
23 | npm run dev
24 | ```
25 |
26 | ### Type-Check, Compile and Minify for Production
27 |
28 | ```sh
29 | npm run build
30 | ```
31 |
32 | ### Run Unit Tests with [Vitest](https://vitest.dev/)
33 |
34 | ```sh
35 | npm run test:unit
36 | ```
37 |
38 | ### Lint with [ESLint](https://eslint.org/)
39 |
40 | ```sh
41 | npm run lint
42 | ```
43 |
44 | ### Prettier formatting with [Prettier](https://prettier.io/)
45 |
46 | ```sh
47 | npm run format
48 | ```
49 |
50 | ### Type-Check for Typescript
51 |
52 | ```sh
53 | npm run type-check
54 | ```
55 |
--------------------------------------------------------------------------------
/docs/utils/gen-openapi.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import os
4 | from pathlib import Path
5 |
6 | from rfl.build.projectversion import project_version
7 | from racksdb import RacksDB
8 | from racksdb.views import RacksDBViews
9 | from racksdb.generic.schema import Schema, SchemaFileLoader, SchemaDefinedTypeLoader
10 | from racksdb.generic.openapi import OpenAPIGenerator
11 | from racksdb.generic.dumpers import DBDumperFactory
12 | from racksdb.drawers.parameters import DrawingParameters
13 |
14 |
15 | def main():
16 | current_dir = os.path.dirname(os.path.realpath(__file__))
17 | racksdb_schema_path = Path(current_dir).joinpath("../../schemas/racksdb.yml")
18 | racksdb_schema = Schema(
19 | SchemaFileLoader(racksdb_schema_path),
20 | SchemaDefinedTypeLoader(RacksDB.DEFINED_TYPES_MODULE),
21 | )
22 | drawings_schema_path = Path(current_dir).joinpath("../../schemas/drawings.yml")
23 | drawing_schema = Schema(
24 | SchemaFileLoader(drawings_schema_path),
25 | SchemaDefinedTypeLoader(DrawingParameters.DEFINED_TYPES_MODULE),
26 | )
27 | views = RacksDBViews()
28 | openapi = OpenAPIGenerator(
29 | "RacksDB",
30 | project_version(),
31 | {"RacksDB": racksdb_schema, "Drawings": drawing_schema},
32 | views,
33 | )
34 | dumper = DBDumperFactory.get("yaml")()
35 | print(dumper.dump(openapi.generate()))
36 |
37 |
38 | if __name__ == "__main__":
39 | main()
40 |
--------------------------------------------------------------------------------
/frontend/tests/components/DatacenterListInfrastructures.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect } from 'vitest'
10 | import { mount } from '@vue/test-utils'
11 |
12 | import DatacenterListInfrastructures from '@/components/DatacenterListInfrastructures.vue'
13 |
14 | describe('DatacenterListInfrastructures', () => {
15 | test('renders infrastructure links when infrastructures are provided', () => {
16 | const wrapper = mount(DatacenterListInfrastructures, {
17 | props: {
18 | infrastructures: ['core', 'edge', 'storage']
19 | }
20 | })
21 |
22 | expect(wrapper.text()).toContain('core')
23 | expect(wrapper.text()).toContain('edge')
24 | expect(wrapper.text()).toContain('storage')
25 | expect(wrapper.text()).toContain(',') // comma separators
26 | })
27 |
28 | test('renders dash when no infrastructures are provided', () => {
29 | const wrapper = mount(DatacenterListInfrastructures, {
30 | props: {
31 | infrastructures: []
32 | }
33 | })
34 |
35 | expect(wrapper.text()).toContain('-')
36 | })
37 |
38 | test('renders single infrastructure without comma', () => {
39 | const wrapper = mount(DatacenterListInfrastructures, {
40 | props: {
41 | infrastructures: ['core']
42 | }
43 | })
44 |
45 | expect(wrapper.text()).toContain('core')
46 | expect(wrapper.text()).not.toContain(',')
47 | })
48 | })
49 |
--------------------------------------------------------------------------------
/docs/modules/db/examples/datacenter.yml:
--------------------------------------------------------------------------------
1 | # tag::datacenter[]
2 | datacenters:
3 | - name: mydatacenter
4 | rooms:
5 | - name: myroom
6 | # end::datacenter[]
7 | # tag::dimensions[]
8 | dimensions:
9 | depth: 8m
10 | width: 10m
11 | # end::dimensions[]
12 | # tag::rows[]
13 | rows:
14 | # end::rows[]
15 | # tag::row-r1[]
16 | # Row R1 with 4 racks horizontally aligned (no rotation)
17 | - name: R1
18 | position:
19 | depth: 2m
20 | width: 1m
21 | racks:
22 | - name: R1-[01-04] # 4 racks
23 | type: standard
24 | # end::row-r1[]
25 |
26 | # tag::row-r2[]
27 | # Row R2 with 4 racks and doors oriented to the top on the room.
28 | - name: R2
29 | reversed: true
30 | position:
31 | depth: 1.5m
32 | width: 6m
33 | racks:
34 | - name: R2-[01-04] # 4 racks
35 | type: standard
36 | # end::row-r2[]
37 |
38 | # tag::row-r3[]
39 | # Row R3 with 6 racks
40 | - name: R3
41 | position:
42 | depth: 4m
43 | width: 2m
44 | rotation: 30 # Slight rotation
45 | racks:
46 | - name: R3-[01-06] # 6 racks
47 | type: standard
48 | # end::row-r3[]
49 |
50 | # tag::row-r4[]
51 | # Row R4 with 6 racks.
52 | - name: R4
53 | reversed: true # Get doors oriented to room's right wall
54 | position:
55 | depth: 3m
56 | width: 8m
57 | rotation: 90 # Vertical row
58 | racks:
59 | - name: R4-[01-04] # 4 racks
60 | type: standard
61 | # end::row-r4[]
62 |
--------------------------------------------------------------------------------
/racksdb/drawers/parameters.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from pathlib import Path
8 | from typing import Union
9 |
10 | from ..generic.schema import Schema, SchemaFileLoader, SchemaDefinedTypeLoader
11 | from ..generic.db import GenericDB
12 |
13 |
14 | class DrawingParameters(GenericDB):
15 | DEFAULT_SCHEMA = "/usr/share/racksdb/schemas/drawings.yml"
16 | PREFIX = "DrawingParameters"
17 | DEFINED_TYPES_MODULE = "racksdb.drawers.dtypes"
18 |
19 | def __init__(self, schema, loader):
20 | super().__init__(self.PREFIX, schema)
21 | self._loader = loader
22 |
23 | @classmethod
24 | def load(
25 | cls,
26 | db_loader,
27 | schema: Union[str, Path, None] = None,
28 | ):
29 | # Unfortunately, default values to arguments cannot be used as they are
30 | # class attributes and the class is not defined yet at this stage at
31 | # compilation time. As an alternative, the value None is checked at
32 | # runtime and replaced by values of class attributes.
33 |
34 | if schema is None:
35 | schema = Path(cls.DEFAULT_SCHEMA)
36 | elif isinstance(schema, str):
37 | schema = Path(schema)
38 |
39 | _schema = Schema(
40 | SchemaFileLoader(schema),
41 | SchemaDefinedTypeLoader(cls.DEFINED_TYPES_MODULE),
42 | )
43 | _db = cls(_schema, db_loader)
44 | super(cls, _db).load(_db._loader)
45 | return _db
46 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/dumpers/test_dumper_console.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 |
9 | from racksdb.generic.dumpers.console import DBDumperConsole
10 | from racksdb.generic.db import DBList, DBDict
11 | from racksdb.generic.errors import DBDumperError
12 |
13 |
14 | class TestDBDumperConsole(unittest.TestCase):
15 | def test_dump(self):
16 | dumper = DBDumperConsole()
17 | # DBDumperConsole only support lists
18 | content = DBList()
19 | content.append("foo")
20 | content.append("bar")
21 | result = dumper.dump(content)
22 | self.assertEqual(result, "bar,foo")
23 |
24 | def test_dump_unsupported(self):
25 | dumper = DBDumperConsole()
26 | content = DBDict()
27 | content["foo"] = "bar"
28 | with self.assertRaisesRegex(
29 | DBDumperError,
30 | "Unsupported type '' for "
31 | "DBDumperConsole",
32 | ):
33 | dumper.dump(content)
34 |
35 | def test_dump_fold(self):
36 | dumper = DBDumperConsole()
37 | content = DBList()
38 | content.append("foo1")
39 | content.append("foo2")
40 | result = dumper.dump(content)
41 | self.assertEqual(result, "foo[1-2]")
42 |
43 | def test_dump_expand(self):
44 | dumper = DBDumperConsole(fold=False)
45 | content = DBList()
46 | content.append("foo1")
47 | content.append("foo2")
48 | result = dumper.dump(content)
49 | self.assertEqual(result, "foo1\nfoo2")
50 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "racksdb-webui",
3 | "version": "0.6.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "run-p type-check \"build-only {@}\" --",
8 | "preview": "vite preview",
9 | "test:unit": "vitest",
10 | "build-only": "vite build",
11 | "type-check": "vue-tsc --noEmit -p tsconfig.vitest.json --composite false",
12 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
13 | "format": "prettier --write src/ tests/"
14 | },
15 | "dependencies": {
16 | "@headlessui/vue": "^1.7.17",
17 | "@heroicons/vue": "^2.1.1",
18 | "axios": "^1.6.2",
19 | "leaflet": "^1.9.4",
20 | "vue": "^3.4",
21 | "vue-router": "^4.2.4"
22 | },
23 | "devDependencies": {
24 | "@rushstack/eslint-patch": "^1.3.3",
25 | "@tailwindcss/forms": "^0.5.7",
26 | "@tsconfig/node18": "^18.2.2",
27 | "@types/jsdom": "^21.1.3",
28 | "@types/leaflet": "^1.9.8",
29 | "@types/node": "^18.17.17",
30 | "@vitejs/plugin-vue": "^5.2.3",
31 | "@vue/eslint-config-prettier": "^8.0.0",
32 | "@vue/eslint-config-typescript": "^12.0.0",
33 | "@vue/test-utils": "^2.4.1",
34 | "@vue/tsconfig": "^0.4.0",
35 | "autoprefixer": "^10.4.16",
36 | "eslint": "^8.49.0",
37 | "eslint-plugin-vue": "^9.17.0",
38 | "jsdom": "^22.1.0",
39 | "npm-run-all2": "^6.0.6",
40 | "postcss": "^8.4.31",
41 | "prettier": "^3.0.3",
42 | "prettier-plugin-tailwindcss": "^0.5.5",
43 | "tailwindcss": "^3.3.3",
44 | "typescript": "~5.2.0",
45 | "vite": "^6.2.3",
46 | "vitest": "^3.0.9",
47 | "vue-router-mock": "^2.0.0",
48 | "vue-tsc": "^2.0.29"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/docs/modules/db/examples/types.yml:
--------------------------------------------------------------------------------
1 | types:
2 |
3 | # nodes
4 | nodes:
5 | - id: dense # node type for set 1
6 | height: 1u
7 | width: 1/2 # half rack width
8 | # tag::boilerplate[]
9 | model: Dense node of 1U height and half rack width
10 | cpus:
11 | sockets: 2
12 | model: Intel Xeon Silver 4316
13 | cores: 20
14 | ram:
15 | dimm: 4
16 | size: 32GB
17 | # end::boilerplate[]
18 | - id: classic # node type for set 2
19 | height: 1u
20 | width: full
21 | # tag::boilerplate[]
22 | model: Classic node of 1U height
23 | cpus:
24 | sockets: 2
25 | model: Intel Xeon Silver 4316
26 | cores: 20
27 | ram:
28 | dimm: 4
29 | size: 32GB
30 | # end::boilerplate[]
31 | - id: fat # node type for set 3
32 | height: 2u
33 | width: full
34 | # tag::boilerplate[]
35 | model: Large node of 2U height
36 | cpus:
37 | sockets: 2
38 | model: Intel Xeon Silver 4316
39 | cores: 20
40 | ram:
41 | dimm: 4
42 | size: 32GB
43 | # end::boilerplate[]
44 | - id: blade # node type for set 4
45 | height: 5u
46 | width: 1/7 # 7 blades can be installed in the width of the rack
47 | # tag::boilerplate[]
48 | model: Blade node
49 | cpus:
50 | sockets: 2
51 | model: Intel Xeon Silver 4316
52 | cores: 20
53 | ram:
54 | dimm: 4
55 | size: 32GB
56 | # end::boilerplate[]
57 |
58 | # network equipments
59 | network:
60 | - id: racksw # network equipment type for set 5
61 | height: 1u
62 | model: Rack switch
63 |
64 | # storage equipments
65 | storage:
66 | - id: nas # storage equipment type for set 6
67 | height: 2u
68 | model: 2U NAS
69 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [project]
6 | name = "RacksDB"
7 | version = "0.6.0"
8 | description = "Modelize your datacenters infrastructures in YAML database"
9 | license = {text = "MIT"}
10 | requires-python = ">=3.6"
11 | keywords = ["cmdb", "inventory", "yaml", "datacenters", "racks", "hpc", "cluster"]
12 | authors = [
13 | {name = "Rémi Palancher", email = "remi@rackslab.io"},
14 | ]
15 | dependencies = [
16 | "PyYAML",
17 | "ClusterShell",
18 | "pycairo",
19 | "PyGObject",
20 | "RFL.log >= 1.6.0",
21 | ]
22 | readme = "README.md"
23 | classifiers = [
24 | "Development Status :: 5 - Production/Stable",
25 | "Environment :: Console",
26 | "Intended Audience :: System Administrators",
27 | "Topic :: Software Development :: Libraries :: Python Modules",
28 | "Topic :: Database :: Database Engines/Servers",
29 | "Topic :: System :: Clustering",
30 | "Topic :: System :: Systems Administration",
31 | ]
32 |
33 | [project.optional-dependencies]
34 | dev = [
35 | "Flask-Cors",
36 | ]
37 | web = [
38 | "Flask",
39 | "requests-toolbelt",
40 | ]
41 | tests = [
42 | "coverage",
43 | "parameterized",
44 | "pytest",
45 | "pytest-cov",
46 | ]
47 |
48 | [project.scripts]
49 | racksdb = "racksdb.exec:RacksDBExec.run"
50 | racksdb-web = "racksdb.web.app:RacksDBWebApp.run"
51 |
52 | [tool.setuptools.packages.find]
53 | include = ["racksdb*"]
54 |
55 | [project.urls]
56 | "Homepage" = "https://github.com/rackslab/RacksDB"
57 | "Bug Tracker" = "https://github.com/rackslab/RacksDB/issues"
58 |
59 | [tool.ruff.lint]
60 | # Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
61 | select = ["E", "F"]
62 |
--------------------------------------------------------------------------------
/frontend/tests/setup.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { vi } from 'vitest'
10 | import { createRouterMock, injectRouterMock } from 'vue-router-mock'
11 | import { RouterLinkStub } from '@vue/test-utils'
12 |
13 | // JSDOM may not provide createObjectURL; mock it to prevent console errors
14 | if (typeof URL.createObjectURL !== 'function') {
15 | URL.createObjectURL = vi.fn(() => 'blob:mock-url')
16 | }
17 |
18 | if (typeof URL.revokeObjectURL !== 'function') {
19 | URL.revokeObjectURL = vi.fn()
20 | }
21 |
22 | // Mock ResizeObserver for Headless UI components
23 | global.ResizeObserver = vi.fn().mockImplementation(() => ({
24 | observe: vi.fn(),
25 | unobserve: vi.fn(),
26 | disconnect: vi.fn()
27 | }))
28 |
29 | // Create router mock with Vitest spy
30 | const router = createRouterMock({
31 | spy: {
32 | create: vi.fn,
33 | reset: vi.clearAllMocks
34 | },
35 | // Add any default routes here
36 | routes: [
37 | { path: '/', name: 'home' },
38 | { path: '/datacenters', name: 'datacenters' },
39 | { path: '/datacenters/:name', name: 'datacenterdetails' },
40 | { path: '/datacenters/:datacenterName/:datacenterRoom', name: 'datacenterroom' },
41 | { path: '/infrastructures', name: 'infrastructures' },
42 | { path: '/infrastructures/:name', name: 'infrastructuredetails' }
43 | ]
44 | })
45 |
46 | // Inject router mock globally
47 | injectRouterMock(router)
48 |
49 | // Make router available globally for tests
50 | global.routerMock = router
51 |
52 | // Configure global stubs for Vue Test Utils
53 | import { config } from '@vue/test-utils'
54 |
55 | config.global.stubs = {
56 | RouterLink: RouterLinkStub
57 | }
58 |
--------------------------------------------------------------------------------
/frontend/tests/views/InfrastructureDetailsView.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect, vi, beforeEach } from 'vitest'
10 | import { mount, flushPromises } from '@vue/test-utils'
11 | import { nextTick } from 'vue'
12 |
13 | import InfrastructureDetailsView from '@/views/InfrastructureDetailsView.vue'
14 |
15 | const infrastructuresMock = vi.fn()
16 | const infrastructureImageSvgMock = vi.fn()
17 |
18 | vi.mock('@/plugins/http', () => ({
19 | useHttp: () => ({})
20 | }))
21 |
22 | vi.mock('@/composables/RacksDBAPI', () => ({
23 | useRacksDBAPI: () => ({
24 | infrastructures: infrastructuresMock,
25 | infrastructureImageSvg: infrastructureImageSvgMock
26 | })
27 | }))
28 |
29 | describe('InfrastructureDetailsView', () => {
30 | beforeEach(() => {
31 | vi.clearAllMocks()
32 | })
33 |
34 | test('loads infrastructure details and image blob', async () => {
35 | infrastructuresMock.mockResolvedValueOnce([
36 | { name: 'core', layout: [], racks: [], dimensions: { width: 0, depth: 0 } }
37 | ])
38 | infrastructureImageSvgMock.mockResolvedValueOnce(
39 | new Blob([''], { type: 'image/svg+xml' })
40 | )
41 |
42 | const wrapper = mount(InfrastructureDetailsView, {
43 | props: { name: 'core' },
44 | global: {
45 | stubs: { BreadCrumbs: true, Dialog: true, DialogPanel: true, InfrastructureTable: true }
46 | },
47 | attachTo: document.body
48 | })
49 |
50 | await flushPromises()
51 | await nextTick()
52 |
53 | expect(infrastructuresMock).toHaveBeenCalled()
54 | expect(infrastructureImageSvgMock).toHaveBeenCalledWith('core')
55 | })
56 | })
57 |
--------------------------------------------------------------------------------
/examples/db/datacenters/paris.yml:
--------------------------------------------------------------------------------
1 | tags:
2 | - freecooling
3 | - tier2
4 | rooms:
5 | - name: noisy
6 | dimensions:
7 | width: 14m
8 | depth: 25m
9 | rows:
10 | - name: R1
11 | position:
12 | width: 3m
13 | depth: 4m
14 | racks:
15 | - name: R1-A[01-10]
16 | type: standard
17 | tags:
18 | - first
19 | - name: R2
20 | reversed: true
21 | position:
22 | width: 3m
23 | depth: 6m
24 | racks:
25 | - name: R2-A[01-02]
26 | type: half
27 | - name: R2-A[03-10]
28 | type: standard
29 | slot: 2
30 | - name: R3
31 | position:
32 | width: 3m
33 | depth: 9m
34 | racks:
35 | - name: R3-A[01-05]
36 | type: standard
37 | slot: 2
38 | - name: R3-A[07-10]
39 | type: standard
40 | slot: 9
41 | - name: R4
42 | reversed: True
43 | position:
44 | width: 3m
45 | depth: 12m
46 | rotation: 90
47 | racks:
48 | - name: R4-A[01-06]
49 | type: standard
50 | - name: R5
51 | position:
52 | width: 5m
53 | depth: 12m
54 | rotation: 90
55 | racks:
56 | - name: R5-A[01-06]
57 | type: standard
58 | - name: R6
59 | position:
60 | width: 10m
61 | depth: 15m
62 | rotation: 60
63 | racks:
64 | - name: R6-A[01-06]
65 | type: standard
66 | - name: R7
67 | position:
68 | width: 11.8m
69 | depth: 20m
70 | rotation: 120
71 | racks:
72 | - name: R7-A[01-06]
73 | type: standard
74 | tags:
75 | - last
76 | location:
77 | longitude: 2.294694
78 | latitude: 48.858093
79 |
--------------------------------------------------------------------------------
/examples/simple/racksdb.yml:
--------------------------------------------------------------------------------
1 | types:
2 | nodes:
3 | - id: sm220bt
4 | model: SuperMicro A+ 2124BT-HTR
5 | height: 1u
6 | width: 1/2
7 | cpu:
8 | cores: 32
9 | model: AMD EPYC 7573X
10 | sockets: 2
11 | ram:
12 | dimm: 8
13 | size: 32GB
14 | storage:
15 | - model: Samsung 980 Pro
16 | size: 256GB
17 | type: nvme
18 | netifs:
19 | - bandwidth: 10Gb
20 | type: ethernet
21 | network:
22 | - id: cisco3650
23 | model: Cisco Catalyst 3650 switch
24 | height: 1u
25 | netifs:
26 | - bandwidth: 1Gb
27 | number: 48
28 | type: ethernet
29 | storage:
30 | - id: qnaph1277
31 | model: QNAP TS-H1277XU-RP
32 | height: 2u
33 | disks:
34 | - model: Seagate IronWolf
35 | number: 12
36 | size: 4TB
37 | type: disk
38 | racks:
39 | - id: standard
40 | height: 1867mm
41 | width: 600mm
42 | depth: 914mm
43 | slots: 42u
44 |
45 | datacenters:
46 | - name: paris
47 | rooms:
48 | - name: atlas
49 | dimensions:
50 | depth: 4m
51 | width: 8m
52 | rows:
53 | - name: R1
54 | position:
55 | depth: 1.5m
56 | width: 1.5m
57 | racks:
58 | - name: R1-A[01-10]
59 | type: standard
60 |
61 | infrastructures:
62 | - name: tiger
63 | description: HPC cluster
64 | layout:
65 | - rack: R1-A01
66 | tags:
67 | - compute
68 | nodes:
69 | - name: ticn[0001-0040]
70 | slot: 1
71 | type: sm220bt
72 | - rack: R1-A02
73 | tags:
74 | - compute
75 | network:
76 | - type: cisco3650
77 | name: tisw[01-02]
78 | slot: 10
79 | tags:
80 | - network
81 | storage:
82 | - name: tinas01
83 | slot: 14
84 | tags:
85 | - storage
86 | type: qnaph1277
87 |
--------------------------------------------------------------------------------
/racksdb/tests/lib/common.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import os
8 | from pathlib import Path
9 |
10 | CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
11 |
12 |
13 | def _first_path(paths, error):
14 | for path in paths:
15 | if path.exists():
16 | return path
17 | raise FileNotFoundError(error)
18 |
19 |
20 | def drawing_schema_path():
21 | return _first_path(
22 | [
23 | Path(CURRENT_DIR).joinpath("../../../schemas/drawings.yml"),
24 | Path("schemas/drawings.yml"),
25 | ],
26 | "Unable to find drawing schema to run tests",
27 | )
28 |
29 |
30 | def schema_path():
31 | return _first_path(
32 | [
33 | Path(CURRENT_DIR).joinpath("../../../schemas/racksdb.yml"),
34 | Path("schemas/racksdb.yml"),
35 | ],
36 | "Unable to find schema to run tests",
37 | )
38 |
39 |
40 | def db_path():
41 | return _first_path(
42 | [Path(CURRENT_DIR).joinpath("../../../examples/db"), Path("examples/db")],
43 | "Unable to find database to run tests",
44 | )
45 |
46 |
47 | def db_one_file_path():
48 | return _first_path(
49 | [
50 | Path(CURRENT_DIR).joinpath("../../../examples/simple/racksdb.yml"),
51 | Path("examples/simple/racksdb.yml"),
52 | ],
53 | "Unable to find one file database to run tests",
54 | )
55 |
56 |
57 | def ui_path():
58 | # This path does not contain the full UI application but enough files to
59 | # test.
60 | return _first_path(
61 | [
62 | Path(CURRENT_DIR).joinpath("../../../frontend/public"),
63 | Path("frontend/public"),
64 | ],
65 | "Unable to find UI public directory to run tests",
66 | )
67 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/test_schema_file_loader.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 | import tempfile
9 | from pathlib import Path
10 |
11 | import yaml
12 |
13 | from racksdb.generic.schema import SchemaFileLoader
14 | from racksdb.generic.errors import DBSchemaError
15 |
16 | from .lib.common import VALID_SCHEMA, VALID_EXTENSIONS
17 |
18 |
19 | class TestSchemaFileLoader(unittest.TestCase):
20 | def test_ok(self):
21 | with tempfile.NamedTemporaryFile() as tmpfile:
22 | with open(tmpfile.name, "w+") as fh:
23 | fh.write(yaml.dump(VALID_SCHEMA))
24 | loader = SchemaFileLoader(Path(tmpfile.name))
25 | self.assertIsInstance(loader.content, dict)
26 |
27 | def test_file_not_found(self):
28 | loader = SchemaFileLoader(Path("/dev/fail"))
29 | with self.assertRaisesRegex(
30 | DBSchemaError, "Schema path /dev/fail does not exist"
31 | ):
32 | _ = loader.content
33 |
34 | def test_extensions(self):
35 | with tempfile.TemporaryDirectory() as tmpdir:
36 | schema_path = Path(tmpdir) / "schema.yaml"
37 | ext_path = Path(tmpdir) / "extensions.yaml"
38 | with open(schema_path, "w+") as fh:
39 | fh.write(yaml.dump(VALID_SCHEMA))
40 | with open(ext_path, "w+") as fh:
41 | fh.write(yaml.dump(VALID_EXTENSIONS))
42 | loader = SchemaFileLoader(schema_path, ext_path)
43 | content = loader.content
44 | self.assertIsInstance(content, dict)
45 | self.assertIn("plums", content["_content"]["properties"].keys())
46 | self.assertIn("juiciness", content["_objects"]["Pear"]["properties"].keys())
47 | self.assertIn("Plum", content["_objects"].keys())
48 |
--------------------------------------------------------------------------------
/frontend/tests/components/ComboBox.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect, vi, beforeEach } from 'vitest'
10 | import { mount } from '@vue/test-utils'
11 | import { nextTick } from 'vue'
12 | import { getRouter } from 'vue-router-mock'
13 |
14 | import ComboBox from '@/components/ComboBox.vue'
15 |
16 | describe('ComboBox', () => {
17 | test('renders with datacenter items and filters correctly', async () => {
18 | const items = [
19 | { name: 'paris', rooms: [] },
20 | { name: 'london', rooms: [] }
21 | ]
22 |
23 | const wrapper = mount(ComboBox, {
24 | props: {
25 | itemType: 'datacenter',
26 | items
27 | }
28 | })
29 |
30 | expect(wrapper.text()).toContain('Select a datacenter')
31 | })
32 |
33 | test('renders with infrastructure items', () => {
34 | const items = [
35 | { name: 'core', description: 'Core infrastructure' },
36 | { name: 'edge', description: 'Edge infrastructure' }
37 | ]
38 |
39 | const wrapper = mount(ComboBox, {
40 | props: {
41 | itemType: 'infrastructure',
42 | items
43 | }
44 | })
45 |
46 | expect(wrapper.text()).toContain('Select an infrastructure')
47 | })
48 |
49 | test('navigates to correct route when item is selected', async () => {
50 | const router = getRouter()
51 | const items = [{ name: 'paris', rooms: [] }]
52 |
53 | const wrapper = mount(ComboBox, {
54 | props: {
55 | itemType: 'datacenter',
56 | items
57 | }
58 | })
59 |
60 | // Simulate item selection
61 | wrapper.vm.goToItem('paris')
62 | await nextTick()
63 |
64 | expect(router.push).toHaveBeenCalledWith({
65 | name: 'datacenterdetails',
66 | params: { name: 'paris' }
67 | })
68 | })
69 | })
70 |
--------------------------------------------------------------------------------
/examples/db/datacenters/london.yml:
--------------------------------------------------------------------------------
1 | tags:
2 | - freecooling
3 | - tier3
4 | rooms:
5 | - name: level1
6 | dimensions:
7 | width: 15m
8 | depth: 8m
9 | rows:
10 | - name: A
11 | reversed: true
12 | position:
13 | width: 3m
14 | depth: 2m
15 | rotation: 90
16 | racks:
17 | - name: rackA[01-06]
18 | type: standard
19 | - name: B
20 | position:
21 | width: 6m
22 | depth: 2m
23 | rotation: 90
24 | racks:
25 | - name: rackB[01-06]
26 | type: standard
27 | - name: C
28 | reversed: true
29 | position:
30 | width: 10m
31 | depth: 2m
32 | rotation: 90
33 | racks:
34 | - name: rackC[01-06]
35 | type: standard
36 | - name: D
37 | position:
38 | width: 13m
39 | depth: 2m
40 | rotation: 90
41 | racks:
42 | - name: rackD[01-06]
43 | type: standard
44 | - name: level2
45 | dimensions:
46 | width: 15m
47 | depth: 8m
48 | rows:
49 | - name: E
50 | reversed: true
51 | position:
52 | width: 3m
53 | depth: 2m
54 | rotation: 90
55 | racks:
56 | - name: rackE[01-06]
57 | type: standard
58 | - name: F
59 | position:
60 | width: 6m
61 | depth: 2m
62 | rotation: 90
63 | racks:
64 | - name: rackF[01-06]
65 | type: standard
66 | - name: G
67 | reversed: true
68 | position:
69 | width: 10m
70 | depth: 2m
71 | rotation: 90
72 | racks:
73 | - name: rackG[01-06]
74 | type: standard
75 | - name: H
76 | position:
77 | width: 13m
78 | depth: 2m
79 | rotation: 90
80 | racks:
81 | - name: rackH[01-06]
82 | type: standard
83 | location:
84 | longitude: -0.075278
85 | latitude: 51.505554
86 |
--------------------------------------------------------------------------------
/frontend/src/router/index.ts:
--------------------------------------------------------------------------------
1 | /*Copyright (c) 2022-2023 Rackslab
2 |
3 | This file is part of RacksDB.
4 |
5 | SPDX-License-Identifier: MIT */
6 |
7 | import { createRouter, createWebHistory } from 'vue-router'
8 | import HomeView from '../views/HomeView.vue'
9 | import DatacentersView from '../views/DatacentersView.vue'
10 | import DatacenterDetailsView from '../views/DatacenterDetailsView.vue'
11 | import DatacenterRoomView from '../views/DatacenterRoomView.vue'
12 | import InfrastructuresView from '../views/InfrastructuresView.vue'
13 | import InfrastructureDetailsView from '../views/InfrastructureDetailsView.vue'
14 |
15 | const router = createRouter({
16 | history: createWebHistory(import.meta.env.BASE_URL),
17 | routes: [
18 | {
19 | path: '/',
20 | name: 'home',
21 | component: HomeView,
22 | meta: { entry: 'home' }
23 | },
24 |
25 | {
26 | path: '/datacenters',
27 | name: 'datacenters',
28 | component: DatacentersView,
29 | meta: { entry: 'datacenters' }
30 | },
31 |
32 | {
33 | path: '/datacenters/:name',
34 | name: 'datacenterdetails',
35 | component: DatacenterDetailsView,
36 | props: true,
37 | meta: { entry: 'datacenters' }
38 | },
39 |
40 | {
41 | path: '/datacenters/:datacenterName/:datacenterRoom',
42 | name: 'datacenterroom',
43 | component: DatacenterRoomView,
44 | props: true,
45 | meta: { entry: 'datacenters' }
46 | },
47 |
48 | {
49 | path: '/infrastructures',
50 | name: 'infrastructures',
51 | component: InfrastructuresView,
52 | meta: { entry: 'infrastructures' }
53 | },
54 |
55 | {
56 | path: '/infrastructures/:name',
57 | name: 'infrastructuredetails',
58 | component: InfrastructureDetailsView,
59 | props: true,
60 | meta: { entry: 'infrastructures' }
61 | }
62 | ]
63 | })
64 |
65 | export default router
66 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/dumpers/test_dumper_json.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 | import json
9 |
10 | from racksdb.generic.dumpers.json import DBDumperJSON
11 |
12 | from ..lib.common import valid_db
13 |
14 |
15 | class TestDBDumperJSON(unittest.TestCase):
16 | def test_dump_list(self):
17 | db = valid_db()
18 | dumper = DBDumperJSON()
19 | result = dumper.dump(db.apples)
20 | json.loads(result)
21 |
22 | def test_dump_folded(self):
23 | db = valid_db()
24 | dumper = DBDumperJSON()
25 | result = dumper.dump(db.stock)
26 | stock = json.loads(result)
27 | self.assertEqual(len(stock["content"]), 2)
28 |
29 | def test_dump_expanded(self):
30 | db = valid_db()
31 | dumper = DBDumperJSON(fold=False)
32 | result = dumper.dump(db.stock)
33 | stock = json.loads(result)
34 | self.assertEqual(len(stock["content"]), 20)
35 |
36 | def test_dump_recursion(self):
37 | db = valid_db()
38 | dumper = DBDumperJSON()
39 | # Check recursion error is raised
40 | with self.assertRaisesRegex(ValueError, "Circular reference detected"):
41 | dumper.dump(db.bananas)
42 |
43 | def test_dump_map_attribute(self):
44 | db = valid_db()
45 | dumper = DBDumperJSON(objects_map={"TestBananaOrigin": "origin"})
46 | result = dumper.dump(db.bananas)
47 | bananas = json.loads(result)
48 | self.assertEqual(bananas[0]["species"][0]["origin"], bananas[0]["origin"])
49 |
50 | def test_dump_map_none(self):
51 | db = valid_db()
52 | dumper = DBDumperJSON(objects_map={"TestBananaOrigin": None})
53 | result = dumper.dump(db.bananas)
54 | bananas = json.loads(result)
55 | self.assertNotIn("origin", bananas[0]["species"][0])
56 |
--------------------------------------------------------------------------------
/frontend/tests/views/DatacenterDetailsView.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect, vi, beforeEach } from 'vitest'
10 | import { mount } from '@vue/test-utils'
11 | import { nextTick } from 'vue'
12 |
13 | import DatacenterDetailsView from '@/views/DatacenterDetailsView.vue'
14 |
15 | const datacentersMock = vi.fn()
16 |
17 | vi.mock('@/plugins/http', () => ({
18 | useHttp: () => ({})
19 | }))
20 |
21 | vi.mock('@/composables/RacksDBAPI', () => ({
22 | useRacksDBAPI: () => ({
23 | datacenters: datacentersMock
24 | })
25 | }))
26 |
27 | describe('DatacenterDetailsView', () => {
28 | beforeEach(() => {
29 | vi.clearAllMocks()
30 | })
31 |
32 | const flushPromises = () => new Promise((r) => setTimeout(r))
33 |
34 | test('loads datacenter details and filters rooms by input', async () => {
35 | datacentersMock.mockResolvedValueOnce([
36 | {
37 | name: 'paris',
38 | rooms: [
39 | { name: 'alpha', dimensions: { width: 1000, depth: 2000 }, rows: [{ nbracks: 2 }] },
40 | { name: 'beta', dimensions: { width: 1000, depth: 1000 }, rows: [{ nbracks: 1 }] }
41 | ]
42 | }
43 | ])
44 |
45 | const wrapper = mount(DatacenterDetailsView, {
46 | props: { name: 'paris' },
47 | global: {
48 | stubs: {
49 | BreadCrumbs: true
50 | }
51 | }
52 | })
53 |
54 | await flushPromises()
55 | await nextTick()
56 |
57 | // Ensure two rooms initially
58 | let rows = wrapper.findAll('tbody tr')
59 | expect(rows.length).toBeGreaterThan(0)
60 |
61 | // Filter
62 | const input = wrapper.find('input[type="text"]')
63 | await input.setValue('alpha')
64 | await nextTick()
65 |
66 | rows = wrapper.findAll('tbody tr')
67 | expect(rows.length).toBeGreaterThan(0)
68 | })
69 | })
70 |
--------------------------------------------------------------------------------
/frontend/tests/components/BreadCrumbs.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect } from 'vitest'
10 | import { mount } from '@vue/test-utils'
11 |
12 | import { getRouter } from 'vue-router-mock'
13 |
14 | import BreadCrumbs from '@/components/BreadCrumbs.vue'
15 |
16 | describe('BreadCrumbs', () => {
17 | test('renders logo and datacenter breadcrumb when route meta entry is datacenters', () => {
18 | const router = getRouter()
19 | router.currentRoute.value = {
20 | meta: { entry: 'datacenters' },
21 | name: 'datacenterdetails'
22 | }
23 |
24 | const wrapper = mount(BreadCrumbs, {
25 | props: {
26 | datacenterName: 'paris'
27 | }
28 | })
29 |
30 | expect(wrapper.find('img[alt="Rackslab logo"]').exists()).toBe(true)
31 | expect(wrapper.text()).toContain('Datacenters')
32 | expect(wrapper.text()).toContain('paris')
33 | })
34 |
35 | test('renders infrastructure breadcrumb when route meta entry is infrastructures', () => {
36 | const router = getRouter()
37 | router.currentRoute.value = {
38 | meta: { entry: 'infrastructures' },
39 | name: 'infrastructuredetails'
40 | }
41 |
42 | const wrapper = mount(BreadCrumbs, {
43 | props: {
44 | infrastructureName: 'core'
45 | }
46 | })
47 |
48 | expect(wrapper.text()).toContain('Infrastructures')
49 | expect(wrapper.text()).toContain('core')
50 | })
51 |
52 | test('renders room breadcrumb when route name is datacenterroom', () => {
53 | const router = getRouter()
54 | router.currentRoute.value = {
55 | meta: { entry: 'datacenters' },
56 | name: 'datacenterroom'
57 | }
58 |
59 | const wrapper = mount(BreadCrumbs, {
60 | props: {
61 | datacenterName: 'paris',
62 | datacenterRoom: 'room1'
63 | }
64 | })
65 |
66 | expect(wrapper.text()).toContain('paris')
67 | expect(wrapper.text()).toContain('room1')
68 | })
69 | })
70 |
--------------------------------------------------------------------------------
/docs/modules/db/partials/deftypes.adoc:
--------------------------------------------------------------------------------
1 | [#deftype]
2 | == Defined Types
3 |
4 | :url-deftypes: https://github.com/rackslab/racksdb/blob/main/racksdb/dtypes
5 |
6 | [cols="2a,2a,1l,3a"]
7 | |===
8 | |Name|Examples|Resulting type|Comment
9 |
10 | |[#deftype-angle]`~angle`
11 | {url-deftypes}/angle.py[icon:code[]]
12 | |* `90`
13 | * `120`
14 | |int
15 | |The resulting value is angle in *degrees*. RacksDB does not accept values below
16 | 0 or above 360.
17 |
18 | |[#deftype-bits]`~bits`
19 | {url-deftypes}/bits.py[icon:code[]]
20 | |* `1.2Gb`
21 | * `3Mb`
22 | |int
23 | |The resulting value is the *number of bits*.
24 |
25 | |[#deftype-bytes]`~bytes`
26 | {url-deftypes}/bytes.py[icon:code[]]
27 | |* `32GB`
28 | * `8TB`
29 | |int
30 | |The resulting value is the *number of bytes*.
31 |
32 | |[#deftype-dimension]`~dimension`
33 | {url-deftypes}/dimension.py[icon:code[]]
34 | |* `23cm`
35 | * `1.56m`
36 | * `1900mm`
37 | |int
38 | |The resulting value is the *number of millimeters*.
39 |
40 | |[#deftype-netif_type]`~netif_type`
41 | {url-deftypes}/netif_type.py[icon:code[]]
42 | |* `ethernet`
43 | * `infiniband`
44 | |str
45 | |The resulting value is the *name of network interface type*.
46 |
47 | |[#deftype-rack_height]`~rack_height`
48 | {url-deftypes}/rack_height.py[icon:code[]]
49 | |* `1u`
50 | * `2u`
51 | |int
52 | |The resulting value is the *number of rack units* (aka. U).
53 |
54 | |[#deftype-rack_width]`~rack_width`
55 | {url-deftypes}/rack_width.py[icon:code[]]
56 | |* `full`
57 | * `1/2`
58 | * `1/8`
59 | * `1`
60 | |float
61 | |The resulting value is *the normalized part of the rack width* (_ex:_ 1.0 is
62 | 100% of the rack width, 0.5 is 50% of the rack width). The special value `full`
63 | is equals to 1.
64 |
65 | |[#deftype-storage_type]`~storage_type`
66 | {url-deftypes}/storage_type.py[icon:code[]]
67 | |* `disk`
68 | * `ssd`
69 | * `nvme`
70 | |str
71 | |The resulting value is the *name of storage device type*.
72 |
73 | |[#deftype-watts]`~watts`
74 | {url-deftypes}/watts.py[icon:code[]]
75 | |* `800W`
76 | * `1.5KW`
77 | |int
78 | |The resulting value is the *number of watts*.
79 | |===
80 |
--------------------------------------------------------------------------------
/docs/modules/db/partials/prop-types.adoc:
--------------------------------------------------------------------------------
1 | [#object]
2 | == Objects Definitions
3 |
4 | Objects definitions are mappings with the following expected keys:
5 |
6 | `description` _(optional)_:: String to describe the purpose of the object.
7 |
8 | `properties`:: Mapping of objects properties. The keys are the names of the
9 | properties. The values are mapping with the following expected keys:
10 |
11 | `type`::: The xref:#type[property type], as described below.
12 |
13 | `description`::: String to describe the purpose and content of the property.
14 |
15 | `key` _(optional)_::: Boolean to determine if the property is a
16 | xref:concepts.adoc#key[key]. Default value is _false_.
17 |
18 | `default` _(optional)_::: Default value of the property. When the `type` is an
19 | xref:concepts.adoc#object[object], the special value `:recursive` can be used to
20 | make the default value of the property the corresponding object with all its
21 | defaults properties, recursively.
22 |
23 | `example` _(optional)_::: Example value of the property.
24 |
25 | `optional` _(optional)_::: Boolean to determine if property is optional.
26 | Default value is _false_.
27 |
28 | `computed` _(optional)_::: Boolean to determine if property is
29 | xref:concepts.adoc#computed[automatically computed]. Default value is _false_.
30 |
31 | [#type]
32 | == Properties Type
33 |
34 | The xref:concepts.adoc#types[properties types] are expressed with the following
35 | notation:
36 |
37 | * xref:concepts.adoc#native[native types] are expressed by their name +
38 | _ex:_ _str_ or _int_
39 | * xref:concepts.adoc#object [objects] are prefixed by `:` +
40 | _ex:_ `:DatacenterRoomDimensions`
41 | * xref:concepts.adoc#sequence[sequences] (or lists) are noted `list[]` +
42 | _ex:_ `list[str]` or `list[:Infrastructure]`
43 | * xref:concepts.adoc#reference[reference to other objects properties] are
44 | prefixed by `$` +
45 | _ex:_ `$NodeType.id`
46 | * xref:concepts.adoc#backref[back references] to a parent objects or one of
47 | their properties are prefixed by `^` +
48 | _ex:_ `^Datacenter` or `^InfrastructurePart.rack`
49 | * xref:structure.adoc#deftypes[defined types] are prefixed by `~` +
50 | _ex:_ `~dimension`
51 |
--------------------------------------------------------------------------------
/docs/utils/schema-objs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | #
3 | # Copyright (C) 2022 Rackslab
4 | #
5 | # This file is part of RacksDB.
6 | #
7 | # RacksDB is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # RacksDB is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with RacksDB. If not, see .
19 |
20 | """
21 | To generate partial of database objects reference documentation, run this
22 | command:
23 |
24 | $ python3 docs/utils/schema-objs.py > docs/modules/db/partials/objects.adoc
25 | """
26 |
27 | from pathlib import Path
28 |
29 | import jinja2
30 |
31 | from racksdb import RacksDB
32 | from racksdb.generic.schema import SchemaBackReference
33 |
34 |
35 | def bases(obj):
36 | """Jinja2 Filter to list of parent classes names of an object."""
37 | return [_class.__name__ for _class in obj.__class__.__bases__]
38 |
39 |
40 | def main():
41 | # Load the schema, the DB does not matter here.
42 | db = RacksDB.load(
43 | schema=Path("../schemas/racksdb.yml"),
44 | db=Path("../examples/simple/racksdb.yml"),
45 | )
46 |
47 | # Enrich schema object with has_backref boolean
48 | for obj in db._schema.objects.values():
49 | obj.has_backref = False
50 | for prop in obj.properties:
51 | if isinstance(prop.type, SchemaBackReference):
52 | obj.has_backref = True
53 |
54 | # Render template
55 | env = jinja2.Environment(loader=jinja2.FileSystemLoader("utils"))
56 | env.filters["bases"] = bases
57 | template = env.get_template("schema-objs.adoc.j2")
58 | output = template.render(
59 | schema=db._schema,
60 | object_prefix="RacksDB",
61 | deftype_prefix="racksdb.dtypes.",
62 | )
63 | print(output)
64 |
65 |
66 | if __name__ == "__main__":
67 | main()
68 |
--------------------------------------------------------------------------------
/racksdb/tests/drawers/test_parameters.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 | import yaml
9 | from pathlib import Path
10 | import tempfile
11 |
12 | from racksdb.generic.db import DBDictsLoader, DBSplittedFilesLoader
13 | from racksdb.drawers.parameters import DrawingParameters
14 | from racksdb.generic.errors import DBFormatError
15 | from ..lib.common import drawing_schema_path
16 |
17 |
18 | class TestRacksDBDrawingParameters(unittest.TestCase):
19 | def test_load(self):
20 | DrawingParameters.load(DBDictsLoader(), drawing_schema_path())
21 |
22 | def test_load_ext_yaml(self):
23 | with tempfile.TemporaryDirectory() as tmpdir:
24 | drawing_parameters_db_path = Path(tmpdir) / "params.yaml"
25 | with open(drawing_parameters_db_path, "w+") as fh:
26 | fh.write(yaml.dump({"margin": {"left": 10, "top": 10}}))
27 | DrawingParameters.load(
28 | DBSplittedFilesLoader(drawing_parameters_db_path), drawing_schema_path()
29 | )
30 |
31 | def test_load_ext_yml(self):
32 | with tempfile.TemporaryDirectory() as tmpdir:
33 | drawing_parameters_db_path = Path(tmpdir) / "params.yml"
34 | with open(drawing_parameters_db_path, "w+") as fh:
35 | fh.write(yaml.dump({"margin": {"left": 10, "top": 10}}))
36 | DrawingParameters.load(
37 | DBSplittedFilesLoader(drawing_parameters_db_path), drawing_schema_path()
38 | )
39 |
40 | def test_load_ext_fail(self):
41 | with tempfile.TemporaryDirectory() as tmpdir:
42 | drawing_parameters_db_path = Path(tmpdir) / "params.fail"
43 | with open(drawing_parameters_db_path, "w+") as fh:
44 | fh.write(yaml.dump({"margin": {"left": 10, "top": 10}}))
45 | # FIXME: should raise RacksDBFormatError
46 | with self.assertRaisesRegex(
47 | DBFormatError,
48 | r"^DB contains file \/.*\/params\.fail without valid extensions: .*$",
49 | ):
50 | DrawingParameters.load(
51 | DBSplittedFilesLoader(drawing_parameters_db_path),
52 | drawing_schema_path(),
53 | )
54 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/dumpers/test_dumper_yaml.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 |
9 | import yaml
10 |
11 | from racksdb.generic.dumpers.yaml import DBDumperYAML, SchemaDumperYAML
12 |
13 | from ..lib.common import valid_db, valid_schema
14 |
15 |
16 | class TestDBDumperYAML(unittest.TestCase):
17 | def test_dump_list(self):
18 | db = valid_db()
19 | dumper = DBDumperYAML()
20 | result = dumper.dump(db.apples)
21 | yaml.safe_load(result)
22 |
23 | def test_dump_show_types(self):
24 | db = valid_db()
25 | dumper = DBDumperYAML(show_types=True)
26 | dumper.dump(db.apples)
27 |
28 | def test_dump_folded(self):
29 | db = valid_db()
30 | dumper = DBDumperYAML()
31 | result = dumper.dump(db.stock)
32 | stock = yaml.safe_load(result)
33 | self.assertEqual(len(stock["content"]), 2)
34 |
35 | def test_dump_expanded(self):
36 | db = valid_db()
37 | dumper = DBDumperYAML(fold=False)
38 | result = dumper.dump(db.stock)
39 | stock = yaml.safe_load(result)
40 | self.assertEqual(len(stock["content"]), 20)
41 |
42 | def test_dump_recursion(self):
43 | db = valid_db()
44 | dumper = DBDumperYAML()
45 | with self.assertLogs("racksdb", "ERROR") as cm:
46 | result = dumper.dump(db.bananas)
47 | # Check one error has been logged and check its value.
48 | self.assertEqual(len(cm.output), 1)
49 | self.assertIn(
50 | "ERROR:racksdb.generic.dumpers.yaml:Recursion loop detected during dump, "
51 | "last represented objects:",
52 | cm.output[0],
53 | )
54 | self.assertEqual(result, "")
55 |
56 | def test_dump_map_attribute(self):
57 | db = valid_db()
58 | dumper = DBDumperYAML(objects_map={"TestBananaOrigin": "origin"})
59 | result = dumper.dump(db.bananas)
60 | bananas = yaml.safe_load(result)
61 | self.assertEqual(bananas[0]["species"][0]["origin"], bananas[0]["origin"])
62 |
63 | def test_dump_map_none(self):
64 | db = valid_db()
65 | dumper = DBDumperYAML(objects_map={"TestBananaOrigin": None})
66 | result = dumper.dump(db.bananas)
67 | bananas = yaml.safe_load(result)
68 | self.assertNotIn("origin", bananas[0]["species"][0])
69 |
70 |
71 | class TestSchemaDumperYAML(unittest.TestCase):
72 | def test_dump(self):
73 | result = SchemaDumperYAML().dump(valid_schema())
74 | yaml.safe_load(result)
75 |
--------------------------------------------------------------------------------
/docs/utils/drawings-schema-objs.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | #
3 | # Copyright (C) 2022 Rackslab
4 | #
5 | # This file is part of RacksDB.
6 | #
7 | # RacksDB is free software: you can redistribute it and/or modify
8 | # it under the terms of the GNU General Public License as published by
9 | # the Free Software Foundation, either version 3 of the License, or
10 | # (at your option) any later version.
11 | #
12 | # RacksDB is distributed in the hope that it will be useful,
13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | # GNU General Public License for more details.
16 | #
17 | # You should have received a copy of the GNU General Public License
18 | # along with RacksDB. If not, see .
19 |
20 | """
21 | To generate partial of drawing parameters database objects reference documentation,
22 | run this command:
23 |
24 | $ python3 docs/utils/drawings-schema-objs.py > \
25 | modules/usage/partials/drawing-objects.adoc
26 | """
27 |
28 | from pathlib import Path
29 |
30 | import jinja2
31 | import yaml
32 |
33 | from racksdb.drawers.parameters import DrawingParameters
34 | from racksdb.generic.schema import (
35 | Schema,
36 | SchemaFileLoader,
37 | SchemaDefinedTypeLoader,
38 | SchemaBackReference,
39 | )
40 |
41 |
42 | def bases(obj):
43 | """Jinja2 Filter to list of parent classes names of an object."""
44 | return [_class.__name__ for _class in obj.__class__.__bases__]
45 |
46 |
47 | def toyaml(stuff):
48 | return yaml.dump(stuff).rstrip()
49 |
50 |
51 | def main():
52 | # Load the schema, the DB does not matter here.
53 | drawing_schema = Schema(
54 | SchemaFileLoader(Path("../schemas/drawings.yml")),
55 | SchemaDefinedTypeLoader(DrawingParameters.DEFINED_TYPES_MODULE),
56 | )
57 |
58 | # Enrich schema object with has_backref boolean
59 | for obj in drawing_schema.objects.values():
60 | obj.has_backref = False
61 | for prop in obj.properties:
62 | if isinstance(prop.type, SchemaBackReference):
63 | obj.has_backref = True
64 |
65 | # Render template
66 | env = jinja2.Environment(loader=jinja2.FileSystemLoader("utils"))
67 | env.filters["bases"] = bases
68 | env.filters["toyaml"] = toyaml
69 | template = env.get_template("schema-objs.adoc.j2")
70 | output = template.render(
71 | schema=drawing_schema,
72 | object_prefix="Drawings",
73 | deftype_prefix="racksdb.drawers.dtypes.",
74 | )
75 | print(output)
76 |
77 |
78 | if __name__ == "__main__":
79 | main()
80 |
--------------------------------------------------------------------------------
/docs/modules/usage/pages/racksdb-web.adoc:
--------------------------------------------------------------------------------
1 | ifeval::["{backend}" != "manpage"]
2 | = `racksdb-web` CLI manpage
3 |
4 | This section explains all `racksdb-web` command options available. The content
5 | of the page this also available in `racksdb-web(1)` manpage after installation.
6 |
7 | endif::[]
8 |
9 | :!example-caption:
10 |
11 | == Synopsis
12 |
13 | [.cli-opt]#*racksdb-web*# `[_OPTIONS_]`
14 |
15 | == Options
16 |
17 | [.cli-opt]#*-h, --help*#::
18 | Show this help message and exit.
19 |
20 | [.cli-opt]#*-v, --version*#::
21 | Show RacksDB version number and exit.
22 |
23 | [.cli-opt]#*--debug*#::
24 | Enable debug mode with more messages in output.
25 |
26 | [.cli-opt]#*-b, --db*=#[.cli-optval]##_DB_##::
27 | Path to database. Both files and directories paths are accepted. If the path
28 | is a directory, all YAML files in this directory are loaded, recursively. If
29 | the path does not exist, an error is reported. Default value is
30 | [.path]#`/var/lib/racksdb/`# directory.
31 |
32 | [.cli-opt]#*-s, --schema*=#[.cli-optval]##_SCHEMA_##::
33 | Path to RacksDB schema YAML file. If the file does not exist, an error is
34 | reported. Default value is [.path]#`/usr/share/racksdb/schemas/racksdb.yml`#.
35 |
36 | [.cli-opt]#*-e, --ext*=#[.cli-optval]##_EXT_##::
37 | Path to optional RacksDB schema extensions. If the file does not exist,
38 | it is silently ignored by RacksDB. Default value is
39 | [.path]#`/etc/racksdb/extensions.yml`#.
40 |
41 | [.cli-opt]#*--host*=#[.cli-optval]##_HOST_##::
42 | The hostname to listen for incoming requests. Set to `0.0.0.0` to listen on
43 | all IP addresses of the host. Default value is `localhost` which restricts
44 | connections to local host.
45 |
46 | [.cli-opt]#*-p, --port*=#[.cli-optval]##_PORT_##::
47 | TCP port to listen for incoming requests. Default value is 5000.
48 |
49 | [.cli-opt]#*--drawings-schema*=#[.cli-optval]##_DRAWINGS_SCHEMA_##::
50 | Path to drawing parameters schema YAML file. Default value is
51 | [.path]#`/usr/share/racksdb/drawings.yml`#.
52 |
53 | [.cli-opt]#*--cors*#::
54 | Enable CORS headers.
55 |
56 | [.cli-opt]#*--openapi*#::
57 | Enable OpenAPI endpoint to retrieve REST API specifications.
58 |
59 | [.cli-opt]#*--with-ui*#[.cli-optval]##[=_PATH_]##::
60 | Enable web UI. The web UI is disabled by default. When the option is used
61 | without value, default UI path [.path]#`/usr/share/racksdb/frontend`# is used.
62 | When a path is provided, `racksdb-web` loads UI files from this path.
63 |
64 | == Exit status
65 |
66 | *0*::
67 | `racksdb-web` has processed command with success.
68 |
69 | *1*::
70 | `racksdb-web` encountered an error.
71 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/lib/views.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import typing as t
8 |
9 | from racksdb.generic.views import (
10 | DBViewSet,
11 | DBView,
12 | DBViewParameter,
13 | DBViewFilter,
14 | DBAction,
15 | DBActionParameter,
16 | DBActionResponse,
17 | DBActionError,
18 | )
19 |
20 |
21 | class TestDBViews(DBViewSet):
22 | VIEWS = [
23 | DBView(
24 | content="apples",
25 | objects_name="TestDBApple",
26 | description="Get information about apples",
27 | filters=[
28 | DBViewFilter(name="name", description="Filter apples by name"),
29 | DBViewFilter(
30 | name="color", description="Filter apples by color", nargs="*"
31 | ),
32 | ],
33 | objects_map={},
34 | ),
35 | ]
36 | PARAMETERS = [
37 | DBViewParameter(
38 | "format",
39 | "Select output format",
40 | choices=["yaml", "json"],
41 | ),
42 | ]
43 | ACTIONS = [
44 | DBAction(
45 | name="sell",
46 | path="/sell",
47 | description="Sell fruits",
48 | methods=["GET", "POST"],
49 | parameters=[
50 | DBActionParameter(
51 | "type", description="Type of fruits", default="apple"
52 | ),
53 | DBActionParameter(
54 | "negotiable",
55 | description="Can price be negociated?",
56 | nargs=0,
57 | ),
58 | DBActionParameter(
59 | "buyer",
60 | description="Name of buyer",
61 | specific="cli",
62 | ),
63 | DBActionParameter(
64 | "parameters",
65 | description="Sales parameters",
66 | schema="Test",
67 | in_body=True,
68 | ),
69 | ],
70 | responses=[
71 | DBActionResponse("application/json", schema=int),
72 | DBActionResponse("application/x-yaml", schema=t.List[str]),
73 | ],
74 | errors=[
75 | DBActionError(
76 | 400,
77 | "Missing request parameter.",
78 | ),
79 | DBActionError(
80 | 404,
81 | "Type of fruits not found in database.",
82 | ),
83 | ],
84 | ),
85 | ]
86 |
--------------------------------------------------------------------------------
/frontend/tests/views/DatacenterRoomView.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect, vi, beforeEach } from 'vitest'
10 | import { mount } from '@vue/test-utils'
11 | import { nextTick } from 'vue'
12 |
13 | import DatacenterRoomView from '@/views/DatacenterRoomView.vue'
14 |
15 | const datacentersMock = vi.fn()
16 | const infrastructuresMock = vi.fn()
17 | const roomImageSvgMock = vi.fn()
18 |
19 | vi.mock('@/plugins/http', () => ({
20 | useHttp: () => ({})
21 | }))
22 |
23 | vi.mock('@/composables/RacksDBAPI', () => ({
24 | useRacksDBAPI: () => ({
25 | datacenters: datacentersMock,
26 | infrastructures: infrastructuresMock,
27 | roomImageSvg: roomImageSvgMock
28 | })
29 | }))
30 |
31 | describe('DatacenterRoomView', () => {
32 | beforeEach(() => {
33 | vi.clearAllMocks()
34 | })
35 |
36 | const flushPromises = () => new Promise((resolve) => setTimeout(resolve))
37 |
38 | test('lists racks for the selected room and filters with input', async () => {
39 | infrastructuresMock.mockResolvedValueOnce([{ name: 'core', layout: [] }])
40 |
41 | datacentersMock.mockResolvedValueOnce([
42 | {
43 | name: 'paris',
44 | rooms: [
45 | {
46 | name: 'roomA',
47 | rows: [
48 | {
49 | racks: [
50 | { name: 'R01', fillrate: 0.1 },
51 | { name: 'R02', fillrate: 0 }
52 | ]
53 | }
54 | ]
55 | }
56 | ]
57 | }
58 | ])
59 |
60 | roomImageSvgMock.mockResolvedValueOnce(new Blob([''], { type: 'image/svg+xml' }))
61 |
62 | const wrapper = mount(DatacenterRoomView, {
63 | props: {
64 | datacenterName: 'paris',
65 | datacenterRoom: 'roomA'
66 | },
67 | global: {
68 | stubs: {
69 | BreadCrumbs: true,
70 | DatacenterListInfrastructures: true
71 | }
72 | }
73 | })
74 |
75 | await flushPromises()
76 | await nextTick()
77 |
78 | // Initially, both racks are present
79 | expect(wrapper.findAll('tbody tr').length).toBeGreaterThan(0)
80 |
81 | // Filter by name
82 | const input = wrapper.find('input[type="text"]')
83 | await input.setValue('R01')
84 | await nextTick()
85 |
86 | // Only one rack should remain visible in filtered table
87 | const rows = wrapper.findAll('tbody tr')
88 | // Count rows with real rack entries (skip possible empty state rows)
89 | const rackRows = rows.filter((tr) => tr.findAll('td').length === 3)
90 | expect(rackRows.length).toBe(1)
91 | })
92 | })
93 |
--------------------------------------------------------------------------------
/racksdb/tests/lib/reference.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 |
9 | # Expected values from testing reference DB.
10 | REFDB_DATACENTERS = ["paris", "london"]
11 | REFDB_INFRASTRUCTURES = ["mercury", "jupiter", "sharednet"]
12 | REFDB_TOTAL_NODES = 130
13 |
14 |
15 | class TestRacksDBReferenceDB(unittest.TestCase):
16 | """Abstract TestCase class with method to validate data from testing reference
17 | database."""
18 |
19 | def assertDatacentersResponse(self, content):
20 | self.assertIsInstance(content, list)
21 | self.assertEqual(len(content), 2)
22 | self.assertCountEqual(
23 | [datacenter["name"] for datacenter in content], REFDB_DATACENTERS
24 | )
25 | self.assertIsInstance(content[0], dict)
26 | # tags key is optional, it may not be present in selected datacenter.
27 | for key in ["name", "rooms", "location"]:
28 | self.assertIn(
29 | key,
30 | content[0],
31 | )
32 |
33 | def assertInfrastructuresResponse(self, content):
34 | self.assertIsInstance(content, list)
35 | self.assertEqual(len(content), 3)
36 | self.assertCountEqual(
37 | [infrastructure["name"] for infrastructure in content],
38 | REFDB_INFRASTRUCTURES,
39 | )
40 | self.assertIsInstance(content[0], dict)
41 | # tags key is optional, it may not be present in selected infrastructure.
42 | for key in ["name", "description", "layout"]:
43 | self.assertIn(
44 | key,
45 | content[0],
46 | )
47 |
48 | def assertNodesResponse(self, content):
49 | self.assertIsInstance(content, list)
50 | self.assertEqual(len(content), 130)
51 | self.assertIsInstance(content[0], dict)
52 | self.assertCountEqual(
53 | content[0].keys(),
54 | ["name", "infrastructure", "rack", "type", "slot", "tags", "position"],
55 | )
56 |
57 | def assertRacksResponse(self, content):
58 | self.assertIsInstance(content, list)
59 | self.assertEqual(len(content), 101)
60 | self.assertIsInstance(content[0], dict)
61 | # tags key is optional, it may not be present in select rack.
62 | for key in [
63 | "name",
64 | "slot",
65 | "type",
66 | "datacenter",
67 | "room",
68 | "row",
69 | "nodes",
70 | "fillrate",
71 | ]:
72 | self.assertIn(
73 | key,
74 | content[0].keys(),
75 | )
76 |
--------------------------------------------------------------------------------
/frontend/tests/components/FiltersBar.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect } from 'vitest'
10 | import { mount } from '@vue/test-utils'
11 |
12 | import FiltersBar from '@/components/FiltersBar.vue'
13 |
14 | describe('FiltersBar', () => {
15 | test('renders active filters as badges', () => {
16 | const wrapper = mount(FiltersBar, {
17 | props: {
18 | selectedRacks: ['R01', 'R02'],
19 | selectedEquipmentTypes: ['server'],
20 | selectedCategories: ['nodes'],
21 | selectedTags: ['prod'],
22 | inputEquipmentName: 'test-server'
23 | }
24 | })
25 |
26 | expect(wrapper.text()).toContain('R01')
27 | expect(wrapper.text()).toContain('R02')
28 | expect(wrapper.text()).toContain('server')
29 | expect(wrapper.text()).toContain('nodes')
30 | expect(wrapper.text()).toContain('prod')
31 | expect(wrapper.text()).toContain('test-server')
32 | })
33 |
34 | test('does not render empty filters', () => {
35 | const wrapper = mount(FiltersBar, {
36 | props: {
37 | selectedRacks: [],
38 | selectedEquipmentTypes: [],
39 | selectedCategories: [],
40 | selectedTags: [],
41 | inputEquipmentName: ''
42 | }
43 | })
44 |
45 | // Should only show "Filters" label and screen reader text
46 | expect(wrapper.text()).toContain('Filters')
47 | expect(wrapper.text()).toContain('active')
48 | })
49 |
50 | test('removes filter when X button is clicked', async () => {
51 | const wrapper = mount(FiltersBar, {
52 | props: {
53 | selectedRacks: ['R01'],
54 | selectedEquipmentTypes: [],
55 | selectedCategories: [],
56 | selectedTags: [],
57 | inputEquipmentName: ''
58 | }
59 | })
60 |
61 | // Find and click the X button for R01
62 | const removeButton = wrapper.find('button')
63 | await removeButton.trigger('click')
64 |
65 | // Check that the filter was removed
66 | expect(wrapper.vm.selectedRacks).toEqual([])
67 | })
68 |
69 | test('clears equipment name filter when X button is clicked', async () => {
70 | const wrapper = mount(FiltersBar, {
71 | props: {
72 | selectedRacks: [],
73 | selectedEquipmentTypes: [],
74 | selectedCategories: [],
75 | selectedTags: [],
76 | inputEquipmentName: 'test'
77 | }
78 | })
79 |
80 | // Find and click the X button for equipment name
81 | const removeButton = wrapper.find('button')
82 | await removeButton.trigger('click')
83 |
84 | // Check that the equipment name was cleared
85 | expect(wrapper.vm.inputEquipmentName).toBe('')
86 | })
87 | })
88 |
--------------------------------------------------------------------------------
/docs/modules/db/pages/files.adoc:
--------------------------------------------------------------------------------
1 | = Database Files
2 |
3 | == Location
4 |
5 | By default, RacksDB expects the database to be located in
6 | [.path]#`/var/lib/racksdb`# directory, splitted into multiple files.
7 |
8 | NOTE: RacksDB can load databases at the location of your choice, at the
9 | condition of specifying the path in xref:usage:racksdb.adoc[`racksdb`
10 | command] options and xref:usage:lib.adoc[library `load()`] arguments. For this
11 | reason, it is highly recommended to use the default path.
12 |
13 | [#splitted]
14 | == Files Tree
15 |
16 | RacksDB database can be stored in a single YAML file. However, for obvious
17 | maintenaibility reasons, it is recommended to split the database into multiple
18 | files. RacksDB can load a database by walking into a directory recursively. It
19 | loads all YAML formatted files with `.yml` extension. It perfoms a mapping
20 | between the files hierarchy and the resulting YAML tree.
21 |
22 | When a folder is suffixed by `.l`, RacksDB considers the files in this folder
23 | to be members of a sequence (_aka._ list or array). The content of each of these
24 | files is an item in this list. In this case, the filename is ignored.
25 |
26 | In other folders, the files are considered to be a key/value pair of a mapping
27 | (_aka_. hash or associative array). The filename without its extension is the
28 | key, the value is the content of the file.
29 |
30 | Following these principles, users are free to choose the level of fragmentation
31 | in the database model, at their own convenience.
32 |
33 | .Show basic example
34 | [%collapsible]
35 | ====
36 | For example, consider this basic example YAML file:
37 |
38 | [source,yaml]
39 | ----
40 | apples:
41 | - variety: granny smith
42 | color: green
43 | - variety: golden
44 | color: yellow
45 | stocks:
46 | apples:
47 | granny smith: 10
48 | golden: 20
49 | ----
50 |
51 | It is strictly identical to this folder tree:
52 |
53 | [source]
54 | ----
55 | 📂 apples.l/
56 | 📄 granny.yml
57 | 📄 golden.yml
58 | 📂 stocks/
59 | 📄 apples.yml
60 | ----
61 |
62 | With the following files content:
63 |
64 | `granny.yml`::
65 |
66 | [source,yaml]
67 | ----
68 | variety: granny smith
69 | color: green
70 | ----
71 |
72 | `golden.yml`::
73 |
74 | [source,yaml]
75 | ----
76 | variety: golden
77 | color: yellow
78 | ----
79 |
80 | `apples.yml`::
81 |
82 | [source,yaml]
83 | ----
84 | granny smith: 10
85 | golden: 20
86 | ----
87 | ====
88 |
89 | == Examples
90 |
91 | When RacksDB is installed, full examples of databases are available in path
92 | [.path]#`/usr/share/doc/racksdb/examples/db/`#. Database contained in a single
93 | YAML file and databases splitted in a tree of YAML files can be found in this
94 | folder.
95 |
96 | Examples are also available in
97 | https://github.com/rackslab/racksdb/blob/main/examples/[source code Git
98 | repository].
99 |
--------------------------------------------------------------------------------
/frontend/tests/views/DatacentersView.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect, vi, beforeEach } from 'vitest'
10 | import { mount } from '@vue/test-utils'
11 | import { nextTick } from 'vue'
12 |
13 | import DatacentersView from '@/views/DatacentersView.vue'
14 |
15 | // Mock router used by the view
16 | vi.mock('vue-router', () => ({
17 | useRouter: () => ({
18 | resolve: () => ({ fullPath: '/datacenter/paris' })
19 | })
20 | }))
21 |
22 | // Mock HTTP plugin and API composable
23 | const datacentersMock = vi.fn()
24 | vi.mock('@/plugins/http', () => ({
25 | useHttp: () => ({})
26 | }))
27 | vi.mock('@/composables/RacksDBAPI', () => ({
28 | useRacksDBAPI: () => ({
29 | datacenters: datacentersMock
30 | })
31 | }))
32 |
33 | // Mock Leaflet to avoid DOM-heavy behavior
34 | vi.mock('leaflet', () => {
35 | const addTo = () => ({})
36 | const on = () => ({})
37 | return {
38 | default: {
39 | map: () => ({ setView: () => ({ on, fitBounds: () => {} }) }),
40 | tileLayer: () => ({ addTo }),
41 | marker: () => ({ addTo: () => ({ bindPopup: () => {} }) }),
42 | latLngBounds: () => ({ extend: () => {} }),
43 | icon: () => ({})
44 | }
45 | }
46 | })
47 |
48 | describe('DatacentersView', () => {
49 | beforeEach(() => {
50 | vi.clearAllMocks()
51 | })
52 |
53 | const flushPromises = () => new Promise((resolve) => setTimeout(resolve))
54 |
55 | test('hides the map when no datacenter has a location', async () => {
56 | datacentersMock.mockResolvedValueOnce([
57 | { name: 'paris', rooms: [], location: undefined },
58 | { name: 'london', rooms: [], location: undefined }
59 | ])
60 |
61 | const wrapper = mount(DatacentersView, {
62 | global: {
63 | stubs: {
64 | BreadCrumbs: true
65 | }
66 | }
67 | })
68 |
69 | // Wait for onMounted async code
70 | await flushPromises()
71 | await nextTick()
72 |
73 | expect(wrapper.vm.showMap).toBe(false)
74 | })
75 |
76 | test('shows the map when at least one datacenter has a location', async () => {
77 | datacentersMock.mockResolvedValueOnce([
78 | {
79 | name: 'paris',
80 | rooms: [],
81 | location: { latitude: 48.85, longitude: 2.35 }
82 | },
83 | { name: 'london', rooms: [], location: undefined }
84 | ])
85 |
86 | const wrapper = mount(DatacentersView, {
87 | attachTo: document.body,
88 | global: {
89 | stubs: {
90 | BreadCrumbs: true
91 | }
92 | }
93 | })
94 |
95 | await flushPromises()
96 | await nextTick()
97 |
98 | expect(wrapper.vm.showMap).toBe(true)
99 | })
100 | })
101 |
--------------------------------------------------------------------------------
/frontend/tests/components/HomeViewCards.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect, vi, beforeEach } from 'vitest'
10 | import { mount, flushPromises } from '@vue/test-utils'
11 | import { nextTick } from 'vue'
12 |
13 | // Mock API composable
14 | const datacentersMock = vi.fn()
15 | const infrastructuresMock = vi.fn()
16 |
17 | vi.mock('@/plugins/http', () => ({
18 | useHttp: () => ({})
19 | }))
20 |
21 | vi.mock('@/composables/RacksDBAPI', () => ({
22 | useRacksDBAPI: () => ({
23 | datacenters: datacentersMock,
24 | infrastructures: infrastructuresMock
25 | })
26 | }))
27 |
28 | import HomeViewCards from '@/components/HomeViewCards.vue'
29 |
30 | describe('HomeViewCards', () => {
31 | beforeEach(() => {
32 | vi.clearAllMocks()
33 | })
34 |
35 | test('loads and displays datacenters and infrastructures', async () => {
36 | datacentersMock.mockResolvedValueOnce([
37 | { name: 'paris', rooms: [], tags: ['prod'] },
38 | { name: 'london', rooms: [], tags: ['test'] }
39 | ])
40 |
41 | infrastructuresMock.mockResolvedValueOnce([
42 | { name: 'core', description: 'Core infrastructure', tags: ['compute'] },
43 | { name: 'edge', description: 'Edge infrastructure', tags: ['network'] }
44 | ])
45 |
46 | const wrapper = mount(HomeViewCards)
47 |
48 | await flushPromises()
49 | await nextTick()
50 |
51 | expect(wrapper.text()).toContain('2 datacenter')
52 | expect(wrapper.text()).toContain('2 infrastructure')
53 | expect(wrapper.text()).toContain('paris')
54 | expect(wrapper.text()).toContain('london')
55 | expect(wrapper.text()).toContain('core')
56 | expect(wrapper.text()).toContain('edge')
57 | })
58 |
59 | test('displays singular form for single items', async () => {
60 | datacentersMock.mockResolvedValueOnce([{ name: 'paris', rooms: [], tags: [] }])
61 |
62 | infrastructuresMock.mockResolvedValueOnce([
63 | { name: 'core', description: 'Core infrastructure', tags: [] }
64 | ])
65 |
66 | const wrapper = mount(HomeViewCards)
67 |
68 | await flushPromises()
69 | await nextTick()
70 |
71 | expect(wrapper.text()).toContain('1 datacenter')
72 | expect(wrapper.text()).toContain('1 infrastructure')
73 | })
74 |
75 | test('displays tags for datacenters and infrastructures', async () => {
76 | datacentersMock.mockResolvedValueOnce([{ name: 'paris', rooms: [], tags: ['prod', 'eu'] }])
77 |
78 | infrastructuresMock.mockResolvedValueOnce([
79 | { name: 'core', description: 'Core infrastructure', tags: ['compute', 'storage'] }
80 | ])
81 |
82 | const wrapper = mount(HomeViewCards)
83 |
84 | await flushPromises()
85 | await nextTick()
86 |
87 | expect(wrapper.text()).toContain('#prod')
88 | expect(wrapper.text()).toContain('#eu')
89 | expect(wrapper.text()).toContain('#compute')
90 | expect(wrapper.text()).toContain('#storage')
91 | })
92 | })
93 |
--------------------------------------------------------------------------------
/racksdb/tests/generic/test_db_dict.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 |
9 | from racksdb.generic.db import DBDict, DBObjectRange, DBExpandableObject
10 |
11 | from .lib.common import valid_db
12 |
13 |
14 | class TestDBDict(unittest.TestCase):
15 | def setUp(self):
16 | self.db = valid_db()
17 |
18 | def test_filter_ok(self):
19 | self.assertIsInstance(self.db.apples, DBDict)
20 | yellow_species = self.db.apples.filter(color="yellow")
21 | self.assertIsInstance(yellow_species, DBDict)
22 | self.assertCountEqual(yellow_species.keys(), ["golden"])
23 | self.assertEqual(yellow_species["golden"].__class__.__name__, "TestApple")
24 |
25 | def test_filter_no_value(self):
26 | blue_species = self.db.apples.filter(color="blue")
27 | self.assertIsInstance(blue_species, DBDict)
28 | self.assertCountEqual(blue_species.keys(), [])
29 |
30 | def test_filter_invalid_criteria(self):
31 | with self.assertRaises(TypeError):
32 | self.db.apples.filter(fail="fail")
33 |
34 | def test_iter(self):
35 | nb = 0
36 | for item in self.db.apples:
37 | nb += 1
38 | self.assertEqual(nb, 2)
39 |
40 | def test_getitem(self):
41 | self.assertEqual(self.db.apples["golden"].__class__.__name__, "TestApple")
42 |
43 | def test_getitem_not_found(self):
44 | with self.assertRaisesRegex(KeyError, "fail"):
45 | self.db.apples["fail"]
46 |
47 | def test_len(self):
48 | self.assertEqual(len(self.db.apples), 2)
49 |
50 | def test_first(self):
51 | first = self.db.apples.first()
52 | self.assertEqual(first.__class__.__name__, "TestApple")
53 |
54 |
55 | class TestDBDictExpandable(unittest.TestCase):
56 | def setUp(self):
57 | self.db = valid_db()
58 | self.expandable_dict = DBDict()
59 | # Create DBDict of DBExpandableObjects with DBList of AppleCrate.
60 | for crates in self.db.stock.content.itervalues():
61 | self.expandable_dict[crates.name] = crates
62 | self.assertIsInstance(crates.name, DBObjectRange)
63 | self.assertIsInstance(crates, DBExpandableObject)
64 |
65 | def test_expandable_filter(self):
66 | selected = self.expandable_dict.filter(quantity_min=15)
67 | self.assertIsInstance(selected, DBDict)
68 | self.assertEqual(len(selected), 10)
69 | selected = self.expandable_dict.filter(quantity_min=5)
70 | self.assertEqual(len(selected), 20)
71 |
72 | def test_expandable_iter(self):
73 | nb = 0
74 | for item in self.expandable_dict:
75 | nb += 1
76 | self.assertEqual(nb, 20)
77 |
78 | def test_expandable_getitem(self):
79 | self.assertEqual(self.expandable_dict["crate02"].name, "crate02")
80 |
81 | def test_expandable_len(self):
82 | self.assertEqual(len(self.expandable_dict), 20)
83 |
84 | def test_expandable_first(self):
85 | self.assertEqual(self.expandable_dict.first().name, "crate01")
86 |
--------------------------------------------------------------------------------
/docs/modules/usage/pages/ui.adoc:
--------------------------------------------------------------------------------
1 | = Web UI
2 |
3 | This section describes all features available in Web UI pages.
4 |
5 | == Home
6 |
7 | image::shadowed/home.webp[UI Homeview,800,xref=image$raw/home.png]
8 |
9 | The *Home* page provides an overview of what's inside RacksDB in card form.
10 | Users can navigate to different sections, such as _datacenters_, _datacenter
11 | details_, _datacenter room_, _infrastructures_, _infrastructure details,_
12 | either through the navigation bar or by clicking on the items in the cards.
13 |
14 | == Datacenters
15 |
16 | image::shadowed/datacenters.webp[UI Datacenters,800,xref=image$raw/datacenters.png]
17 |
18 | In *Datacenters* page, users can select a datacenter in the list. The
19 | datacenters can be filtered by typing portion of their name in the *search bar*.
20 | The datacenters can also be selected in *the map* by clicking on the markers.
21 |
22 | == Datacenter Details
23 |
24 | image::shadowed/rooms.webp[UI Datacenter Details,800,xref=image$raw/rooms.png]
25 |
26 | The *Datacenter Details* page displays details about the selected datacenter.
27 | Users can filter the list of rooms by typing portion of their names in the text
28 | input field in top right corner. The order of rooms can be inverted with *sort
29 | rooms* button. Users can *click on a room* to access the room details page.
30 |
31 | == Room Details
32 |
33 | image::shadowed/racks.webp[UI Datacenter Room,800,xref=image$raw/racks.png]
34 |
35 | In *Room Details* page, a thumbnail of the room map is displayed at the top.
36 | Users can *click on this thumbnail* to display a larger image.
37 |
38 | Below this image, a table contains the list of racks in the room with their
39 | _names_, _fill rates_ and _infrastructures_ using them. Infrastructures can be
40 | clicked to access infrastructure details.
41 |
42 | Racks can be filtered using the *filter bar*. The list can be inverted with
43 | *sort racks* button. Empty racks can be also hidden using the *toggle button*.
44 |
45 | == Infrastructures
46 |
47 | image::shadowed/infrastructures.webp[UI Infrastructures,800,xref=image$raw/infrastructures.png]
48 |
49 | In *Infrastructures* page, users can select an infrastructure in the list. The
50 | list can be filtered by typing portion of their name in the *search bar*.
51 |
52 | == Infrastructure Details
53 |
54 | image::shadowed/infrastructure.webp[UI Infrastructure Details,800,xref=image$raw/infrastructure.png]
55 |
56 | In *Infrastructure Details* page, a graphical representation of the
57 | infrastructure is displayed at the top. Users can *click on this thumbnail* to
58 | display a larger image.
59 |
60 | The page contains a table of all infrastructure equipment grouped by rack,
61 | including their _name_, _category_, _model_ and _tags_.
62 |
63 | Users can *show or hide racks* by clicking on racks name. Users can also click
64 | on equipment model name to *open a modal* with more details about this model.
65 |
66 | Filters are available to help users find specific equipment in infrastructures.
67 | Lists allow _selection of racks_ and _equipment types_, *checkboxes* for
68 | _equipment categories_ and _tags_ and a *search bar* to find specific equipment
69 | by name:
70 |
71 | image::shadowed/infrastructure_filters.webp[UI Infrastructure Filters,800,xref=image$raw/infrastructure_filters.png]
72 |
--------------------------------------------------------------------------------
/frontend/src/components/BreadCrumbs.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
19 |
20 |
21 |
22 |

25 |
26 |
27 |
28 |
29 |
30 |
Datacenters
35 |
36 |
37 |
38 |
39 |
40 |
{{
42 | props.datacenterName
43 | }}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
{{
53 | props.datacenterName
54 | }}
56 |
57 |
58 |
59 |
60 |
{{
62 | props.datacenterRoom
63 | }}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
Infrastructures
77 |
78 |
79 |
80 |
81 |
82 |
{{
85 | props.infrastructureName
86 | }}
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/frontend/src/views/InfrastructureDetailsView.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
58 |
59 |
60 |
61 |
62 |
![]()
69 |
70 |
71 |
97 |
98 |
99 |
103 |
104 |
105 |
--------------------------------------------------------------------------------
/racksdb/tests/drawers/test_axonometric_infrastructure.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | import unittest
8 | import tempfile
9 | from pathlib import Path
10 |
11 | from racksdb import RacksDB
12 | from racksdb.drawers.axonometric_infrastructure import AxonometricInfrastructureDrawer
13 | from racksdb.drawers.parameters import DrawingParameters
14 | from racksdb.generic.db import DBDictsLoader
15 | from racksdb.errors import RacksDBDrawingError
16 |
17 | from ..lib.common import drawing_schema_path, schema_path, db_path
18 |
19 |
20 | class TestAxonometricInfrastructureDrawer(unittest.TestCase):
21 | def setUp(self):
22 | try:
23 | self.drawings_schema_path = drawing_schema_path()
24 | self.schema_path = schema_path()
25 | self.db_path = db_path()
26 | except FileNotFoundError as err:
27 | self.skipTest(err)
28 | self.parameters = DrawingParameters.load(
29 | DBDictsLoader({}), self.drawings_schema_path
30 | )
31 | self.db = RacksDB.load(schema=self.schema_path, db=self.db_path)
32 |
33 | def test_draw_png(self):
34 | with tempfile.TemporaryDirectory() as tmpdir:
35 | filename = Path(tmpdir) / "output.png"
36 | drawer = AxonometricInfrastructureDrawer(
37 | self.db, "mercury", filename, "png", self.parameters, None, "yaml"
38 | )
39 | drawer.draw()
40 | self.assertTrue(filename.exists())
41 |
42 | def test_draw_svg(self):
43 | with tempfile.TemporaryDirectory() as tmpdir:
44 | filename = Path(tmpdir) / "output.svg"
45 | drawer = AxonometricInfrastructureDrawer(
46 | self.db, "mercury", filename, "svg", self.parameters, None, "yaml"
47 | )
48 | drawer.draw()
49 | self.assertTrue(filename.exists())
50 |
51 | def test_draw_pdf(self):
52 | with tempfile.TemporaryDirectory() as tmpdir:
53 | filename = Path(tmpdir) / "output.pdf"
54 | drawer = AxonometricInfrastructureDrawer(
55 | self.db, "mercury", filename, "pdf", self.parameters, None, "yaml"
56 | )
57 | drawer.draw()
58 | self.assertTrue(filename.exists())
59 |
60 | def test_draw_not_existing_infrastructure(self):
61 | with tempfile.TemporaryDirectory() as tmpdir:
62 | filename = Path(tmpdir) / "output.png"
63 | with self.assertRaisesRegex(
64 | RacksDBDrawingError, "^Unable to find infrastructure fail in database$"
65 | ):
66 | AxonometricInfrastructureDrawer(
67 | self.db, "fail", filename, "png", self.parameters, None, "yaml"
68 | )
69 | self.assertFalse(filename.exists())
70 |
71 | def test_draw_no_rack_error(self):
72 | self.parameters.infrastructure.equipment_tags = "fail"
73 | with tempfile.TemporaryDirectory() as tmpdir:
74 | filename = Path(tmpdir) / "output.png"
75 | drawer = AxonometricInfrastructureDrawer(
76 | self.db, "mercury", filename, "png", self.parameters, None, "yaml"
77 | )
78 | with self.assertRaisesRegex(
79 | RacksDBDrawingError,
80 | "^Unable to find racks to draw with filters provided in drawing "
81 | "parameters$",
82 | ):
83 | drawer.draw()
84 | self.assertFalse(filename.exists())
85 |
--------------------------------------------------------------------------------
/docs/modules/usage/pages/integrations.adoc:
--------------------------------------------------------------------------------
1 | = Integrations
2 |
3 | == Clustershell
4 |
5 | https://clustershell.readthedocs.io/en/latest/index.html[ClusterShell] is a
6 | scalable cluster administration Python framework, designed to execute commands
7 | on many cluster nodes in parallel. ClusterShell supports the possibility to
8 | define groups of nodes. These groups can be extracted from external sources.
9 |
10 | This section illustrates how to make ClusterShell extracts groups from RacksDB
11 | and tags associated to nodes with a simple example. In this example, let's
12 | consider a cluster infrastructure named _atlas_.
13 |
14 | Create file [.path]#`/etc/clustershell/groups.conf.d/racksdb.conf`# with this content:
15 |
16 | [source,ini]
17 | ----
18 | [racksdb]
19 | map: racksdb nodes --infrastructure atlas --tags $GROUP --list
20 | all: racksdb nodes --infrastructure atlas --list
21 | list: racksdb tags --infrastructure atlas --on-nodes
22 | reverse: racksdb tags --node $NODE
23 | ----
24 |
25 | With this simple configuration, ClusterShell can extract groups and nodes from
26 | RacksDB. For example, with `nodeset` command:
27 |
28 | * Get the list of groups:
29 | +
30 | --
31 | [source,console]
32 | ----
33 | $ nodeset -s racksdb -l
34 | @racksdb:admin
35 | @racksdb:compute
36 | @racksdb:login
37 | ----
38 | --
39 |
40 | * Get all nodes in _compute_ group (ie. associated to _compute_ tag in
41 | database):
42 | +
43 | --
44 | [source,console]
45 | ----
46 | $ nodeset -f @racksdb:compute
47 | atcn[1-2]
48 | ----
49 | --
50 |
51 | * Get all nodes in this cluster:
52 | +
53 | --
54 | [source,console]
55 | ----
56 | $ nodeset -f -s racksdb -a
57 | atadmin,atcn[1-2],atlogin
58 | ----
59 | --
60 |
61 | ClusterShell can execute commands on nodes from these groups with `clush`:
62 |
63 | [source,console]
64 | ----
65 | # clush -bw @racksdb:compute uname
66 | ---------------
67 | atcn[1-2] (2)
68 | ---------------
69 | Linux
70 | ----
71 |
72 | This way, ClusterShell groups are always synchronized with tags and content of
73 | RacksDB database. when RacksDB database is updated, changes are automatically
74 | reflected in ClusterShell.
75 |
76 | .More than one infrastructure?
77 | ****
78 | If you need to extract groups from multiple infrastructures in RacksDB, a simple
79 | approach if to use ClusterShell
80 | https://clustershell.readthedocs.io/en/latest/config.html#multiple-sources-section[multiple sources sections]
81 | feature and `$GROUP` variable. For example, in
82 | [.path]#`/etc/clustershell/groups.conf.d/racksdb.conf`#:
83 |
84 | [source,ini]
85 | ----
86 | [trinity,atlas]
87 | map: racksdb nodes --infrastructure $SOURCE --tags $GROUP --list
88 | all: racksdb nodes --infrastructure $SOURCE --list
89 | list: racksdb tags --infrastructure $SOURCE --on-nodes
90 | reverse: racksdb tags --node $NODE
91 | ----
92 |
93 | This way, RacksDB commands are executed for multiple infrastructures:
94 |
95 | [source,console]
96 | ----
97 | $ nodeset -f @trinity:compute
98 | tricn[1-4]
99 | $ nodeset -f @atlas:compute
100 | atcn[1-2]
101 | ----
102 |
103 | Another yet more generic approach to split the group source with shell:
104 |
105 | [source,ini]
106 | ----
107 | [racksdb]
108 | map: GRP=$GROUP; racksdb nodes --infrastructure ${GRP%:*} --tags ${GRP#*:} --list
109 | all: racksdb nodes --list
110 | reverse: racksdb tags --node $NODE
111 | ----
112 |
113 | This way, infrastructures names are not mentioned in ClusterShell configuration
114 | file and all infrastructures defined in RacksDB can be requested:
115 |
116 | [source,console]
117 | ----
118 | $ nodeset -f @racksdb:trinity:compute
119 | tricn[1-4]
120 | $ nodeset -f @racksdb:atlas:compute
121 | atcn[1-2]
122 | ----
123 | ****
124 |
--------------------------------------------------------------------------------
/docs/modules/usage/pages/drawparams.adoc:
--------------------------------------------------------------------------------
1 | = Drawing Parameters
2 |
3 | RacksDB offers the possibity to generate graphical representations of datacenter
4 | room and infrastructures with their racks and equipments in various images
5 | formats. The images are generated with various default drawing parameters that
6 | user are able to customize for their requirements.
7 |
8 | The xref:#usage[first section] of this page describes how to specify drawing
9 | parameters with both RacksDB xref:racksdb.adoc[CLI] and
10 | xref:rest.adoc[REST API]. The xref:#reference[second section] is the reference
11 | documentation of all available drawing parameters with their format and default
12 | values.
13 |
14 | [#usage]
15 | == Usage
16 |
17 | === CLI
18 |
19 | The `draw` subcommand of xref:racksdb.adoc[`racksdb` CLI] accepts drawing
20 | parameters with `--parameters` option. The value of this option must specify a
21 | path to a YAML file that contains the drawing parameters.
22 |
23 | .Show example `custom.yml` drawing parameters YAML file
24 | [%collapsible]
25 | ====
26 | [source,yaml]
27 | ----
28 | include::example$custom.yml[]
29 | ----
30 | ====
31 |
32 | For example, to generate graphical representation of _tiger_ infrastructure with
33 | custom drawing parameters in `custom.yml` file, run this command:
34 |
35 | [source,console]
36 | ----
37 | $ racksdb draw infrastructure --name=tiger --parameters=custom.yml
38 | ----
39 |
40 | Alternatively, the option accepts the special value `-`. In this case, the
41 | drawing parameters are read on command standard input (_stdin_). For example:
42 |
43 | [source,console]
44 | ----
45 | $ racksdb draw room --name=noisy --parameters=- < custom.yaml
46 | ----
47 |
48 | === REST API
49 |
50 | It is also possible to provide custom drawing parameters to RacksDB
51 | xref:rest.adoc[REST API]. The REST API accepts drawing parameters in both JSON
52 | and YAML formats. The parameters must be sent in requests body content with
53 | content type header set according to the format.
54 |
55 | For example, to generate graphical representation of _mercury_ infrastructure
56 | with inline drawing parameters in JSON format, the following `curl` command can
57 | be used:
58 |
59 | [source,console,subs=attributes]
60 | ----
61 | $ curl -X POST -H 'Content-Type: application/json' \
62 | --data '{"margin":{ "top": 200 }}' \
63 | http://localhost:5000/{api-version}/draw/infrastructure/mercury.png \
64 | --output mercury.png
65 | ----
66 |
67 | Alternatively, the drawing parameters can be extracted a JSON file. For example
68 | with file `custom.json`:
69 |
70 | [source,console,subs=attributes]
71 | ----
72 | $ curl -X POST -H 'Content-Type: application/json' \
73 | --data @custom.json
74 | http://localhost:5000/{api-version}/draw/infrastructure/mercury.png \
75 | --output mercury.png
76 | ----
77 |
78 | .Show example `custom.json` drawing parameters YAML file
79 | [%collapsible]
80 | ====
81 | [source,json]
82 | ----
83 | include::example$custom.json[]
84 | ----
85 | ====
86 |
87 | To send drawing parameters in YAML format, the content type HTTP header must be
88 | changed:
89 |
90 | [source,console,subs=attributes]
91 | ----
92 | $ curl -X POST -H 'Content-Type: application/x-yaml' \
93 | --data-binary @custom.yml \
94 | http://localhost:5000/{api-version}/draw/infrastructure/mercury.png \
95 | --output mercury.png
96 | ----
97 |
98 | CAUTION: To send YAML content with `curl` client, the `--data-binary` option
99 | must be used in favor of `-d, --data` to preserve line breaks.
100 |
101 | [#reference]
102 | == Reference
103 |
104 | include::db:partial$symbols.adoc[leveloffset=+1]
105 |
106 | include::partial$drawing-objects.adoc[leveloffset=+1]
107 |
108 | include::partial$drawing-deftypes.adoc[leveloffset=+1]
109 |
--------------------------------------------------------------------------------
/racksdb/generic/dumpers/json.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2023 Rackslab
2 | #
3 | # This file is part of RacksDB.
4 | #
5 | # SPDX-License-Identifier: MIT
6 |
7 | from typing import Any
8 | import json
9 | import logging
10 |
11 | from ._common import MapperDumper
12 | from ...generic.db import DBObject, DBObjectRange, DBObjectRangeId, DBDict, DBList
13 |
14 | logger = logging.getLogger(__name__)
15 |
16 |
17 | class GenericJSONEncoder(json.JSONEncoder, MapperDumper):
18 | def __init__(self, objects_map={}, fold=True, **kwargs):
19 | json.JSONEncoder.__init__(self, **kwargs)
20 | MapperDumper.__init__(self, objects_map)
21 | self.fold = fold
22 |
23 | def _fill_obj_dict(self, result, obj, prop, value):
24 | value = self.map(obj, prop, value)
25 | if value is None:
26 | return
27 | if (
28 | isinstance(value, DBObject)
29 | or isinstance(value, DBDict)
30 | or isinstance(value, DBList)
31 | ):
32 | result[prop] = self.default(value)
33 | else:
34 | result[prop] = value
35 |
36 | def default(self, obj: Any) -> Any:
37 | if isinstance(obj, DBObjectRange):
38 | return str(obj.rangeset)
39 | elif isinstance(obj, DBObjectRangeId):
40 | return obj.start
41 | elif isinstance(obj, DBDict):
42 | if self.fold:
43 | # Force iteration over the values of the dictionnary to avoid automatic
44 | # expansion performed by DBDict iterator.
45 | return [item for item in obj.values()]
46 | else:
47 | # Use DBDict iterator to expand potential DBExpandableObject.
48 | return [item for item in obj]
49 | elif isinstance(obj, DBList):
50 | if self.fold:
51 | return [item for item in obj.itervalues()]
52 | else:
53 | # As a DBList is also a standard iterable Python list, json.JSONEncoder
54 | # will iterate itself over the DBList, thus expanding potential
55 | # expandable objects automatically. There is nothing more to do in this
56 | # case.
57 | return obj
58 | elif isinstance(obj, DBObject):
59 | result = {}
60 | for prop in obj._schema.properties:
61 | try:
62 | self._fill_obj_dict(result, obj, prop.name, getattr(obj, prop.name))
63 | except AttributeError:
64 | continue
65 | return result
66 | # Let the base class default method raise the TypeError
67 | return json.JSONEncoder.default(self, obj)
68 |
69 |
70 | class DBDumperJSON:
71 | def __init__(self, show_types=False, objects_map={}, fold=True):
72 | self.objects_map = objects_map
73 | self.fold = fold
74 |
75 | def dump(self, obj: Any) -> str:
76 | # DBDict are also standard python dictionnaries, then json.JSONEncoder thinks it
77 | # can handle without hurdle and it does not call GenericJSONEncoder.default()
78 | # for this obj type. However:
79 | #
80 | # - DBDict must be represented as a list of values in dumps, the keys must not
81 | # be represented as they are just defined for pratical reasons to facilitate
82 | # data manipulation in library.
83 | # - Keys can be DBObjectRange objects that cannot be represented by standard
84 | # json.JSONEncoder.
85 | #
86 | # For these reasons, DBDict is converted here as a standard list of values.
87 | if isinstance(obj, DBDict):
88 | if self.fold:
89 | obj = [item for item in obj.values()]
90 | else:
91 | obj = [item for item in obj]
92 | return GenericJSONEncoder(objects_map=self.objects_map, fold=self.fold).encode(
93 | obj
94 | )
95 |
--------------------------------------------------------------------------------
/docs/utils/schema-objs.adoc.j2:
--------------------------------------------------------------------------------
1 | {#
2 | Warn people opening the resulting file it is generated using this template
3 | and the accompanying script.
4 | -#}
5 | ////
6 | Do not modify this file directly, it is automatically generated by combining
7 | the Python script `docs/utils/schema-objs.py` and the template
8 | `docs/utils/schema-objs.adoc.j2`. Please refer to the Python script comments
9 | to discover how it is used.
10 | ////
11 | {#
12 | Macro to represent the type of an object attribute
13 | #}
14 | {%- macro represent_type(data_type) -%}
15 | {%- if data_type.__class__.__name__ == 'SchemaContainerList' -%}
16 | {symbol-seq}{nbsp}{{ represent_type(data_type.content) }}
17 | {%- elif data_type.__class__.__name__ == 'SchemaReference' -%}
18 | {symbol-ref}{nbsp}{{ represent_type(data_type.obj) }}{% if data_type.prop is not none %}.{{data_type.prop}}{% endif %}
19 | {%- elif data_type.__class__.__name__ == 'SchemaBackReference' -%}
20 | {{ represent_type(data_type.obj) }}{% if data_type.prop is not none %}.{{data_type.prop}}{% endif %}
21 | {%- elif data_type.__class__.__name__ == 'SchemaObject' -%}
22 | xref:#obj-{{ data_type.name|lower() }}[{symbol-obj}{nbsp}{{ data_type.name }}]
23 | {%- elif 'SchemaDefinedType' in data_type|bases -%}
24 | xref:#deftype-{{ data_type.name[deftype_prefix|length:]|lower() }}[{symbol-deftype}{nbsp}~{{ data_type.name[deftype_prefix|length:] }}]
25 | {%- else -%}
26 | {{ data_type }}
27 | {%- endif -%}
28 | {%- endmacro -%}
29 |
30 | {%- macro represent_automatic_prop_name(prop) -%}
31 | {%- if prop.type.__class__.__name__ == 'SchemaBackReference' -%}
32 | {symbol-backref}
33 | {%- elif prop.computed -%}
34 | {symbol-computed}
35 | {%- endif -%}
36 | {nbsp}{{ prop.name }}
37 | {%- endmacro -%}
38 | {#
39 | Macro to render obj attributes table
40 | #}
41 | {%- macro obj_attributes(obj) -%}
42 | .Properties
43 | [cols="{tbl-obj-props-cols-specs}"]
44 | |===
45 | |Property|Description|Type|Required
46 | {% for prop in obj.properties if not prop.type.__class__.__name__ == 'SchemaBackReference' and not prop.computed %}
47 | |{{ prop.name }}{% if prop.key is true %}{nbsp}{symbol-key}{% endif %}
48 | |{{ prop.description }}
49 | {% if prop.default is not none %}
50 | {%- if prop.default is mapping %}
51 | *Default value:*
52 |
53 | [source,yaml]
54 | ----
55 | {{prop.default|toyaml}}
56 | ----
57 | {%- else %}
58 | *Default value:* `{{prop.default}}`
59 | {%- endif %}
60 | {%- endif %}
61 | |{{ represent_type(prop.type) }}
62 | |{% if prop.required is false %}[.grey]#-#{% else %}[.green]#icon:check[]#{% endif %}
63 | {% endfor -%}
64 | |===
65 | {%- endmacro -%}
66 | {#
67 | Macro to render obj backrefs table
68 | #}
69 | {%- macro obj_backrefs(obj) %}
70 |
71 | .Automatic properties
72 | [cols="{tbl-obj-backrefs-cols-specs}"]
73 | |===
74 | |Property|Description|Type
75 | {% for prop in obj.properties if prop.type.__class__.__name__ == 'SchemaBackReference' or prop.computed %}
76 | |{{ represent_automatic_prop_name(prop) }}
77 | |{{ prop.description }}
78 | |{{ represent_type(prop.type) }}
79 | {% endfor -%}
80 | |===
81 | {%- endmacro -%}
82 | :tbl-obj-props-cols-specs: 2m,6a,4,^.^1
83 | :tbl-obj-backrefs-cols-specs: 4m,6a,4
84 |
85 | [#obj-root]
86 | == Database Root
87 |
88 | Library class type: `{{object_prefix}}`
89 |
90 | {{ obj_attributes(schema.content) }}
91 |
92 | [#objects]
93 | == Objects
94 | {#-
95 | Iterate over the various objects sorted in lexicographical order.
96 | -#}
97 | {% for obj in schema.objects.values()|sort(attribute='name') %}
98 |
99 | [#obj-{{ obj.name|lower() }}]
100 | === {{ obj.name }}
101 |
102 | Library class type: `{{object_prefix}}{{ obj.name }}`
103 | {% if obj.description is not none %}
104 | {{ obj.description }}
105 | {% endif %}
106 | {{ obj_attributes(obj) }}
107 | {%- if obj.has_backref -%}
108 | {{ obj_backrefs(obj) }}
109 | {%- endif -%}
110 | {%- endfor %}
111 |
--------------------------------------------------------------------------------
/docs/modules/overview/pages/overview.adoc:
--------------------------------------------------------------------------------
1 | = Overview
2 |
3 | == Purpose
4 |
5 | *RacksDB* is an _open source_ solution for modeling your datacenters
6 | infrastructures. It provides a simple database schema to store information about
7 | the equipments in your datacenters, with tools and library to request this
8 | database. This database can be used as *reference source* for many purposes in
9 | IT management.
10 |
11 | image::overview:racksdb_overview.png[RacksDB Overview]
12 |
13 | * **Inventory**: Get permanent reference list of equipments charactistics and
14 | enable conformity testing.
15 | * **Automation**: Adopt Infrastructure-as-Code by coupling generic configuration
16 | management logic with infrastructures model.
17 | * **Continuous deployment**: Define advanced deployment rules based on nodes
18 | characteristics declared in RacksDB.
19 | * **Monitoring**: Synchronize monitoring services with equipments database and
20 | define dynamic dashboard.
21 | * **Documentation**: Get updated architecture diagrams and define generic
22 | adaptative procedures.
23 |
24 | :wiki-cmdb: https://en.wikipedia.org/wiki/Configuration_management_database
25 | :wiki-dcim: https://en.wikipedia.org/wiki/Data_center_management#Data_center_infrastructure_management
26 |
27 | Generally speaking, *RacksDB* is a specialized
28 | {wiki-cmdb}[CMDB]footnote:[Configuration management database] dedicated
29 | to {wiki-dcim}[DCIM]footnote:[Data center-infrastructure management].
30 |
31 | Compared to others products, *RacksDB* can be considered lightweight
32 | alternative to more advanced tools such as https://netbox.dev/[NetBox] and
33 | https://www.racktables.org/[RackTables]. Discover the xref:#characteristics[main
34 | characteristics] that makes it unique among other solutions.
35 |
36 | [#characteristics]
37 | == Characteristics
38 |
39 | The main characteristics of *RacksDB* are the following:
40 |
41 | * **YAML based.** The data are stored in plain files using human-readable YAML
42 | format, easily managed with Git.
43 | * **Simple and pragmatic.** The database schema is intuitive and practical,
44 | infrastructures can be described in few minutes.
45 | * **Tag-based.** Easy filtering of data and equipments based on associated tags.
46 | * **Decentralized architecture.** RacksDB can be deployed without central
47 | server, just by replicating few plain YAML files.
48 | * **Extensibility.** In addition to RacksDB simple format, custom schema
49 | extensions can be defined to store specific data.
50 |
51 | == Interfaces
52 |
53 | image::overview:racksdb_interfaces.png[RacksDB Interfaces]
54 |
55 | Once the database is defined, data can be searched and extracted using the
56 | xref:usage:racksdb.adoc[`racksdb` CLI]. A xref:usage:ui.adoc[Web UI] is also
57 | available to explore its content.
58 |
59 | image::overview:racksdb_web_ui_screenshots.webp[RacksDB Web UI,width=600]
60 |
61 | For full integration with your software, a xref:usage:lib.adoc[Python library]
62 | and a xref:usage:rest.adoc[REST API] can also be used to stay continuously
63 | synchronized with database content.
64 |
65 | [sidebar]
66 | --
67 | .More links
68 | * xref:usage:racksdb.adoc[`racksdb` command manual]
69 | * xref:usage:ui.adoc[Web UI user guide] with screenshots and features list
70 | * xref:usage:lib.adoc[Python Library reference] documentation with examples
71 | * xref:usage:rest.adoc[REST API reference] documentation with examples
72 | --
73 |
74 | == Diagrams
75 |
76 | RacksDB can be used to generate diagrams of your datacenter rooms and the racks
77 | with all their equipments, including _axonometric views_ for 3D representations.
78 |
79 | image::overview:racksdb_diagrams.png[RacksDB Diagrams,xref=image$racksdb_diagrams.svg]
80 |
81 | These diagrams can be generated in various formats (PNG, SVG, PDF) and be
82 | customized with many xref:usage:drawparams.adoc[drawing parameters].
83 |
84 | [sidebar]
85 | --
86 | .More links
87 | * xref:usage:racksdb.adoc#draw[`racksdb` CLI drawing feature]
88 | * xref:usage:drawparams.adoc[Drawing parameters howto and reference
89 | documentation]
90 | --
91 |
--------------------------------------------------------------------------------
/frontend/src/views/DatacentersView.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
117 |
118 |
--------------------------------------------------------------------------------
/frontend/tests/components/InfrastructureFilters.spec.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2025 Rackslab
3 |
4 | This file is part of RacksDB.
5 |
6 | SPDX-License-Identifier: MIT
7 | */
8 |
9 | import { describe, test, expect } from 'vitest'
10 | import { mount } from '@vue/test-utils'
11 | import { nextTick } from 'vue'
12 |
13 | import InfrastructureFilters from '@/components/InfrastructureFilters.vue'
14 |
15 | describe('InfrastructureFilters', () => {
16 | test('renders filter sections when slider is shown', async () => {
17 | const wrapper = mount(InfrastructureFilters, {
18 | props: {
19 | showSlider: true,
20 | racks: ['R01', 'R02'],
21 | equipmentCategories: ['nodes', 'storage'],
22 | equipmentTypes: ['server', 'switch'],
23 | tags: ['prod', 'test'],
24 | selectedRacks: [],
25 | selectedEquipmentTypes: [],
26 | selectedCategories: [],
27 | selectedTags: [],
28 | inputEquipmentName: ''
29 | }
30 | })
31 |
32 | await nextTick()
33 |
34 | // Test that the dialog component exists (even if not visible due to teleport)
35 | expect(wrapper.findComponent({ name: 'Dialog' }).exists()).toBe(true)
36 |
37 | // Test that the component renders without errors
38 | expect(wrapper.exists()).toBe(true)
39 |
40 | // Test that the props are passed correctly
41 | expect(wrapper.props('showSlider')).toBe(true)
42 | expect(wrapper.props('racks')).toEqual(['R01', 'R02'])
43 | expect(wrapper.props('equipmentCategories')).toEqual(['nodes', 'storage'])
44 | expect(wrapper.props('equipmentTypes')).toEqual(['server', 'switch'])
45 | expect(wrapper.props('tags')).toEqual(['prod', 'test'])
46 | })
47 |
48 | test('filters racks based on query', async () => {
49 | const wrapper = mount(InfrastructureFilters, {
50 | props: {
51 | showSlider: true,
52 | racks: ['R01', 'R02', 'R10'],
53 | equipmentCategories: [],
54 | equipmentTypes: [],
55 | tags: [],
56 | selectedRacks: [],
57 | selectedEquipmentTypes: [],
58 | selectedCategories: [],
59 | selectedTags: [],
60 | inputEquipmentName: ''
61 | }
62 | })
63 |
64 | await nextTick()
65 |
66 | // Set query to filter racks
67 | wrapper.vm.queryRacks = 'R0'
68 | await nextTick()
69 |
70 | const filteredRacks = wrapper.vm.filteredRacks
71 | expect(filteredRacks).toEqual(['R01', 'R02'])
72 | })
73 |
74 | test('filters equipment types based on query', async () => {
75 | const wrapper = mount(InfrastructureFilters, {
76 | props: {
77 | showSlider: true,
78 | racks: [],
79 | equipmentCategories: [],
80 | equipmentTypes: ['server', 'switch', 'storage'],
81 | tags: [],
82 | selectedRacks: [],
83 | selectedEquipmentTypes: [],
84 | selectedCategories: [],
85 | selectedTags: [],
86 | inputEquipmentName: ''
87 | }
88 | })
89 |
90 | await nextTick()
91 |
92 | // Set query to filter equipment types
93 | wrapper.vm.queryEquipmentTypes = 'ser'
94 | await nextTick()
95 |
96 | const filteredEquipmentTypes = wrapper.vm.filteredEquipmentTypes
97 | expect(filteredEquipmentTypes).toEqual(['server'])
98 | })
99 |
100 | test('emits toggleSlider when close button is clicked', async () => {
101 | const wrapper = mount(InfrastructureFilters, {
102 | props: {
103 | showSlider: true,
104 | racks: [],
105 | equipmentCategories: [],
106 | equipmentTypes: [],
107 | tags: [],
108 | selectedRacks: [],
109 | selectedEquipmentTypes: [],
110 | selectedCategories: [],
111 | selectedTags: [],
112 | inputEquipmentName: ''
113 | }
114 | })
115 |
116 | await nextTick()
117 |
118 | // Test that the component renders without errors
119 | expect(wrapper.exists()).toBe(true)
120 |
121 | // Test that the component can emit toggleSlider event
122 | await wrapper.vm.$emit('toggleSlider')
123 | expect(wrapper.emitted('toggleSlider')).toBeTruthy()
124 | })
125 | })
126 |
--------------------------------------------------------------------------------
/frontend/src/components/HomeViewCards.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
32 |
33 |
34 |
35 |
38 |
39 |
40 |
43 | {{ datacenters.length }} datacenter
44 | s
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {{ datacenter.name }}
56 |
57 |
58 |
59 |
{{ datacenter.rooms.length }} rooms
60 |
{{ datacenter.rooms.length }} room
61 |
62 |
63 |
64 | #{{ tag }}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
78 |
79 |
80 |
83 | {{ infrastructures.length }} infrastructure
84 | s
85 |
86 |
87 |
88 |
89 |
90 |
91 |
94 | {{ infrastructure.name }}
98 |
99 |
100 |
101 |
{{ infrastructure.description }}
102 |
103 |
104 |
105 | #{{ tag }}
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/docs/update-materials:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | #
3 | # Copyright (c) 2024 Rackslab
4 | #
5 | # This file is part of RacksDB.
6 | #
7 | # SPDX-License-Identifier: GPL-3.0-or-later
8 |
9 | import sys
10 | import yaml
11 | from pathlib import Path
12 |
13 | from rfl.build.ninja import NinjaBuilder
14 | from rfl.build.projectversion import project_version
15 |
16 | # Sizes names and associated DPI
17 | SVG_SIZES = {"tiny": 32, "small": 64, "medium": 96, "large": 128, "xlarge": 192}
18 | TARGETS = ["all", "man", "schema-ref", "drawing-ref", "openapi", "png", "clean"]
19 | MANPAGE_SOURCE = f"RacksDB {project_version()}"
20 | DOCS_DIR = Path(__file__).parent
21 | RACKSDB_SCHEMA = DOCS_DIR.parent / "schemas/racksdb.yml"
22 | RACKSDB_REF = DOCS_DIR / "modules/db/partials/objects.adoc"
23 | DRAWINGS_SCHEMA = DOCS_DIR.parent / "schemas/drawings.yml"
24 | DRAWINGS_REF = DOCS_DIR / "modules/usage/partials/drawing-objects.adoc"
25 | OPENAPI = DOCS_DIR / "modules/usage/attachments/openapi.yml"
26 |
27 |
28 | def main():
29 | targets = sys.argv[1:]
30 |
31 | if any([target not in TARGETS for target in targets]):
32 | print(f"Supported targets are: {TARGETS}")
33 | sys.exit(1)
34 |
35 | if not (len(targets)):
36 | targets = ["all"]
37 |
38 | builder = NinjaBuilder()
39 |
40 | with open(DOCS_DIR / "utils/build.yaml") as fh:
41 | rules = yaml.safe_load(fh.read())
42 |
43 | # clean target
44 | if "clean" in targets:
45 | manpages = (DOCS_DIR / "man").glob("*.1")
46 | for manpage in manpages:
47 | print(f"Removing manpage {manpage}")
48 | manpage.unlink()
49 | generated_pngs = [
50 | (DOCS_DIR / svg_s).with_suffix(".png") for svg_s in rules["diagrams"].keys()
51 | ]
52 | for generated_png in generated_pngs:
53 | print(f"Removing generated PNG image {generated_png}")
54 | generated_png.unlink()
55 | print(f"Removing RacksDB schema reference documentation {RACKSDB_REF}")
56 | RACKSDB_REF.unlink()
57 | print(f"Removing RacksDB drawing schema reference documentation {DRAWINGS_REF}")
58 | DRAWINGS_REF.unlink()
59 | print(f"Removing OpenAPI description {OPENAPI}")
60 | OPENAPI.unlink()
61 |
62 | for size, dpi in SVG_SIZES.items():
63 | builder.rule(
64 | name=f"png-{size}",
65 | command=(
66 | f"inkscape $in --export-type=png --export-overwrite --export-dpi={dpi} "
67 | "--export-filename=$out"
68 | ),
69 | )
70 |
71 | builder.rule(
72 | name="manpage",
73 | command=(
74 | f"asciidoctor --backend manpage --attribute mansource='{MANPAGE_SOURCE}' "
75 | "$in"
76 | ),
77 | )
78 |
79 | builder.rule(name="schema-ref", command="python3 utils/schema-objs.py > $out")
80 |
81 | builder.rule(
82 | name="drawing-ref", command="python3 utils/drawings-schema-objs.py > $out"
83 | )
84 | builder.rule(name="openapi", command="python3 utils/gen-openapi.py > $out")
85 |
86 | if "man" in targets or "all" in targets:
87 | manpages = (DOCS_DIR / "man").glob("*.adoc")
88 | for manpage in manpages:
89 | implicit = DOCS_DIR / "modules/usage/pages" / manpage.name
90 | target = (DOCS_DIR / "man" / manpage.stem).with_suffix(".1")
91 | builder.build(
92 | outputs=[target], rule="manpage", inputs=[manpage], implicit=[implicit]
93 | )
94 |
95 | if "schema-ref" in targets or "all" in targets:
96 | builder.build(
97 | outputs=[RACKSDB_REF], rule="schema-ref", implicit=[RACKSDB_SCHEMA]
98 | )
99 |
100 | if "drawing-ref" in targets or "all" in targets:
101 | builder.build(
102 | outputs=[DRAWINGS_REF], rule="drawing-ref", implicit=[DRAWINGS_SCHEMA]
103 | )
104 |
105 | if "openapi" in targets or "all" in targets:
106 | builder.build(
107 | outputs=[OPENAPI],
108 | rule="openapi",
109 | implicit=[RACKSDB_SCHEMA, DRAWINGS_SCHEMA],
110 | )
111 |
112 | if "png" in targets or "all" in targets:
113 | svg_rules = rules["diagrams"]
114 | for svg_s, size in svg_rules.items():
115 | svg = DOCS_DIR / svg_s
116 | png = svg.with_suffix(".png")
117 | builder.build(outputs=[png], rule=f"png-{size}", inputs=[svg])
118 |
119 | builder.run()
120 |
121 |
122 | if __name__ == "__main__":
123 | main()
124 |
--------------------------------------------------------------------------------