├── debian
├── docs
├── source
│ └── format
├── proxmox-datacenter-manager-docs.install
├── proxmox-datacenter-manager-docs.lintian-overrides
├── pdm-enterprise.sources
├── proxmox-datacenter-manager.lintian-overrides
├── proxmox-datacenter-manager-client.install
├── proxmox-datacenter-manager.prerm
├── scripts
│ └── elf-strip-unused-dependencies.sh
├── proxmox-datacenter-manager-docs.links
├── copyright
├── proxmox-datacenter-manager.install
├── rules
└── proxmox-datacenter-manager.postinst
├── rustfmt.toml
├── ui
├── debian
│ ├── docs
│ ├── source
│ │ └── format
│ ├── scripts
│ │ └── elf-strip-unused-dependencies.sh
│ ├── copyright
│ ├── rules
│ └── control
├── src
│ ├── sdn
│ │ ├── mod.rs
│ │ └── evpn
│ │ │ └── mod.rs
│ ├── remotes
│ │ ├── firewall
│ │ │ └── mod.rs
│ │ └── mod.rs
│ ├── widget
│ │ ├── mod.rs
│ │ ├── view_selector.rs
│ │ └── remote_endpoint_selector.rs
│ ├── view_list_context.rs
│ ├── search_provider.rs
│ ├── load_result.rs
│ ├── dashboard
│ │ ├── mod.rs
│ │ └── resource_tree.rs
│ ├── configuration
│ │ └── permission_path_selector.rs
│ ├── pbs
│ │ └── datastore.rs
│ ├── certificates.rs
│ └── pve
│ │ └── remote
│ │ └── mod.rs
├── css
│ ├── crisp-yew-style.scss
│ ├── material-yew-style.scss
│ ├── desktop-yew-style.scss
│ └── pdm.scss
├── README.md
├── images
│ ├── icon-sdn-vnet.svg
│ ├── icon-cd-drive.svg
│ ├── icon-memory.svg
│ ├── favicon.svg
│ ├── icon-cpu.svg
│ ├── icon-sdn.svg
│ ├── proxmox_logo_white.svg
│ └── proxmox_logo.svg
├── Trunk.toml
├── index.html
├── index.hbs
└── Cargo.toml
├── lib
├── pdm-ui-shared
│ ├── src
│ │ └── lib.rs
│ └── Cargo.toml
├── pdm-buildcfg
│ ├── Cargo.toml
│ ├── build.rs
│ └── src
│ │ └── lib.rs
├── pdm-search
│ └── Cargo.toml
├── pdm-api-types
│ ├── src
│ │ ├── metric_collection.rs
│ │ ├── user.rs
│ │ ├── proxy.rs
│ │ ├── pbs.rs
│ │ ├── translation.rs
│ │ └── node_config.rs
│ └── Cargo.toml
├── pdm-client
│ └── Cargo.toml
└── pdm-config
│ ├── Cargo.toml
│ └── src
│ ├── views.rs
│ ├── lib.rs
│ ├── node.rs
│ └── setup.rs
├── docs
├── images
│ ├── favicon.ico
│ ├── proxmox-logo.png
│ └── screenshots
│ │ ├── custom-view.png
│ │ ├── login-window.png
│ │ ├── remote-node-shell.png
│ │ ├── all-remotes-firewall.png
│ │ ├── all-remotes-updates.png
│ │ ├── remote-node-overview.png
│ │ └── global-overview-dashboard.png
├── proxmox-datacenter-manager-admin
│ ├── description.rst
│ └── man1.rst
├── todos.rst
├── faq-release-support-table.csv
├── proxmox-datacenter-manager-client
│ ├── description.rst
│ └── man1.rst
├── _templates
│ ├── sidebar-header.html
│ └── index-sidebar.html
├── proxmox-datacenter-api
│ ├── description.rst
│ └── man1.rst
├── _static
│ └── custom.js
├── services.rst
├── command-syntax.rst
├── command-line-tools.rst
├── config
│ ├── remotes
│ │ └── man5.rst
│ └── views
│ │ └── man5.rst
├── configuration-files.rst
├── proxmox-datacenter-privileged-api
│ ├── description.rst
│ └── man1.rst
├── api-viewer
│ ├── index.html
│ └── Makefile
├── dev
│ ├── debug-features.md
│ └── pdm-permission-system.md
├── pdm-copyright.rst
├── epilog.rst
├── sysadmin.rst
├── index.rst
├── system-requirements.rst
├── views.rst
├── remotes.rst
├── Makefile
├── installation.rst
└── faq.rst
├── server
├── src
│ ├── test_support
│ │ ├── mod.rs
│ │ └── temp.rs
│ ├── bin
│ │ ├── proxmox-datacenter-api
│ │ │ └── tasks
│ │ │ │ ├── mod.rs
│ │ │ │ └── remote_updates.rs
│ │ ├── docgen.rs
│ │ └── proxmox-datacenter-manager-banner.rs
│ ├── env.rs
│ ├── acl.rs
│ ├── api
│ │ ├── sdn
│ │ │ └── mod.rs
│ │ ├── config
│ │ │ ├── access
│ │ │ │ └── mod.rs
│ │ │ ├── mod.rs
│ │ │ └── notes.rs
│ │ ├── nodes
│ │ │ ├── status.rs
│ │ │ ├── report.rs
│ │ │ ├── journal.rs
│ │ │ ├── syslog.rs
│ │ │ ├── time.rs
│ │ │ ├── rrddata.rs
│ │ │ ├── mod.rs
│ │ │ ├── dns.rs
│ │ │ └── sdn.rs
│ │ ├── metric_collection.rs
│ │ ├── pve
│ │ │ └── storage.rs
│ │ ├── pbs
│ │ │ └── node.rs
│ │ ├── mod.rs
│ │ └── rrd_common.rs
│ ├── task_utils.rs
│ ├── lib.rs
│ ├── resource_cache.rs
│ ├── context.rs
│ └── auth
│ │ ├── csrf.rs
│ │ ├── certs.rs
│ │ └── key.rs
└── Cargo.toml
├── .gitmodules
├── .cargo
└── config.toml
├── services
├── proxmox-datacenter-manager-daily-update.timer
├── proxmox-datacenter-manager-daily-update.service
├── Makefile
├── proxmox-datacenter-manager-banner.service
├── proxmox-datacenter-privileged-api.service
└── proxmox-datacenter-api.service
├── cli
├── proxmox-fido2
│ └── Cargo.toml
├── pdmAtoB
│ ├── Cargo.toml
│ └── src
│ │ └── main.rs
├── client
│ ├── src
│ │ ├── upid.rs
│ │ ├── time.rs
│ │ └── metric_collection.rs
│ └── Cargo.toml
├── admin
│ ├── Cargo.toml
│ └── src
│ │ ├── support_status.rs
│ │ └── main.rs
└── completions
│ └── Makefile
├── .gitignore
├── defines.mk
├── visualize_rrd.py
└── README.md
/debian/docs:
--------------------------------------------------------------------------------
1 | debian/SOURCE
2 |
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | edition = "2021"
2 |
--------------------------------------------------------------------------------
/ui/debian/docs:
--------------------------------------------------------------------------------
1 | debian/SOURCE
2 |
--------------------------------------------------------------------------------
/debian/source/format:
--------------------------------------------------------------------------------
1 | 3.0 (native)
2 |
--------------------------------------------------------------------------------
/ui/debian/source/format:
--------------------------------------------------------------------------------
1 | 3.0 (native)
2 |
--------------------------------------------------------------------------------
/lib/pdm-ui-shared/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod colors;
2 |
--------------------------------------------------------------------------------
/ui/src/sdn/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod evpn;
2 |
3 | mod zone_tree;
4 | pub use zone_tree::ZoneTree;
5 |
--------------------------------------------------------------------------------
/ui/css/crisp-yew-style.scss:
--------------------------------------------------------------------------------
1 | @import "../pwt-assets/scss/crisp-yew-style";
2 | @import "pdm";
3 |
--------------------------------------------------------------------------------
/ui/css/material-yew-style.scss:
--------------------------------------------------------------------------------
1 | @import "../pwt-assets/scss/material-yew-style";
2 | @import "pdm";
3 |
4 |
--------------------------------------------------------------------------------
/docs/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxmox/proxmox-datacenter-manager/HEAD/docs/images/favicon.ico
--------------------------------------------------------------------------------
/docs/images/proxmox-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxmox/proxmox-datacenter-manager/HEAD/docs/images/proxmox-logo.png
--------------------------------------------------------------------------------
/server/src/test_support/mod.rs:
--------------------------------------------------------------------------------
1 | #[cfg(remote_config = "faked")]
2 | pub mod fake_remote;
3 |
4 | #[cfg(test)]
5 | pub mod temp;
6 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "ui/pwt-assets"]
2 | path = ui/pwt-assets
3 | url = git://git.proxmox.com/git/ui/proxmox-yew-widget-toolkit-assets.git
4 |
--------------------------------------------------------------------------------
/docs/images/screenshots/custom-view.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxmox/proxmox-datacenter-manager/HEAD/docs/images/screenshots/custom-view.png
--------------------------------------------------------------------------------
/docs/images/screenshots/login-window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxmox/proxmox-datacenter-manager/HEAD/docs/images/screenshots/login-window.png
--------------------------------------------------------------------------------
/docs/proxmox-datacenter-manager-admin/description.rst:
--------------------------------------------------------------------------------
1 | This tool exposes some of the datacenter managers administrative API on the
2 | command line.
3 |
--------------------------------------------------------------------------------
/docs/images/screenshots/remote-node-shell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxmox/proxmox-datacenter-manager/HEAD/docs/images/screenshots/remote-node-shell.png
--------------------------------------------------------------------------------
/.cargo/config.toml:
--------------------------------------------------------------------------------
1 | [source]
2 | [source.debian-packages]
3 | directory = "/usr/share/cargo/registry"
4 | [source.crates-io]
5 | replace-with = "debian-packages"
6 |
--------------------------------------------------------------------------------
/docs/images/screenshots/all-remotes-firewall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxmox/proxmox-datacenter-manager/HEAD/docs/images/screenshots/all-remotes-firewall.png
--------------------------------------------------------------------------------
/docs/images/screenshots/all-remotes-updates.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxmox/proxmox-datacenter-manager/HEAD/docs/images/screenshots/all-remotes-updates.png
--------------------------------------------------------------------------------
/docs/images/screenshots/remote-node-overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxmox/proxmox-datacenter-manager/HEAD/docs/images/screenshots/remote-node-overview.png
--------------------------------------------------------------------------------
/server/src/bin/proxmox-datacenter-api/tasks/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod logrotate;
2 |
3 | pub mod remote_node_mapping;
4 | pub mod remote_tasks;
5 | pub mod remote_updates;
6 |
--------------------------------------------------------------------------------
/debian/proxmox-datacenter-manager-docs.install:
--------------------------------------------------------------------------------
1 | usr/share/doc/proxmox-datacenter-manager/html
2 | usr/share/doc/proxmox-datacenter-manager/proxmox-datacenter-manager.pdf
3 |
--------------------------------------------------------------------------------
/docs/images/screenshots/global-overview-dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/proxmox/proxmox-datacenter-manager/HEAD/docs/images/screenshots/global-overview-dashboard.png
--------------------------------------------------------------------------------
/docs/todos.rst:
--------------------------------------------------------------------------------
1 | Documentation Todo List
2 | =======================
3 |
4 | This is an auto-generated list of the todo references in the documentation.
5 |
6 | .. todolist::
7 |
--------------------------------------------------------------------------------
/debian/proxmox-datacenter-manager-docs.lintian-overrides:
--------------------------------------------------------------------------------
1 | proxmox-datacenter-manager-docs: embedded-javascript-library please use * [usr/share/doc/proxmox-datacenter-manager/html/_static/*.js]
2 |
--------------------------------------------------------------------------------
/debian/pdm-enterprise.sources:
--------------------------------------------------------------------------------
1 | Types: deb
2 | URIs: https://enterprise.proxmox.com/debian/pdm
3 | Suites: trixie
4 | Components: pdm-enterprise
5 | Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg
6 |
--------------------------------------------------------------------------------
/docs/faq-release-support-table.csv:
--------------------------------------------------------------------------------
1 | Proxmox Datacenter Manager , Debian Version , First Release , Debian EOL , Proxmox Datacenter Manager EOL
2 | Proxmox Datacenter Manager 1 , Debian 13 (Trixie) , 2025-12 , TBA , TBA
3 |
--------------------------------------------------------------------------------
/docs/proxmox-datacenter-manager-client/description.rst:
--------------------------------------------------------------------------------
1 | This tool implements a datacenter manager client, i.e. it can connect to any datacenter manager with
2 | the same major version and issue management commands, query status and control remotes and their
3 | resources.
4 |
--------------------------------------------------------------------------------
/debian/proxmox-datacenter-manager.lintian-overrides:
--------------------------------------------------------------------------------
1 | package-installs-apt-sources [etc/apt/sources.list.d/pdm-enterprise.sources]
2 | uses-dpkg-database-directly [usr/libexec/proxmox/proxmox-datacenter-api]
3 | uses-dpkg-database-directly [usr/libexec/proxmox/proxmox-datacenter-privileged-api]
4 |
--------------------------------------------------------------------------------
/lib/pdm-buildcfg/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pdm-buildcfg"
3 | version.workspace = true
4 | authors.workspace = true
5 | edition.workspace = true
6 | description = "macros used for PDM related paths such as configdir and rundir"
7 | build = "build.rs"
8 |
9 | [dependencies]
10 |
--------------------------------------------------------------------------------
/services/proxmox-datacenter-manager-daily-update.timer:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Daily Proxmox Datacenter Manager update and maintenance activities
3 |
4 | [Timer]
5 | OnCalendar=*-*-* 1:00
6 | RandomizedDelaySec=5h
7 | Persistent=true
8 |
9 | [Install]
10 | WantedBy=timers.target
11 |
--------------------------------------------------------------------------------
/docs/_templates/sidebar-header.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lib/pdm-search/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pdm-search"
3 | description = "Simple search query language for the PDM web UI"
4 | homepage = "https://www.proxmox.com"
5 | version = "0.2.0"
6 |
7 | edition.workspace = true
8 | license.workspace = true
9 | repository.workspace = true
10 |
--------------------------------------------------------------------------------
/debian/proxmox-datacenter-manager-client.install:
--------------------------------------------------------------------------------
1 | usr/bin/proxmox-datacenter-manager-client
2 | usr/share/bash-completion/completions/proxmox-datacenter-manager-client.bc
3 | usr/share/man/man1/proxmox-datacenter-manager-client.1
4 | usr/share/zsh/vendor-completions/_proxmox-datacenter-manager-client
5 |
--------------------------------------------------------------------------------
/docs/proxmox-datacenter-api/description.rst:
--------------------------------------------------------------------------------
1 | This daemon exposes the whole Proxmox Datacenter Manager API on TCP port 8443 using HTTPS. It runs
2 | as user ``www-data`` and has very limited permissions. Operations requiring more permissions are
3 | forwarded to the local ``proxmox-datacenter-privileged-api`` service.
4 |
--------------------------------------------------------------------------------
/server/src/env.rs:
--------------------------------------------------------------------------------
1 | pub fn sanitize_environment_vars() {
2 | std::env::set_var("PATH", "/sbin:/bin:/usr/sbin:/usr/bin");
3 | // Make %ENV safer - as suggested by https://perldoc.perl.org/perlsec.html
4 | for name in &["IFS", "CDPATH", "ENV", "BASH_ENV"] {
5 | std::env::remove_var(name);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/services/proxmox-datacenter-manager-daily-update.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Daily Proxmox Datacenter Manager update and maintenance activities
3 | After=network-online.target
4 | Wants=network-online.target
5 |
6 | [Service]
7 | Type=oneshot
8 | ExecStart=/usr/libexec/proxmox/proxmox-datacenter-manager-daily-update
9 |
--------------------------------------------------------------------------------
/lib/pdm-ui-shared/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pdm-ui-shared"
3 | description = "Proxmox Datacenter Manager shared ui modules"
4 | homepage = "https://www.proxmox.com"
5 |
6 | version.workspace = true
7 | edition.workspace = true
8 | license.workspace = true
9 | repository.workspace = true
10 |
11 | [dependencies]
12 |
--------------------------------------------------------------------------------
/docs/_static/custom.js:
--------------------------------------------------------------------------------
1 | window.addEventListener('DOMContentLoaded', (event) => {
2 | let activeSection = document.querySelector("a.current");
3 | if (activeSection) {
4 | // https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
5 | activeSection.scrollIntoView({ block: 'center' });
6 | }
7 | });
8 |
--------------------------------------------------------------------------------
/server/src/acl.rs:
--------------------------------------------------------------------------------
1 | pub(crate) fn init() {
2 | static ACCESS_CONTROL_CONFIG: pdm_api_types::AccessControlConfig =
3 | pdm_api_types::AccessControlConfig;
4 |
5 | proxmox_access_control::init::init(&ACCESS_CONTROL_CONFIG, pdm_buildcfg::configdir!("/access"))
6 | .expect("failed to setup access control config");
7 | }
8 |
--------------------------------------------------------------------------------
/docs/proxmox-datacenter-manager-admin/man1.rst:
--------------------------------------------------------------------------------
1 | ================================
2 | proxmox-datacenter-manager-admin
3 | ================================
4 |
5 | Synopsis
6 | ========
7 |
8 | .. include:: synopsis.rst
9 |
10 | Description
11 | ============
12 |
13 | .. include:: description.rst
14 |
15 |
16 | .. include:: ../pdm-copyright.rst
17 |
--------------------------------------------------------------------------------
/docs/proxmox-datacenter-manager-client/man1.rst:
--------------------------------------------------------------------------------
1 | =================================
2 | proxmox-datacenter-manager-client
3 | ================================
4 |
5 | Synopsis
6 | ==========
7 |
8 | .. include:: synopsis.rst
9 |
10 | Description
11 | ============
12 |
13 | .. include:: description.rst
14 |
15 |
16 | .. include:: ../pdm-copyright.rst
17 |
--------------------------------------------------------------------------------
/docs/services.rst:
--------------------------------------------------------------------------------
1 | Service Daemons
2 | ---------------
3 |
4 | ``proxmox-datacenter-api``
5 | ~~~~~~~~~~~~~~~~~~~~~~~~~~
6 |
7 | .. include:: proxmox-datacenter-api/description.rst
8 |
9 |
10 | ``proxmox-datacenter-privileged-api``
11 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
12 |
13 | .. include:: proxmox-datacenter-privileged-api/description.rst
14 |
15 |
--------------------------------------------------------------------------------
/docs/command-syntax.rst:
--------------------------------------------------------------------------------
1 | Command Syntax
2 | ==============
3 |
4 | ``proxmox-datacenter-manager-admin``
5 | ------------------------------------
6 |
7 | .. include:: proxmox-datacenter-manager-admin/synopsis.rst
8 |
9 |
10 | ``proxmox-datacenter-manager-client``
11 | -------------------------------------
12 |
13 | .. include:: proxmox-datacenter-manager-client/synopsis.rst
14 |
--------------------------------------------------------------------------------
/cli/proxmox-fido2/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "proxmox-fido2"
3 | description = "Highlevel API to libfido2 with dynamic loading of its library."
4 | homepage = "https://www.proxmox.com"
5 | version = "1.0.0"
6 |
7 | edition.workspace = true
8 | license.workspace = true
9 | repository.workspace = true
10 |
11 | [dependencies]
12 | libc.workspace = true
13 | log.workspace = true
14 |
--------------------------------------------------------------------------------
/docs/command-line-tools.rst:
--------------------------------------------------------------------------------
1 | Command-line Tools
2 | ------------------
3 |
4 | ``proxmox-datacenter-manager-client``
5 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6 |
7 | .. include:: proxmox-datacenter-manager-client/description.rst
8 |
9 | ``proxmox-datacenter-manager-admin``
10 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11 |
12 | .. include:: proxmox-datacenter-manager-admin/description.rst
13 |
14 |
--------------------------------------------------------------------------------
/docs/_templates/index-sidebar.html:
--------------------------------------------------------------------------------
1 | Navigation
2 | {{ toctree(includehidden=theme_sidebar_includehidden, collapse=True, titles_only=True) }}
3 | {% if theme_extra_nav_links %}
4 |
5 | Links
6 |
7 | {% for text, uri in theme_extra_nav_links.items() %}
8 | - {{ text }}
9 | {% endfor %}
10 |
11 | {% endif %}
12 |
--------------------------------------------------------------------------------
/docs/config/remotes/man5.rst:
--------------------------------------------------------------------------------
1 | ===========
2 | remotes.cfg
3 | ===========
4 |
5 | Description
6 | ===========
7 |
8 | The file ``/etc/proxmox-datacenter-manager/remotes.cfg`` is a configuration
9 | file for Proxmox Datacenter Manager. It contains all Proxmox VE and Proxmox
10 | Backup Server remotes that the PDM instances manages.
11 |
12 | Options
13 | =======
14 |
15 | .. include:: config.rst
16 |
17 | .. include:: ../../pdm-copyright.rst
18 |
--------------------------------------------------------------------------------
/cli/pdmAtoB/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pdmAtoB"
3 | description = "Proxmox Datacenter Upgrade Checks"
4 | homepage = "https://www.proxmox.com"
5 | version.workspace = true
6 | authors.workspace = true
7 | edition.workspace = true
8 | license.workspace = true
9 | repository.workspace = true
10 | exclude.workspace = true
11 |
12 | [dependencies]
13 | anyhow.workspace = true
14 | proxmox-upgrade-checks.workspace = true
15 | pdm-buildcfg.workspace = true
16 |
--------------------------------------------------------------------------------
/docs/configuration-files.rst:
--------------------------------------------------------------------------------
1 | Configuration Files
2 | ===================
3 |
4 | The Proxmox Datacenter Manager configuration files are stored at
5 | ``/etc/proxmox-datacenter-manager/`` directory.
6 |
7 |
8 | ``remotes.cfg``
9 | ~~~~~~~~~~~~~~~
10 |
11 |
12 | Options
13 | ^^^^^^^
14 |
15 | .. include:: config/remotes/config.rst
16 |
17 | ``views.cfg``
18 | ~~~~~~~~~~~~~
19 |
20 | Options
21 | ^^^^^^^
22 |
23 | .. include:: config/views/config.rst
24 |
--------------------------------------------------------------------------------
/docs/config/views/man5.rst:
--------------------------------------------------------------------------------
1 | =========
2 | views.cfg
3 | =========
4 |
5 | Description
6 | ===========
7 |
8 | The file ``/etc/proxmox-datacenter-manager/views.cfg`` is a configuration file
9 | for Proxmox Datacenter Manager and is used to configure the customizable views.
10 | It contains the exclude-include filters and layout definition of each view
11 |
12 | Options
13 | =======
14 |
15 | .. include:: config.rst
16 |
17 | .. include:: ../../pdm-copyright.rst
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/*.rs.bk
2 | *.build
3 | *.buildinfo
4 | *.changes
5 | *.deb
6 | *.dsc
7 | *.tar.?z
8 | *~
9 | .create-shell-completions
10 | /docs/*/synopsis.rst
11 | /docs/_ext/__pycache__/
12 | /docs/api-viewer/*.css
13 | /docs/api-viewer/*.js
14 | /docs/api-viewer/*/*.png
15 | /docs/config/*/config.rst
16 | /docs/output
17 | /target
18 | /ui/dist
19 | /ui/proxmox-datacenter-manager-ui-[0-9].*/
20 | /ui/target
21 | Cargo.lock
22 | proxmox-datacenter-manager-[0-9].*/
23 |
--------------------------------------------------------------------------------
/docs/proxmox-datacenter-privileged-api/description.rst:
--------------------------------------------------------------------------------
1 | This daemon exposes the Proxmox Datacenter Manager management API through a restricted UNIX socket
2 | at ``/run/proxmox-datacenter-manager/priv.sock``.
3 | The daemon runs as ``root`` and has permission to do all privileged operations.
4 |
5 | NOTE: The daemon listens to a local UNIX socket address only, so you cannot access it from outside.
6 | The ``proxmox-datacenter-api`` daemon exposes the API to the outside world.
7 |
--------------------------------------------------------------------------------
/services/Makefile:
--------------------------------------------------------------------------------
1 | SERVICES := \
2 | proxmox-datacenter-api.service \
3 | proxmox-datacenter-privileged-api.service \
4 | proxmox-datacenter-manager-banner.service \
5 | proxmox-datacenter-manager-daily-update.service \
6 | proxmox-datacenter-manager-daily-update.timer \
7 |
8 | .PHONY: all
9 | all:
10 |
11 | .PHONY: install
12 | install: $(SERVICES)
13 | install -d $(DESTDIR)/usr/lib/systemd/system
14 | install -m 0644 $(SERVICES) $(DESTDIR)/usr/lib/systemd/system
15 |
--------------------------------------------------------------------------------
/debian/proxmox-datacenter-manager.prerm:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | #DEBHELPER#
6 |
7 | # modeled after dh_systemd_start output
8 | if [ -d /run/systemd/system ] && [ "$1" = remove ]; then
9 | deb-systemd-invoke stop 'proxmox-datacenter-api.service' \
10 | 'proxmox-datacenter-manager-banner.service' \
11 | 'proxmox-datacenter-manager-daily-update.timer' \
12 | 'proxmox-datacenter-privileged-api.service' \
13 | >/dev/null || true
14 | fi
15 |
--------------------------------------------------------------------------------
/debian/scripts/elf-strip-unused-dependencies.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | binary=$1
4 |
5 | exec 3< <(ldd -u "$binary" | grep -oP '[^/:]+$')
6 |
7 | patchargs=""
8 | dropped=""
9 | while read -r dep; do
10 | dropped="$dep $dropped"
11 | patchargs="--remove-needed $dep $patchargs"
12 | done <&3
13 | exec 3<&-
14 |
15 | if [[ $dropped == "" ]]; then
16 | exit 0
17 | fi
18 |
19 | echo -e "patchelf '$binary' - removing unused dependencies:\n $dropped"
20 | patchelf $patchargs $binary
21 |
--------------------------------------------------------------------------------
/ui/debian/scripts/elf-strip-unused-dependencies.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | binary=$1
4 |
5 | exec 3< <(ldd -u "$binary" | grep -oP '[^/:]+$')
6 |
7 | patchargs=""
8 | dropped=""
9 | while read -r dep; do
10 | dropped="$dep $dropped"
11 | patchargs="--remove-needed $dep $patchargs"
12 | done <&3
13 | exec 3<&-
14 |
15 | if [[ $dropped == "" ]]; then
16 | exit 0
17 | fi
18 |
19 | echo -e "patchelf '$binary' - removing unused dependencies:\n $dropped"
20 | patchelf $patchargs $binary
21 |
--------------------------------------------------------------------------------
/services/proxmox-datacenter-manager-banner.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Proxmox Datacenter Manager Login Banner
3 | ConditionPathExists=/usr/libexec/proxmox/proxmox-datacenter-manager-banner
4 | ConditionPathExists=!/usr/bin/pvebanner
5 | DefaultDependencies=no
6 | After=local-fs.target
7 | Before=console-getty.service
8 |
9 | [Service]
10 | ExecStart=/usr/libexec/proxmox/proxmox-datacenter-manager-banner
11 | Type=oneshot
12 | RemainAfterExit=yes
13 |
14 | [Install]
15 | WantedBy=getty.target
16 |
--------------------------------------------------------------------------------
/server/src/api/sdn/mod.rs:
--------------------------------------------------------------------------------
1 | use proxmox_router::{list_subdirs_api_method, Router, SubdirMap};
2 | use proxmox_sortable_macro::sortable;
3 |
4 | pub mod controllers;
5 | pub mod vnets;
6 | pub mod zones;
7 |
8 | #[sortable]
9 | pub const SUBDIRS: SubdirMap = &sorted!([
10 | ("controllers", &controllers::ROUTER),
11 | ("vnets", &vnets::ROUTER),
12 | ("zones", &zones::ROUTER),
13 | ]);
14 |
15 | pub const ROUTER: Router = Router::new()
16 | .get(&list_subdirs_api_method!(SUBDIRS))
17 | .subdirs(SUBDIRS);
18 |
--------------------------------------------------------------------------------
/services/proxmox-datacenter-privileged-api.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Proxmox Datacenter Manager privileged API daemon
3 | Wants=network-online.target
4 | After=network.target
5 |
6 | [Service]
7 | Type=notify
8 | ExecStartPre=/usr/libexec/proxmox/proxmox-datacenter-privileged-api setup
9 | ExecStart=/usr/libexec/proxmox/proxmox-datacenter-privileged-api
10 | ExecReload=/bin/kill -HUP $MAINPID
11 | PIDFile=/run/proxmox-datacenter-manager/priv.pid
12 | Restart=on-failure
13 |
14 | [Install]
15 | WantedBy=multi-user.target
16 |
--------------------------------------------------------------------------------
/defines.mk:
--------------------------------------------------------------------------------
1 | PREFIX = /usr
2 | BINDIR = $(PREFIX)/bin
3 | SBINDIR = $(PREFIX)/sbin
4 | LIBDIR = $(PREFIX)/lib
5 | LIBEXECDIR = $(PREFIX)/libexec
6 | BASHCOMPDIR = $(PREFIX)/share/bash-completion/completions
7 | ZSHCOMPDIR = $(PREFIX)/share/zsh/vendor-completions
8 | MAN1DIR = $(PREFIX)/share/man/man1
9 | MAN5DIR = $(PREFIX)/share/man/man5
10 | DOCDIR = $(PREFIX)/share/doc/proxmox-datacenter-manager
11 | SYSCONFDIR = /etc
12 | ZSH_COMPL_DEST = $(PREFIX)/share/zsh/vendor-completions
13 |
14 | # For local overrides
15 | -include local.mak
16 |
--------------------------------------------------------------------------------
/lib/pdm-buildcfg/build.rs:
--------------------------------------------------------------------------------
1 | // build.rs
2 | use std::env;
3 | use std::process::Command;
4 |
5 | fn main() {
6 | let repoid = match env::var("REPOID") {
7 | Ok(repoid) => repoid,
8 | Err(_) => match Command::new("git").args(["rev-parse", "HEAD"]).output() {
9 | Ok(output) => String::from_utf8(output.stdout).unwrap(),
10 | Err(err) => {
11 | panic!("git rev-parse failed: {}", err);
12 | }
13 | },
14 | };
15 |
16 | println!("cargo:rustc-env=REPOID={}", repoid);
17 | }
18 |
--------------------------------------------------------------------------------
/services/proxmox-datacenter-api.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description=Proxmox Datacenter Manager API daemon
3 | Wants=network-online.target
4 | After=network.target
5 | Wants=proxmox-datacenter-privileged-api.service
6 | After=proxmox-datacenter-privileged-api.service
7 |
8 | [Service]
9 | Type=notify
10 | ExecStart=/usr/libexec/proxmox/proxmox-datacenter-api
11 | ExecReload=/bin/kill -HUP $MAINPID
12 | PIDFile=/run/proxmox-datacenter-manager/api.pid
13 | Restart=on-failure
14 | User=www-data
15 | Group=www-data
16 |
17 | [Install]
18 | WantedBy=multi-user.target
19 |
--------------------------------------------------------------------------------
/server/src/api/config/access/mod.rs:
--------------------------------------------------------------------------------
1 | use proxmox_router::list_subdirs_api_method;
2 | use proxmox_router::{Router, SubdirMap};
3 | use proxmox_sortable_macro::sortable;
4 |
5 | mod ad;
6 | mod ldap;
7 | mod openid;
8 | pub mod tfa;
9 |
10 | #[sortable]
11 | const SUBDIRS: SubdirMap = &sorted!([
12 | ("tfa", &tfa::ROUTER),
13 | ("ldap", &ldap::ROUTER),
14 | ("openid", &openid::ROUTER),
15 | ("ad", &ad::ROUTER),
16 | ]);
17 |
18 | pub const ROUTER: Router = Router::new()
19 | .get(&list_subdirs_api_method!(SUBDIRS))
20 | .subdirs(SUBDIRS);
21 |
--------------------------------------------------------------------------------
/ui/README.md:
--------------------------------------------------------------------------------
1 | # Experimental Yew GUI for Proxmox Datacenter Manager
2 |
3 | # Testing
4 |
5 | 1.) start datacenter manager
6 |
7 | sudo ./target/debug/proxmox-datacenter-privileged-api
8 | sudo -u www-data ./target/debug/proxmox-datacenter-api
9 |
10 | 2.) either: start local trunk server
11 |
12 | trunk serve --proxy-backend=https://localhost:8443/api2/ --proxy-insecure
13 |
14 | then test with url: http://localhost:8080
15 |
16 | 2.) or: copy files into pdm js folder:
17 |
18 | make all
19 | make install
20 |
21 | And test with url https://localhost:8443
22 |
--------------------------------------------------------------------------------
/docs/api-viewer/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Proxmox Datacenter Manager API Documentation
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/server/src/api/config/mod.rs:
--------------------------------------------------------------------------------
1 | use proxmox_router::list_subdirs_api_method;
2 | use proxmox_router::{Router, SubdirMap};
3 | use proxmox_sortable_macro::sortable;
4 |
5 | pub mod access;
6 | pub mod acme;
7 | pub mod certificate;
8 | pub mod notes;
9 | pub mod views;
10 |
11 | #[sortable]
12 | const SUBDIRS: SubdirMap = &sorted!([
13 | ("access", &access::ROUTER),
14 | ("acme", &acme::ROUTER),
15 | ("certificate", &certificate::ROUTER),
16 | ("notes", ¬es::ROUTER),
17 | ("views", &views::ROUTER)
18 | ]);
19 |
20 | pub const ROUTER: Router = Router::new()
21 | .get(&list_subdirs_api_method!(SUBDIRS))
22 | .subdirs(SUBDIRS);
23 |
--------------------------------------------------------------------------------
/cli/pdmAtoB/src/main.rs:
--------------------------------------------------------------------------------
1 | use pdm_buildcfg::{PROXMOX_PKG_RELEASE, PROXMOX_PKG_VERSION};
2 | use proxmox_upgrade_checks::UpgradeCheckerBuilder;
3 |
4 | fn main() -> Result<(), anyhow::Error> {
5 | UpgradeCheckerBuilder::new(
6 | "bookworm",
7 | "trixie",
8 | "proxmox-datacenter-manager",
9 | 0,
10 | 1,
11 | 11,
12 | &format!("{PROXMOX_PKG_VERSION}.{PROXMOX_PKG_RELEASE}"),
13 | )
14 | .apt_state_file_location(pdm_buildcfg::APT_PKG_STATE_FN)
15 | .add_service_to_checks("proxmox-datacenter-api")
16 | .add_service_to_checks("proxmox-datacenter-privileged-api")
17 | .build()
18 | .run()
19 | }
20 |
--------------------------------------------------------------------------------
/debian/proxmox-datacenter-manager-docs.links:
--------------------------------------------------------------------------------
1 | /usr/share/doc/proxmox-datacenter-manager/proxmox-datacenter-manager.pdf /usr/share/doc/proxmox-datacenter-manager/html/proxmox-datacenter-manager.pdf
2 | /usr/share/fonts-font-awesome/ /usr/share/doc/proxmox-datacenter-manager/html/lto-barcode/font-awesome
3 | /usr/share/javascript/extjs /usr/share/doc/proxmox-datacenter-manager/html/api-viewer/extjs
4 | /usr/share/javascript/extjs /usr/share/doc/proxmox-datacenter-manager/html/lto-barcode/extjs
5 | /usr/share/javascript/extjs /usr/share/doc/proxmox-datacenter-manager/html/prune-simulator/extjs
6 | /usr/share/javascript/mathjax /usr/share/doc/proxmox-datacenter-manager/html/_static/mathjax
7 |
--------------------------------------------------------------------------------
/ui/css/desktop-yew-style.scss:
--------------------------------------------------------------------------------
1 | @import "../pwt-assets/scss/desktop-yew-style";
2 | @import "pdm";
3 |
4 | .proxmox-content-spacer {
5 | padding: var(--pwt-spacer-4);
6 | gap: var(--pwt-spacer-4);
7 |
8 | &.proxmox-content-spacer-with-one-child {
9 | padding: 0;
10 | }
11 |
12 | & > * {
13 | border: 0px !important;
14 | border-top: 1px solid var(--pwt-color-surface) !important;
15 | @include elevation-box-shadow(1);
16 | }
17 | }
18 |
19 | // TODO: move to pwt assets once we're sure about doing it this way or drop, if
20 | // we rewroked layouting and placement of the dropdown picker.
21 | .pwt-dropdown {
22 | max-height: min(45dvh, 500px) !important;
23 | }
24 |
--------------------------------------------------------------------------------
/lib/pdm-api-types/src/metric_collection.rs:
--------------------------------------------------------------------------------
1 | //! API types for metric collection.
2 |
3 | use serde::{Deserialize, Serialize};
4 |
5 | use proxmox_schema::api;
6 |
7 | #[api]
8 | #[derive(Clone, Deserialize, Serialize)]
9 | #[serde(rename_all = "kebab-case")]
10 | /// Per-remote collection status.
11 | pub struct MetricCollectionStatus {
12 | /// The remote's name.
13 | pub remote: String,
14 | /// Any error that occured during the last collection attempt.
15 | #[serde(skip_serializing_if = "Option::is_none")]
16 | pub error: Option,
17 | /// Timestamp of last successful collection.
18 | #[serde(skip_serializing_if = "Option::is_none")]
19 | pub last_collection: Option,
20 | }
21 |
--------------------------------------------------------------------------------
/docs/proxmox-datacenter-api/man1.rst:
--------------------------------------------------------------------------------
1 | :orphan:
2 |
3 | ======================
4 | proxmox-datacenter-api
5 | ======================
6 |
7 | Synopsis
8 | ========
9 |
10 | This daemon is normally started and managed as ``systemd`` service::
11 |
12 | systemctl start proxmox-datacenter-api
13 |
14 | systemctl stop proxmox-datacenter-api
15 |
16 | systemctl status proxmox-datacenter-api
17 |
18 | For debugging, you can start the daemon in foreground using::
19 |
20 | sudo -u www-data -g www-data proxmox-datacenter-api
21 |
22 | .. NOTE:: You need to stop the service before starting the daemon in foreground.
23 |
24 | Description
25 | ===========
26 |
27 | .. include:: description.rst
28 |
29 | .. include:: ../pdm-copyright.rst
30 |
--------------------------------------------------------------------------------
/docs/dev/debug-features.md:
--------------------------------------------------------------------------------
1 | # Debug features
2 |
3 | ## Faked remotes
4 |
5 | To enable an alternative implementation for reading remote configurations (`/etc/proxmox-datacenter-manager/remotes.cfg`)
6 | and creating API clients for these remotes, compile the project using:
7 |
8 | ```bash
9 | RUSTFLAGS='--cfg remote_config="faked"'
10 | ```
11 |
12 | This option is helpful for troubleshooting performance issues, especially when managing a large number of remotes
13 | or resources per remote.
14 |
15 | To use this feature, set the `PDM_FAKED_REMOTE_CONFIG` environment variable to the path of a valid
16 | JSON configuration file. For the expected structure, refer to the `FakeRemoteConfig` struct
17 | in `server/src/test_support/fake_remote.rs`.
18 |
--------------------------------------------------------------------------------
/ui/src/remotes/firewall/mod.rs:
--------------------------------------------------------------------------------
1 | mod columns;
2 | mod tree;
3 | mod types;
4 | mod ui_helpers;
5 |
6 | // Re-export public types
7 | pub use tree::FirewallTreeComponent;
8 |
9 | use std::rc::Rc;
10 | use yew::virtual_dom::{VComp, VNode};
11 | use yew::Properties;
12 |
13 | use proxmox_yew_comp::LoadableComponentMaster;
14 |
15 | #[derive(PartialEq, Properties)]
16 | pub struct FirewallTree {}
17 |
18 | impl FirewallTree {
19 | pub fn new() -> Self {
20 | yew::props!(Self {})
21 | }
22 | }
23 |
24 | impl From for VNode {
25 | fn from(value: FirewallTree) -> Self {
26 | let comp =
27 | VComp::new::>(Rc::new(value), None);
28 | VNode::from(comp)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/server/src/task_utils.rs:
--------------------------------------------------------------------------------
1 | use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
2 |
3 | /// Returns an [`Instant`] aligned to a certain boundary.
4 | ///
5 | /// For instance, `next_aligned_instant(60)` will return an [`Instant`] aligned to the next minute
6 | /// boundary.
7 | pub fn next_aligned_instant(seconds: u64) -> Instant {
8 | let now = SystemTime::now();
9 | let epoch_now = match now.duration_since(UNIX_EPOCH) {
10 | Ok(d) => d,
11 | Err(err) => {
12 | log::error!("task scheduler: computing next aligned instant failed - {err}");
13 | return Instant::now() + Duration::from_secs(seconds);
14 | }
15 | };
16 | Instant::now() + Duration::from_secs(seconds - epoch_now.as_secs().rem_euclid(seconds))
17 | }
18 |
--------------------------------------------------------------------------------
/docs/pdm-copyright.rst:
--------------------------------------------------------------------------------
1 | Copyright and Disclaimer
2 | ========================
3 |
4 | |pdm-copyright|
5 |
6 | This program is free software: you can redistribute it and/or modify
7 | it under the terms of the GNU Affero General Public License as
8 | published by the Free Software Foundation, either version 3 of the
9 | License, or (at your option) any later version.
10 |
11 | This program is distributed in the hope that it will be useful, but
12 | WITHOUT ANY WARRANTY; without even the implied warranty of
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 | Affero General Public License for more details.
15 |
16 | You should have received a copy of the GNU Affero General Public
17 | License along with this program. If not, see
18 | http://www.gnu.org/licenses/
19 |
--------------------------------------------------------------------------------
/cli/client/src/upid.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{bail, Error};
2 |
3 | use pdm_api_types::RemoteUpid;
4 |
5 | pub fn parse_for_remote(remote: Option<&str>, upid: &str) -> Result {
6 | if upid.contains('!') {
7 | let upid: RemoteUpid = upid.parse()?;
8 |
9 | if let Some(remote) = remote {
10 | if upid.remote() != remote {
11 | bail!(
12 | "remote in UPID ({:?}) does not match expected remote {remote:?}",
13 | upid.remote()
14 | );
15 | }
16 | }
17 |
18 | Ok(upid)
19 | } else {
20 | match remote {
21 | Some(remote) => format!("{remote}!{upid}").parse(),
22 | None => bail!("upid without a remote"),
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/server/src/api/nodes/status.rs:
--------------------------------------------------------------------------------
1 | use pdm_api_types::{PRIV_SYS_AUDIT, PRIV_SYS_POWER_MANAGEMENT};
2 | use proxmox_router::{ApiMethod, Permission, Router};
3 |
4 | const API_METHOD_GET_STATUS_WITH_ACCESS: ApiMethod = proxmox_node_status::API_METHOD_GET_STATUS
5 | .access(
6 | None,
7 | &Permission::Privilege(&["system", "status"], PRIV_SYS_AUDIT, false),
8 | );
9 |
10 | const API_METHOD_REBOOT_OR_SHUTDOWN_WITH_ACCESS: ApiMethod =
11 | proxmox_node_status::API_METHOD_REBOOT_OR_SHUTDOWN.access(
12 | None,
13 | &Permission::Privilege(&["system", "status"], PRIV_SYS_POWER_MANAGEMENT, false),
14 | );
15 |
16 | pub const ROUTER: Router = Router::new()
17 | .get(&API_METHOD_GET_STATUS_WITH_ACCESS)
18 | .post(&API_METHOD_REBOOT_OR_SHUTDOWN_WITH_ACCESS);
19 |
--------------------------------------------------------------------------------
/lib/pdm-api-types/src/user.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | use proxmox_schema::api;
4 |
5 | #[api]
6 | #[derive(Serialize, Deserialize)]
7 | #[serde(rename_all = "lowercase")]
8 | /// Properties of users which can be unset/deleted.
9 | pub enum DeletableUserProperty {
10 | /// Clear the comment field.
11 | Comment,
12 | /// Reset the enabled state to its default of being enabled.
13 | Enable,
14 | /// Clear the expiration date.
15 | Expire,
16 | /// Clear the first name.
17 | Firstname,
18 | /// Clear the last name.
19 | Lastname,
20 | /// Clear the associated email address.
21 | Email,
22 | }
23 |
24 | serde_plain::derive_display_from_serialize!(DeletableUserProperty);
25 | serde_plain::derive_fromstr_from_deserialize!(DeletableUserProperty);
26 |
--------------------------------------------------------------------------------
/ui/images/icon-sdn-vnet.svg:
--------------------------------------------------------------------------------
1 |
4 |
7 |
--------------------------------------------------------------------------------
/docs/proxmox-datacenter-privileged-api/man1.rst:
--------------------------------------------------------------------------------
1 | :orphan:
2 |
3 | =================================
4 | proxmox-datacenter-privileged-api
5 | =================================
6 |
7 | Synopsis
8 | ========
9 |
10 | This daemon is normally started and managed as ``systemd`` service::
11 |
12 | systemctl start proxmox-datacenter-privileged-api
13 |
14 | systemctl stop proxmox-datacenter-privileged-api
15 |
16 | systemctl status proxmox-datacenter-privileged-api
17 |
18 | For debugging, you can start the daemon in foreground as root user through running::
19 |
20 | proxmox-datacenter-privileged-api
21 |
22 | .. NOTE:: You need to stop the service before starting the daemon in foreground.
23 |
24 | Description
25 | ===========
26 |
27 | .. include:: description.rst
28 |
29 | .. include:: ../pdm-copyright.rst
30 |
--------------------------------------------------------------------------------
/ui/Trunk.toml:
--------------------------------------------------------------------------------
1 | [serve]
2 | addresses = ["127.0.0.1", "::1"] # The address to serve on.
3 |
4 | # FIXME: enable once trunk doesn't errors out if ignored files do not exist -.-
5 | # https://github.com/trunk-rs/trunk/issues/172
6 | #[watch]
7 | #ignore = [ "*.thanks", "*.mbox", "*.deb", "*.changes", "*.build", "*.buildinfo", "proxmox-datacenter-manager-ui-[0-9]*/" ]
8 |
9 | [[hooks]]
10 | stage="build"
11 | command="rust-grass"
12 | command_arguments= ["css/crisp-yew-style.scss", "dist/.stage/crisp-yew-style.css"]
13 |
14 | [[hooks]]
15 | stage="build"
16 | command="rust-grass"
17 | command_arguments= ["css/material-yew-style.scss", "dist/.stage/material-yew-style.css"]
18 |
19 | [[hooks]]
20 | stage="build"
21 | command="rust-grass"
22 | command_arguments= ["css/desktop-yew-style.scss", "dist/.stage/desktop-yew-style.css"]
23 |
--------------------------------------------------------------------------------
/lib/pdm-client/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pdm-client"
3 | description = "Proxmox Datacenter Manager client library crate"
4 | homepage = "https://www.proxmox.com"
5 |
6 | version.workspace = true
7 | edition.workspace = true
8 | license.workspace = true
9 | repository.workspace = true
10 |
11 | [dependencies]
12 | percent-encoding.workspace = true
13 | serde.workspace = true
14 | serde_json.workspace = true
15 |
16 | pdm-api-types.workspace = true
17 |
18 | proxmox-access-control.workspace = true
19 | proxmox-client.workspace = true
20 | proxmox-rrd-api-types.workspace = true
21 | proxmox-tfa = { workspace = true, features = [ "types" ] }
22 |
23 | pve-api-types = { workspace = true, features = [ "client" ] }
24 | pbs-api-types.workspace = true
25 |
26 | [features]
27 | default = []
28 | hyper-client = [ "proxmox-client/hyper-client" ]
29 |
--------------------------------------------------------------------------------
/cli/admin/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "proxmox-datacenter-manager-admin"
3 | description = "Local Proxmox Datacenter Manager administration CLI"
4 | homepage = "https://www.proxmox.com"
5 |
6 | version.workspace = true
7 | edition.workspace = true
8 | license.workspace = true
9 | repository.workspace = true
10 |
11 | [dependencies]
12 | anyhow.workspace = true
13 | serde.workspace = true
14 | serde_json.workspace = true
15 |
16 | proxmox-async.workspace = true
17 | proxmox-log.workspace = true
18 | proxmox-product-config.workspace = true
19 | proxmox-router = { workspace = true, features = [ "cli" ], default-features = false }
20 | proxmox-schema = { workspace = true, features = [ "api-macro" ] }
21 | proxmox-access-control.workspace = true
22 |
23 | pdm-api-types.workspace = true
24 | pdm-config.workspace = true
25 | pdm-buildcfg.workspace = true
26 | server.workspace = true
27 |
--------------------------------------------------------------------------------
/ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Proxmox Datacenter Manager
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/server/src/api/nodes/report.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 |
3 | use proxmox_router::{Permission, Router};
4 | use proxmox_schema::api;
5 |
6 | use pdm_api_types::{NODE_SCHEMA, PRIV_SYS_AUDIT};
7 |
8 | use crate::report;
9 |
10 | #[api(
11 | protected: true,
12 | input: {
13 | properties: {
14 | node: {
15 | schema: NODE_SCHEMA,
16 | },
17 | },
18 | },
19 | returns: {
20 | type: String,
21 | description: "The system report for this PDM node.",
22 | },
23 | access: {
24 | permission: &Permission::Privilege(&["system", "status"], PRIV_SYS_AUDIT, false),
25 | }
26 | )]
27 | /// Get the system report for this node.
28 | pub fn generate_system_report() -> Result {
29 | Ok(report::generate_report())
30 | }
31 |
32 | pub const ROUTER: Router = Router::new().get(&API_METHOD_GENERATE_SYSTEM_REPORT);
33 |
--------------------------------------------------------------------------------
/ui/src/widget/mod.rs:
--------------------------------------------------------------------------------
1 | mod migrate_window;
2 | pub use migrate_window::MigrateWindow;
3 |
4 | mod pve_node_selector;
5 | pub use pve_node_selector::PveNodeSelector;
6 |
7 | mod pve_network_selector;
8 | pub use pve_network_selector::PveNetworkSelector;
9 |
10 | mod pve_storage_selector;
11 | pub use pve_storage_selector::PveStorageSelector;
12 |
13 | mod pve_migrate_mapping;
14 | pub use pve_migrate_mapping::PveMigrateMap;
15 |
16 | mod pve_realm_selector;
17 | pub use pve_realm_selector::PveRealmSelector;
18 |
19 | mod resource_tree;
20 | pub use resource_tree::{RedrawController, ResourceTree};
21 |
22 | mod search_box;
23 | pub use search_box::SearchBox;
24 |
25 | mod remote_selector;
26 | pub use remote_selector::RemoteSelector;
27 |
28 | mod remote_endpoint_selector;
29 |
30 | mod view_selector;
31 | pub use view_selector::ViewSelector;
32 |
33 | mod view_filter_selector;
34 | pub use view_filter_selector::ViewFilterSelector;
35 |
--------------------------------------------------------------------------------
/ui/src/view_list_context.rs:
--------------------------------------------------------------------------------
1 | use pwt::state::{SharedState, SharedStateObserver};
2 | use yew::Callback;
3 |
4 | #[derive(PartialEq, Clone)]
5 | /// Provides a context for updating and listening to changes of the list of views
6 | pub struct ViewListContext {
7 | state: SharedState,
8 | }
9 |
10 | impl ViewListContext {
11 | /// Create a new context
12 | pub fn new() -> Self {
13 | Self {
14 | state: SharedState::new(0),
15 | }
16 | }
17 |
18 | /// Add a listener to the view list context
19 | pub fn add_listener(
20 | &self,
21 | cb: impl Into>>,
22 | ) -> SharedStateObserver {
23 | self.state.add_listener(cb)
24 | }
25 |
26 | /// Triggers an update of the view list for the main menu
27 | pub fn update_views(&self) {
28 | let mut state = self.state.write();
29 | **state = state.saturating_add(1);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/ui/src/search_provider.rs:
--------------------------------------------------------------------------------
1 | use yew::Callback;
2 |
3 | use pwt::state::{SharedState, SharedStateObserver};
4 |
5 | use pdm_search::Search;
6 |
7 | #[derive(Clone, PartialEq)]
8 | pub struct SearchProvider {
9 | state: SharedState,
10 | }
11 |
12 | impl SearchProvider {
13 | pub fn new() -> Self {
14 | Self {
15 | state: SharedState::new("".into()),
16 | }
17 | }
18 |
19 | pub fn add_listener(
20 | &self,
21 | cb: impl Into>>,
22 | ) -> SharedStateObserver {
23 | self.state.add_listener(cb)
24 | }
25 |
26 | pub fn search(&self, search_term: Search) {
27 | **self.state.write() = search_term.to_string();
28 | }
29 | }
30 |
31 | pub fn get_search_provider(ctx: &yew::Context) -> Option {
32 | let (provider, _context_listener) = ctx.link().context(Callback::from(|_| {}))?;
33 |
34 | Some(provider)
35 | }
36 |
--------------------------------------------------------------------------------
/debian/copyright:
--------------------------------------------------------------------------------
1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Source: https://git.proxmox.com/?p=proxmox-datacenter-manager.git;a=summary
3 |
4 | Files:
5 | *
6 | Copyright: 2023 - 2024 Proxmox Server Solutions GmbH
7 | License: AGPL-3.0-or-later
8 | This program is free software: you can redistribute it and/or modify it under
9 | the terms of the GNU Affero General Public License as published by the Free
10 | Software Foundation, either version 3 of the License, or (at your option) any
11 | later version.
12 | .
13 | This program is distributed in the hope that it will be useful, but WITHOUT
14 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 | FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
16 | details.
17 | .
18 | You should have received a copy of the GNU Affero General Public License along
19 | with this program. If not, see .
20 |
--------------------------------------------------------------------------------
/lib/pdm-api-types/src/proxy.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{bail, Error};
2 | use http::Uri;
3 |
4 | use proxmox_schema::{ApiStringFormat, Schema, StringSchema};
5 |
6 | fn verify_proxy_url(http_proxy: &str) -> Result<(), Error> {
7 | let proxy_uri: Uri = http_proxy.parse()?;
8 | if proxy_uri.authority().is_none() {
9 | bail!("missing proxy authority");
10 | };
11 |
12 | match proxy_uri.scheme_str() {
13 | Some("http") => { /* Ok */ }
14 | Some(scheme) => bail!("unsupported proxy scheme '{}'", scheme),
15 | None => { /* assume HTTP */ }
16 | }
17 |
18 | Ok(())
19 | }
20 |
21 | pub const HTTP_PROXY_SCHEMA: Schema =
22 | StringSchema::new("HTTP proxy configuration [http://][:port]")
23 | .format(&ApiStringFormat::VerifyFn(|s| {
24 | verify_proxy_url(s)?;
25 | Ok(())
26 | }))
27 | .min_length(1)
28 | .max_length(128)
29 | .type_text("[http://][:port]")
30 | .schema();
31 |
--------------------------------------------------------------------------------
/lib/pdm-config/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pdm-config"
3 | version = "0.1.0"
4 | authors.workspace = true
5 | edition.workspace = true
6 | description = "Configuration file management for PDM"
7 |
8 | [dependencies]
9 | anyhow.workspace = true
10 | log.workspace = true
11 | nix.workspace = true
12 | once_cell.workspace = true
13 | openssl.workspace = true
14 | serde.workspace = true
15 |
16 | proxmox-config-digest = { workspace = true, features = [ "openssl" ] }
17 | proxmox-http = { workspace = true, features = [ "http-helpers" ] }
18 | proxmox-ldap = { workspace = true, features = [ "types" ]}
19 | proxmox-product-config.workspace = true
20 | proxmox-schema.workspace = true
21 | proxmox-section-config.workspace = true
22 | proxmox-shared-memory.workspace = true
23 | proxmox-simple-config.workspace = true
24 | proxmox-sys = { workspace = true, features = [ "acl", "crypt", "timer" ] }
25 | proxmox-acme-api.workspace = true
26 | pdm-api-types.workspace = true
27 | pdm-buildcfg.workspace = true
28 |
--------------------------------------------------------------------------------
/ui/debian/copyright:
--------------------------------------------------------------------------------
1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
2 | Source: https://git.proxmox.com/?p=proxmox-datacenter-manager.git;a=summary
3 |
4 | Files:
5 | *
6 | Copyright: 2023 - 2024 Proxmox Server Solutions GmbH
7 | License: AGPL-3.0-or-later
8 | This program is free software: you can redistribute it and/or modify it under
9 | the terms of the GNU Affero General Public License as published by the Free
10 | Software Foundation, either version 3 of the License, or (at your option) any
11 | later version.
12 | .
13 | This program is distributed in the hope that it will be useful, but WITHOUT
14 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 | FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
16 | details.
17 | .
18 | You should have received a copy of the GNU Affero General Public License along
19 | with this program. If not, see .
20 |
--------------------------------------------------------------------------------
/ui/images/icon-cd-drive.svg:
--------------------------------------------------------------------------------
1 |
2 |
27 |
--------------------------------------------------------------------------------
/server/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Common API crate for PDM.
2 |
3 | pub mod acl;
4 | pub mod api;
5 | pub mod auth;
6 | pub mod context;
7 | pub mod env;
8 | pub mod jobstate;
9 | pub mod metric_collection;
10 | pub mod parallel_fetcher;
11 | pub mod remote_cache;
12 | pub mod remote_tasks;
13 | pub mod remote_updates;
14 | pub mod report;
15 | pub mod resource_cache;
16 | pub mod task_utils;
17 | pub mod views;
18 |
19 | pub mod connection;
20 | pub mod pbs_client;
21 | pub mod sdn_client;
22 |
23 | #[cfg(any(remote_config = "faked", test))]
24 | pub mod test_support;
25 |
26 | use anyhow::Error;
27 | use serde_json::Value;
28 |
29 | pub(crate) async fn reload_api_certificate() -> Result<(), Error> {
30 | let proxy_pid = proxmox_rest_server::read_pid(pdm_buildcfg::PDM_API_PID_FN)?;
31 | let sock = proxmox_daemon::command_socket::path_from_pid(proxy_pid);
32 | let _: Value =
33 | proxmox_daemon::command_socket::send_raw(sock, "{\"command\":\"reload-certificate\"}\n")
34 | .await?;
35 | Ok(())
36 | }
37 |
--------------------------------------------------------------------------------
/cli/completions/Makefile:
--------------------------------------------------------------------------------
1 |
2 | all:
3 |
4 | # bash completion
5 | %.bc:
6 | @echo "creating bash completion definition for $*"
7 | @printf '# $* bash completion\n\n' >$@.tmp
8 | @printf "complete -C '$* bashcomplete' $*\n" >>$@.tmp
9 | mv $@.tmp $@
10 |
11 | # ZSH completion
12 | _%:
13 | @echo "creating ZSH completion definition for $*"
14 | @printf '#compdef _$*() $*\n\n' >$@.tmp
15 | @printf 'function _$*() {\n' >>$@.tmp
16 | @printf ' local cwords line point cmd curr prev\n' >>$@.tmp
17 | @printf ' cwords=$${#words[@]}\n' >>$@.tmp
18 | @printf ' line=$$words\n' >>$@.tmp
19 | @printf ' point=$${#line}\n' >>$@.tmp
20 | @printf ' cmd=$${words[1]}\n' >>$@.tmp
21 | @printf ' curr=$${words[cwords]}\n' >>$@.tmp
22 | @printf ' prev=$${words[cwords-1]}\n' >>$@.tmp
23 | @printf ' compadd -- $$(COMP_CWORD="$$cwords" COMP_LINE="$$line" COMP_POINT="$$point" \\n' >>$@.tmp
24 | @printf ' $* bashcomplete "$$cmd" "$$curr" "$$prev")\n' >>$@.tmp
25 | @printf '}\n' >>$@.tmp
26 | mv $@.tmp $@
27 |
28 | clean:
29 | rm -f _* *.bc
30 |
--------------------------------------------------------------------------------
/lib/pdm-api-types/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pdm-api-types"
3 | version = "1.0.0"
4 | authors.workspace = true
5 | edition.workspace = true
6 | description = "general API type helpers for PDM"
7 |
8 | [dependencies]
9 | anyhow.workspace = true
10 | const_format.workspace = true
11 | http.workspace = true
12 | regex.workspace = true
13 | serde.workspace = true
14 | serde_plain.workspace = true
15 |
16 | proxmox-acme-api.workspace = true
17 | proxmox-access-control.workspace = true
18 | proxmox-auth-api = { workspace = true, features = ["api-types"] }
19 | proxmox-apt-api-types.workspace = true
20 | proxmox-lang.workspace = true
21 | proxmox-config-digest.workspace = true
22 | proxmox-schema = { workspace = true, features = ["api-macro"] }
23 | proxmox-section-config.workspace = true
24 | proxmox-dns-api.workspace = true
25 | proxmox-time.workspace = true
26 | proxmox-serde.workspace = true
27 | proxmox-subscription = { workspace = true, features = ["api-types"], default-features = false }
28 |
29 | pbs-api-types = { workspace = true }
30 | pve-api-types = { workspace = true }
31 |
--------------------------------------------------------------------------------
/docs/epilog.rst:
--------------------------------------------------------------------------------
1 | .. Epilog (included at top of each file)
2 |
3 | We use this file to define external links and common replacement
4 | patterns.
5 |
6 | .. |WEBSITE| replace:: https://www.proxmox.com
7 | .. |DOWNLOADS| replace:: https://www.proxmox.com/downloads
8 |
9 | .. _Proxmox: https://www.proxmox.com
10 | .. _Proxmox Community Forum: https://forum.proxmox.com
11 | .. _Proxmox Virtual Environment: https://www.proxmox.com/proxmox-ve
12 | .. _Proxmox Datacenter Manager: https://www.proxmox.com/proxmox-datacenter-manager
13 | .. _Proxmox Backup: https://pbs.proxmox.com/wiki/index.php/Main_Page
14 | .. _PDM Development List: https://lists.proxmox.com/cgi-bin/mailman/listinfo/pdm-devel
15 | .. _reStructuredText: https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html
16 | .. _Rust: https://www.rust-lang.org/
17 |
18 | .. _AGPL3: https://www.gnu.org/licenses/agpl-3.0.en.html
19 | .. _Debian: https://www.debian.org
20 | .. _Debian Administrator's Handbook: https://debian-handbook.info/
21 |
22 | .. _LVM: https://en.wikipedia.org/wiki/Logical_Volume_Manager_(Linux)
23 | .. _ZFS: https://en.wikipedia.org/wiki/ZFS
24 |
--------------------------------------------------------------------------------
/cli/client/src/time.rs:
--------------------------------------------------------------------------------
1 | //! Time formatting functions.
2 |
3 | use std::ffi::CStr;
4 | use std::io;
5 |
6 | use anyhow::{bail, format_err, Error};
7 |
8 | pub fn format_epoch(epoch: i64) -> Result {
9 | let mut ts: libc::tm = unsafe { std::mem::zeroed() };
10 | let mut buf = [0u8; 64];
11 | let epoch = epoch as libc::time_t;
12 | let len = unsafe {
13 | if libc::localtime_r(&epoch, &mut ts).is_null() {
14 | return Err(io::Error::last_os_error().into());
15 | }
16 |
17 | libc::strftime(
18 | buf.as_mut_ptr() as *mut libc::c_char,
19 | buf.len(),
20 | c"%c".as_ptr(),
21 | &ts,
22 | )
23 | };
24 |
25 | if len == 0 || len > buf.len() {
26 | bail!("failed to format time");
27 | }
28 |
29 | let text = CStr::from_bytes_with_nul(&buf[..(len + 1)])
30 | .map_err(|_| format_err!("formatted time string is not valid"))?;
31 |
32 | Ok(text.to_string_lossy().to_string())
33 | }
34 |
35 | pub fn format_epoch_lossy(epoch: i64) -> String {
36 | format_epoch(epoch).unwrap_or_else(|_| format!("{epoch}"))
37 | }
38 |
--------------------------------------------------------------------------------
/server/src/resource_cache.rs:
--------------------------------------------------------------------------------
1 | use std::pin::pin;
2 |
3 | use anyhow::Error;
4 |
5 | use crate::task_utils;
6 |
7 | // This is the interval we update the cache independent of any API / UI activity, but depending on
8 | // the max-age from API calls the caches can get updated more frequently.
9 | const METRIC_POLL_INTERVALL: u64 = 15 * 60; // once every 15 minutes
10 |
11 | /// Start the resource caching.
12 | pub fn start_task() {
13 | tokio::spawn(async move {
14 | let task_scheduler = pin!(resource_caching_task());
15 | let abort_future = pin!(proxmox_daemon::shutdown_future());
16 | futures::future::select(task_scheduler, abort_future).await;
17 | });
18 | }
19 |
20 | // FIXME: handle many remotes more intelligently?
21 | async fn resource_caching_task() -> Result<(), Error> {
22 | loop {
23 | if let Err(err) =
24 | crate::api::resources::get_resources_impl(METRIC_POLL_INTERVALL, None, None, None, None)
25 | .await
26 | {
27 | log::error!("could not update resource cache: {err}");
28 | }
29 |
30 | let delay_target = task_utils::next_aligned_instant(METRIC_POLL_INTERVALL + 10);
31 | tokio::time::sleep_until(tokio::time::Instant::from_std(delay_target)).await;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lib/pdm-config/src/views.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 |
3 | use proxmox_product_config::{open_api_lockfile, replace_config, ApiLockGuard};
4 | use proxmox_section_config::typed::{ApiSectionDataEntry, SectionConfigData};
5 |
6 | use pdm_api_types::{views::ViewConfigEntry, ConfigDigest};
7 |
8 | use pdm_buildcfg::configdir;
9 |
10 | const VIEW_CFG_FILENAME: &str = configdir!("/views.cfg");
11 | const VIEW_FILTER_CFG_LOCKFILE: &str = configdir!("/.views.lock");
12 |
13 | /// Get the `views.cfg` config file contents.
14 | pub fn config() -> Result<(SectionConfigData, ConfigDigest), Error> {
15 | let content =
16 | proxmox_sys::fs::file_read_optional_string(VIEW_CFG_FILENAME)?.unwrap_or_default();
17 |
18 | let digest = openssl::sha::sha256(content.as_bytes());
19 |
20 | let data = ViewConfigEntry::parse_section_config(VIEW_CFG_FILENAME, &content)?;
21 | Ok((data, digest.into()))
22 | }
23 |
24 | /// Get exclusive lock
25 | pub fn lock_config() -> Result {
26 | open_api_lockfile(VIEW_FILTER_CFG_LOCKFILE, None, true)
27 | }
28 |
29 | pub fn save_config(config: &SectionConfigData) -> Result<(), Error> {
30 | let raw = ViewConfigEntry::write_section_config(VIEW_CFG_FILENAME, config)?;
31 | replace_config(VIEW_CFG_FILENAME, raw.as_bytes())?;
32 | Ok(())
33 | }
34 |
--------------------------------------------------------------------------------
/server/src/api/nodes/journal.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 | use serde_json::Value;
3 |
4 | use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
5 | use proxmox_schema::api;
6 |
7 | use proxmox_syslog_api::{dump_journal, JournalFilter};
8 |
9 | use pdm_api_types::{NODE_SCHEMA, PRIV_SYS_AUDIT};
10 |
11 | #[api(
12 | protected: true,
13 | input: {
14 | properties: {
15 | node: {
16 | schema: NODE_SCHEMA,
17 | },
18 | filter: {
19 | type: JournalFilter,
20 | flatten: true,
21 | },
22 | },
23 | },
24 | returns: {
25 | type: Array,
26 | description: "Returns a list of journal entries.",
27 | items: {
28 | type: String,
29 | description: "Line text.",
30 | },
31 | },
32 | access: {
33 | permission: &Permission::Privilege(&["system", "log"], PRIV_SYS_AUDIT, false),
34 | },
35 | )]
36 | /// Read syslog entries.
37 | #[allow(clippy::too_many_arguments)]
38 | fn get_journal(
39 | filter: JournalFilter,
40 | _param: Value,
41 | _info: &ApiMethod,
42 | _rpcenv: &mut dyn RpcEnvironment,
43 | ) -> Result, Error> {
44 | dump_journal(filter)
45 | }
46 |
47 | pub const ROUTER: Router = Router::new().get(&API_METHOD_GET_JOURNAL);
48 |
--------------------------------------------------------------------------------
/docs/sysadmin.rst:
--------------------------------------------------------------------------------
1 | .. _sysadmin_host_administration:
2 |
3 | Host System Administration
4 | ==========================
5 |
6 | Proxmox Datacenter Manager is based on the famous Debian_ Linux distribution. This means that you
7 | have access to the entire range of Debian packages, and that the base system is well documented. The
8 | `Debian Administrator's Handbook`_ is available online, and provides a comprehensive introduction to
9 | the Debian operating system.
10 |
11 | A standard Proxmox Datacenter Manager installation uses the default repositories from Debian, so you get bug
12 | fixes and security updates through that channel. In addition, we provide our own package repository
13 | to roll out all Proxmox related packages. This includes updates to some Debian packages when
14 | necessary.
15 |
16 | We also deliver a specially optimized Linux kernel, based on the Ubuntu kernel. This kernel includes
17 | drivers for ZFS_.
18 |
19 | The following sections will explain things which are different on Proxmox Datacenter Manager, or
20 | tasks which are commonly used on Proxmox Datacenter Manager. For other topics, please refer to the
21 | standard Debian documentation.
22 |
23 |
24 | .. include:: local-zfs.rst
25 |
26 | .. include:: system-booting.rst
27 |
28 | .. include:: certificate-management.rst
29 |
30 | .. include:: services.rst
31 |
32 | .. include:: command-line-tools.rst
33 |
--------------------------------------------------------------------------------
/ui/src/load_result.rs:
--------------------------------------------------------------------------------
1 | /// Helper wrapper to factor out some common api loading behavior
2 | pub struct LoadResult {
3 | pub data: Option,
4 | pub error: Option,
5 | }
6 |
7 | impl LoadResult {
8 | /// Creates a new empty result that contains no data or error.
9 | pub fn new() -> Self {
10 | Self {
11 | data: None,
12 | error: None,
13 | }
14 | }
15 |
16 | /// Update the current value with the given result
17 | ///
18 | /// On `Ok`, the previous error will be deleted.
19 | /// On `Err`, the previous valid date is kept.
20 | pub fn update(&mut self, result: Result) {
21 | match result {
22 | Ok(data) => {
23 | self.error = None;
24 | self.data = Some(data);
25 | }
26 | Err(err) => {
27 | self.error = Some(err);
28 | }
29 | }
30 | }
31 |
32 | /// If any of data or err has any value
33 | pub fn has_data(&self) -> bool {
34 | self.data.is_some() || self.error.is_some()
35 | }
36 |
37 | /// Clears both data and the error from the result.
38 | pub fn clear(&mut self) {
39 | self.data = None;
40 | self.error = None;
41 | }
42 | }
43 |
44 | impl Default for LoadResult {
45 | fn default() -> Self {
46 | Self::new()
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/pdm-api-types/src/pbs.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | use proxmox_schema::api;
4 |
5 | #[api]
6 | #[derive(Debug, Deserialize, Serialize, PartialEq)]
7 | #[serde(rename_all = "kebab-case")]
8 | /// Whether a task is still running.
9 | pub enum IsRunning {
10 | /// Task is running.
11 | Running,
12 | /// Task is not running.
13 | Stopped,
14 | }
15 |
16 | // TODO: The pbs code should expose this via pbs-api-types!
17 | #[api]
18 | /// Status if a task.
19 | #[derive(Debug, Deserialize, Serialize)]
20 | pub struct TaskStatus {
21 | /// Exit status, if available.
22 | pub exitstatus: Option,
23 |
24 | /// Task id.
25 | pub id: Option,
26 |
27 | /// Node the task is running on.
28 | pub node: String,
29 |
30 | /// The Unix PID
31 | pub pid: i64,
32 |
33 | /// The task start time (Epoch)
34 | pub pstart: i64,
35 |
36 | /// The task's start time.
37 | pub starttime: i64,
38 |
39 | pub status: IsRunning,
40 |
41 | /// The task type.
42 | #[serde(rename = "type")]
43 | pub ty: String,
44 |
45 | /// The task's UPID.
46 | pub upid: String,
47 |
48 | /// The authenticated entity who started the task.
49 | pub user: String,
50 | }
51 |
52 | impl TaskStatus {
53 | /// Checks if the task is currently running.
54 | pub fn is_running(&self) -> bool {
55 | self.status == IsRunning::Running
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/server/src/api/nodes/syslog.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 | use serde_json::Value;
3 |
4 | use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
5 | use proxmox_schema::api;
6 |
7 | use proxmox_syslog_api::{dump_syslog, SyslogFilter, SyslogLine};
8 |
9 | use pdm_api_types::{NODE_SCHEMA, PRIV_SYS_AUDIT};
10 |
11 | #[api(
12 | protected: true,
13 | input: {
14 | properties: {
15 | node: {
16 | schema: NODE_SCHEMA,
17 | },
18 | filter: {
19 | type: SyslogFilter,
20 | flatten: true,
21 | },
22 | },
23 | },
24 | returns: {
25 | type: Array,
26 | description: "Returns a list of syslog entries.",
27 | items: {
28 | type: SyslogLine,
29 | }
30 | },
31 | access: {
32 | permission: &Permission::Privilege(&["system", "log"], PRIV_SYS_AUDIT, false),
33 | },
34 | )]
35 | /// Read syslog entries.
36 | fn get_syslog(
37 | filter: SyslogFilter,
38 | _info: &ApiMethod,
39 | rpcenv: &mut dyn RpcEnvironment,
40 | ) -> Result, Error> {
41 | //filter.service = filter.service.map(crate::api2::node::services::real_service_name);
42 |
43 | let (count, lines) = dump_syslog(filter)?;
44 |
45 | rpcenv["total"] = Value::from(count);
46 |
47 | Ok(lines)
48 | }
49 |
50 | pub const ROUTER: Router = Router::new().get(&API_METHOD_GET_SYSLOG);
51 |
--------------------------------------------------------------------------------
/visualize_rrd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # deps: python3-matplotlib python3-pandas python3-requests
3 | #
4 | # Usage example:
5 | # ```
6 | # export PDM_USERNAME="root@pam"
7 | # export PDM_PASSWORD=""
8 | # export PDM_URL="https://172.30.0.4:8443"
9 | #
10 | # ./visualize_rrd.py pve/remotes//qemu/100 cpu-current
11 | # ./visualize_rrd.py pbs/remotes//datastore/ disk-used
12 | # ```
13 |
14 | import os
15 | import sys
16 |
17 | import pandas as pd
18 | import matplotlib.pyplot as plt
19 | import requests
20 |
21 | for env_var in ["PDM_URL", "PDM_USERNAME", "PDM_PASSWORD"]:
22 | if not os.environ.get(env_var):
23 | raise Exception(f"{env_var} not set")
24 |
25 | url = os.environ["PDM_URL"]
26 | user = os.environ["PDM_USERNAME"]
27 | password = os.environ["PDM_PASSWORD"]
28 |
29 | query = sys.argv[1]
30 | rows = sys.argv[2:]
31 |
32 | r = requests.post(
33 | f"{url}/api2/json/access/ticket",
34 | verify=False,
35 | data={
36 | "username": user,
37 | "password": password
38 | }
39 | )
40 |
41 | data = r.json()['data']
42 | csrf = data['CSRFPreventionToken']
43 | ticket = data['ticket']
44 |
45 | r = requests.get(
46 | f"{url}/api2/json/{query}/rrddata",
47 | params={"cf": "AVERAGE", "timeframe": "hour"},
48 | cookies={"PDMAuthCookie": ticket},
49 | verify=False
50 | )
51 |
52 | data = r.json()
53 | df = pd.DataFrame(data['data'])
54 | df.plot(x='time', y=rows)
55 | plt.show()
56 |
--------------------------------------------------------------------------------
/debian/proxmox-datacenter-manager.install:
--------------------------------------------------------------------------------
1 | debian/pdm-enterprise.sources etc/apt/sources.list.d/
2 | usr/lib/systemd/system/proxmox-datacenter-api.service
3 | usr/lib/systemd/system/proxmox-datacenter-manager-banner.service
4 | usr/lib/systemd/system/proxmox-datacenter-manager-daily-update.service
5 | usr/lib/systemd/system/proxmox-datacenter-manager-daily-update.timer
6 | usr/lib/systemd/system/proxmox-datacenter-privileged-api.service
7 | usr/libexec/proxmox/proxmox-datacenter-api
8 | usr/libexec/proxmox/proxmox-datacenter-manager-banner
9 | usr/libexec/proxmox/proxmox-datacenter-manager-daily-update
10 | usr/libexec/proxmox/proxmox-datacenter-privileged-api
11 | usr/sbin/pdmAtoB
12 | usr/sbin/proxmox-datacenter-manager-admin
13 | usr/share/bash-completion/completions/pdmAtoB.bc
14 | usr/share/bash-completion/completions/proxmox-datacenter-api.bc
15 | usr/share/bash-completion/completions/proxmox-datacenter-manager-admin.bc
16 | usr/share/bash-completion/completions/proxmox-datacenter-privileged-api.bc
17 | usr/share/man/man1/proxmox-datacenter-api.1
18 | usr/share/man/man1/proxmox-datacenter-manager-admin.1
19 | usr/share/man/man1/proxmox-datacenter-privileged-api.1
20 | usr/share/man/man5/remotes.cfg.5
21 | usr/share/man/man5/views.cfg.5
22 | usr/share/zsh/vendor-completions/_pdmAtoB
23 | usr/share/zsh/vendor-completions/_proxmox-datacenter-api
24 | usr/share/zsh/vendor-completions/_proxmox-datacenter-manager-admin
25 | usr/share/zsh/vendor-completions/_proxmox-datacenter-privileged-api
26 |
--------------------------------------------------------------------------------
/lib/pdm-api-types/src/translation.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | use proxmox_schema::api;
4 |
5 | /// All available languages in Proxmox. Taken from proxmox-i18n repository.
6 | /// pt_BR, zh_CN, and zh_TW use the same case in the translation files.
7 | // TODO: auto-generate from available translations
8 | #[api]
9 | #[derive(Serialize, Deserialize)]
10 | #[serde(rename_all = "lowercase")]
11 | pub enum Translation {
12 | /// Arabic
13 | Ar,
14 | /// Catalan
15 | Ca,
16 | /// Danish
17 | Da,
18 | /// German
19 | De,
20 | /// English
21 | En,
22 | /// Spanish
23 | Es,
24 | /// Euskera
25 | Eu,
26 | /// Persian (Farsi)
27 | Fa,
28 | /// French
29 | Fr,
30 | /// Galician
31 | Gl,
32 | /// Hebrew
33 | He,
34 | /// Hungarian
35 | Hu,
36 | /// Italian
37 | It,
38 | /// Japanese
39 | Ja,
40 | /// Korean
41 | Kr,
42 | /// Norwegian (Bokmal)
43 | Nb,
44 | /// Dutch
45 | Nl,
46 | /// Norwegian (Nynorsk)
47 | Nn,
48 | /// Polish
49 | Pl,
50 | /// Portuguese (Brazil)
51 | #[serde(rename = "pt_BR")]
52 | PtBr,
53 | /// Russian
54 | Ru,
55 | /// Slovenian
56 | Sl,
57 | /// Swedish
58 | Sv,
59 | /// Turkish
60 | Tr,
61 | /// Chinese (simplified)
62 | #[serde(rename = "zh_CN")]
63 | ZhCn,
64 | /// Chinese (traditional)
65 | #[serde(rename = "zh_TW")]
66 | ZhTw,
67 | }
68 |
--------------------------------------------------------------------------------
/server/src/api/nodes/time.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 | use serde_json::Value;
3 |
4 | use proxmox_router::{Permission, Router};
5 | use proxmox_schema::api;
6 |
7 | use pdm_api_types::{NODE_SCHEMA, PRIV_SYS_MODIFY, TIME_ZONE_SCHEMA};
8 | use proxmox_time_api::ServerTimeInfo;
9 |
10 | #[api(
11 | input: {
12 | properties: {
13 | node: {
14 | schema: NODE_SCHEMA,
15 | },
16 | },
17 | },
18 | returns: {
19 | type: ServerTimeInfo,
20 | },
21 | access: {
22 | permission: &Permission::Anybody,
23 | },
24 | )]
25 | /// Read server time and time zone settings.
26 | fn get_time(_param: Value) -> Result {
27 | proxmox_time_api::get_server_time_info()
28 | }
29 |
30 | #[api(
31 | protected: true,
32 | reload_timezone: true,
33 | input: {
34 | properties: {
35 | node: {
36 | schema: NODE_SCHEMA,
37 | },
38 | timezone: {
39 | schema: TIME_ZONE_SCHEMA,
40 | },
41 | },
42 | },
43 | access: {
44 | permission: &Permission::Privilege(&["system", "time"], PRIV_SYS_MODIFY, false),
45 | },
46 | )]
47 | /// Set time zone
48 | fn set_timezone(timezone: String, _param: Value) -> Result<(), Error> {
49 | proxmox_time_api::set_timezone(timezone)
50 | }
51 |
52 | pub const ROUTER: Router = Router::new()
53 | .get(&API_METHOD_GET_TIME)
54 | .put(&API_METHOD_SET_TIMEZONE);
55 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. Proxmox Backup documentation master file
2 |
3 | Welcome to the Proxmox Datacenter Manager documentation!
4 | ========================================================
5 | | |pdm-copyright|
6 | | Version |version| -- |today|
7 |
8 | Permission is granted to copy, distribute and/or modify this document under the
9 | terms of the GNU Free Documentation License, Version 1.3 or any later version
10 | published by the Free Software Foundation; with no Invariant Sections, no
11 | Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included
12 | in the section entitled "GNU Free Documentation License".
13 |
14 |
15 | .. only:: html
16 |
17 | A `PDF` version of the documentation is `also available here <./proxmox-datacenter-manager.pdf>`_
18 |
19 | .. toctree::
20 | :maxdepth: 3
21 | :caption: Table of Contents
22 |
23 | introduction.rst
24 | installation.rst
25 | web-ui.rst
26 | sdn-integration.rst
27 | remotes.rst
28 | views.rst
29 | access-control.rst
30 | sysadmin.rst
31 | faq.rst
32 |
33 | .. raw:: latex
34 |
35 | \appendix
36 |
37 | .. toctree::
38 | :maxdepth: 2
39 | :caption: Appendix
40 |
41 | command-syntax.rst
42 | configuration-files.rst
43 | roadmap.rst
44 | markdown-primer.rst
45 | GFDL.rst
46 |
47 | .. only:: html and devbuild
48 |
49 | .. toctree::
50 | :maxdepth: 2
51 | :hidden:
52 | :caption: Developer Appendix
53 |
54 | todos.rst
55 |
56 |
57 | .. # * :ref:`genindex`
58 |
--------------------------------------------------------------------------------
/server/src/bin/proxmox-datacenter-api/tasks/remote_updates.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 |
3 | use server::{remote_updates, task_utils};
4 |
5 | const REFRESH_TIME: u64 = 6 * 3600;
6 |
7 | /// Start the remote task fetching task
8 | pub fn start_task() -> Result<(), Error> {
9 | tokio::spawn(async move {
10 | let task_scheduler = std::pin::pin!(RemoteUpdateRefreshTask {}.run());
11 | let abort_future = std::pin::pin!(proxmox_daemon::shutdown_future());
12 | futures::future::select(task_scheduler, abort_future).await;
13 | });
14 |
15 | Ok(())
16 | }
17 |
18 | struct RemoteUpdateRefreshTask {}
19 |
20 | impl RemoteUpdateRefreshTask {
21 | async fn run(self) {
22 | loop {
23 | self.refresh().await;
24 | self.wait_for_refresh().await;
25 | }
26 | }
27 |
28 | async fn refresh(&self) {
29 | if let Err(err) = self.do_refresh().await {
30 | log::error!("could not refresh remote update cache: {err:#}");
31 | }
32 | }
33 |
34 | async fn do_refresh(&self) -> Result<(), Error> {
35 | let (config, _digest) = tokio::task::spawn_blocking(pdm_config::remotes::config).await??;
36 | remote_updates::refresh_update_summary_cache(config.into_iter().map(|(_, r)| r).collect())
37 | .await
38 | }
39 |
40 | async fn wait_for_refresh(&self) {
41 | let instant = task_utils::next_aligned_instant(REFRESH_TIME);
42 | tokio::time::sleep_until(instant.into()).await;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/server/src/context.rs:
--------------------------------------------------------------------------------
1 | //! Module to setup the API server's global runtime context.
2 | //!
3 | //! Make sure to call `init` *once* when starting up the API server.
4 |
5 | use anyhow::Error;
6 |
7 | use crate::connection;
8 |
9 | /// Dependency-inject production remote-config implementation and remote client factory
10 | #[allow(dead_code)]
11 | fn default_remote_setup() {
12 | pdm_config::remotes::init(Box::new(pdm_config::remotes::DefaultRemoteConfig));
13 | connection::init(Box::new(connection::DefaultClientFactory));
14 | }
15 |
16 | /// Dependency-inject concrete implementations needed at runtime.
17 | pub fn init() -> Result<(), Error> {
18 | #[cfg(remote_config = "faked")]
19 | {
20 | use anyhow::bail;
21 |
22 | use crate::test_support::fake_remote;
23 |
24 | match std::env::var("PDM_FAKED_REMOTE_CONFIG") {
25 | Ok(path) => {
26 | log::info!("using fake remotes from {path:?}");
27 | let config = fake_remote::FakeRemoteConfig::from_json_config(&path)?;
28 | pdm_config::remotes::init(Box::new(config.clone()));
29 | connection::init(Box::new(fake_remote::FakeClientFactory { config }));
30 | }
31 | Err(_) => {
32 | bail!("compiled with remote_config = 'faked', but PDM_FAKED_REMOTE_CONFIG not set")
33 | }
34 | }
35 | }
36 | #[cfg(not(remote_config = "faked"))]
37 | {
38 | default_remote_setup();
39 | }
40 |
41 | Ok(())
42 | }
43 |
--------------------------------------------------------------------------------
/lib/pdm-config/src/lib.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{format_err, Error};
2 | use nix::unistd::{Gid, Group, Uid, User};
3 | pub use pdm_buildcfg::{BACKUP_GROUP_NAME, BACKUP_USER_NAME};
4 |
5 | pub mod certificate_config;
6 | pub mod domains;
7 | pub mod node;
8 | pub mod remotes;
9 | pub mod setup;
10 | pub mod views;
11 |
12 | mod config_version_cache;
13 | pub use config_version_cache::ConfigVersionCache;
14 |
15 | /// Return User info for the main system user (``getpwnam_r(3)``)
16 | pub fn api_user() -> Result {
17 | if cfg!(test) {
18 | Ok(User::from_uid(Uid::current())?.expect("current user does not exist"))
19 | } else {
20 | User::from_name(BACKUP_USER_NAME)?
21 | .ok_or_else(|| format_err!("Unable to lookup '{}' user.", BACKUP_USER_NAME))
22 | }
23 | }
24 |
25 | /// Return Group info for the main system group (``getgrnam(3)``)
26 | pub fn api_group() -> Result {
27 | if cfg!(test) {
28 | Ok(Group::from_gid(Gid::current())?.expect("current group does not exist"))
29 | } else {
30 | Group::from_name(BACKUP_GROUP_NAME)?
31 | .ok_or_else(|| format_err!("Unable to lookup '{}' group.", BACKUP_GROUP_NAME))
32 | }
33 | }
34 |
35 | pub fn priv_user() -> Result {
36 | if cfg!(test) {
37 | Ok(User::from_uid(Uid::current())?.expect("current user does not exist"))
38 | } else {
39 | User::from_name("root")?.ok_or_else(|| format_err!("Unable to lookup superuser."))
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/server/src/api/nodes/rrddata.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 | use proxmox_rrd_api_types::{RrdMode, RrdTimeframe};
3 |
4 | use proxmox_router::Router;
5 | use proxmox_schema::api;
6 |
7 | use pdm_api_types::rrddata::PdmNodeDatapoint;
8 |
9 | use crate::api::rrd_common::{self, DataPoint};
10 |
11 | impl DataPoint for PdmNodeDatapoint {
12 | fn new(time: u64) -> Self {
13 | Self {
14 | time,
15 | ..Default::default()
16 | }
17 | }
18 |
19 | fn fields() -> &'static [&'static str] {
20 | &["metric-collection-total-time"]
21 | }
22 |
23 | fn set_field(&mut self, name: &str, value: f64) {
24 | if name == "metric-collection-total-time" {
25 | self.total_time = Some(value);
26 | }
27 | }
28 | }
29 |
30 | #[api(
31 | input: {
32 | properties: {
33 | timeframe: {
34 | type: RrdTimeframe,
35 | },
36 | cf: {
37 | type: RrdMode,
38 | },
39 | },
40 | },
41 | returns: {
42 | type: Array,
43 | description: "An array of RRD data points.",
44 | items: {
45 | type: PdmNodeDatapoint,
46 | }
47 | }
48 | )]
49 | /// Read RRD data for this PDM node.
50 | fn get_node_rrddata(timeframe: RrdTimeframe, cf: RrdMode) -> Result, Error> {
51 | let base = "nodes/localhost";
52 | rrd_common::create_datapoints_from_rrd(base, timeframe, cf)
53 | }
54 |
55 | pub const ROUTER: Router = Router::new().get(&API_METHOD_GET_NODE_RRDDATA);
56 |
--------------------------------------------------------------------------------
/server/src/api/metric_collection.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 |
3 | use proxmox_router::{Router, SubdirMap};
4 | use proxmox_schema::api;
5 | use proxmox_sortable_macro::sortable;
6 |
7 | use pdm_api_types::{remotes::REMOTE_ID_SCHEMA, MetricCollectionStatus};
8 |
9 | use crate::metric_collection;
10 |
11 | pub const ROUTER: Router = Router::new().subdirs(SUBDIRS);
12 |
13 | #[sortable]
14 | const SUBDIRS: SubdirMap = &sorted!([
15 | (
16 | "trigger",
17 | &Router::new().post(&API_METHOD_TRIGGER_METRIC_COLLECTION)
18 | ),
19 | (
20 | "status",
21 | &Router::new().get(&API_METHOD_GET_METRIC_COLLECTION_STATUS)
22 | ),
23 | ]);
24 |
25 | #[api(
26 | input: {
27 | properties: {
28 | remote: {
29 | schema: REMOTE_ID_SCHEMA,
30 | optional: true,
31 | },
32 | },
33 | },
34 | )]
35 | /// Trigger metric collection for a provided remote or for all remotes if no remote is passed.
36 | pub async fn trigger_metric_collection(remote: Option) -> Result<(), Error> {
37 | crate::metric_collection::trigger_metric_collection(remote, false).await?;
38 |
39 | Ok(())
40 | }
41 |
42 | #[api(
43 | returns: {
44 | type: Array,
45 | description: "A list of metric collection statuses.",
46 | items: {
47 | type: MetricCollectionStatus,
48 | }
49 | }
50 | )]
51 | /// Read metric collection status.
52 | fn get_metric_collection_status() -> Result, Error> {
53 | metric_collection::get_status()
54 | }
55 |
--------------------------------------------------------------------------------
/ui/src/widget/view_selector.rs:
--------------------------------------------------------------------------------
1 | use std::rc::Rc;
2 |
3 | use pwt::prelude::*;
4 | use pwt::state::Store;
5 | use pwt::widget::form::Combobox;
6 | use pwt_macros::{builder, widget};
7 |
8 | use pdm_api_types::views::ViewConfig;
9 |
10 | #[widget(comp=ViewSelectorComp, @input)]
11 | #[derive(Clone, Properties, PartialEq)]
12 | #[builder]
13 | pub struct ViewSelector {
14 | store: Store,
15 | }
16 |
17 | impl ViewSelector {
18 | pub fn new(store: Store) -> Self {
19 | yew::props!(Self { store })
20 | }
21 | }
22 |
23 | #[doc(hidden)]
24 | pub struct ViewSelectorComp {}
25 |
26 | impl Component for ViewSelectorComp {
27 | type Message = ();
28 | type Properties = ViewSelector;
29 |
30 | fn create(_ctx: &Context) -> Self {
31 | Self {}
32 | }
33 |
34 | fn view(&self, ctx: &Context) -> Html {
35 | let mut list = vec!["__dashboard__".into()];
36 | let store = &ctx.props().store;
37 | for item in store.read().data().iter() {
38 | list.push(item.id.clone().into());
39 | }
40 | Combobox::new()
41 | .items(Rc::new(list))
42 | .with_input_props(&ctx.props().input_props)
43 | .on_change(|_| {})
44 | .render_value({
45 | move |value: &AttrValue| {
46 | if value == "__dashboard__" {
47 | html! {{tr!("Dashboard")}}
48 | } else {
49 | html! {{value}}
50 | }
51 | }
52 | })
53 | .into()
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/server/src/auth/csrf.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 | use std::sync::OnceLock;
3 |
4 | use anyhow::Error;
5 |
6 | use proxmox_sys::fs::file_get_contents;
7 | use proxmox_sys::fs::{replace_file, CreateOptions};
8 |
9 | use proxmox_auth_api::HMACKey;
10 |
11 | use pdm_buildcfg::configdir;
12 |
13 | pub fn csrf_secret() -> &'static HMACKey {
14 | static SECRET: OnceLock = OnceLock::new();
15 |
16 | SECRET.get_or_init(|| {
17 | let bytes = file_get_contents(configdir!("/auth/csrf.key")).unwrap();
18 | std::str::from_utf8(&bytes)
19 | .map_err(anyhow::Error::new)
20 | .and_then(HMACKey::from_base64)
21 | // legacy fall back to load legacy csrf secrets
22 | // TODO: remove once we move away from legacy token verification
23 | .unwrap_or_else(|_| {
24 | let key_as_b64 = proxmox_base64::encode_no_pad(bytes);
25 | HMACKey::from_base64(&key_as_b64).unwrap()
26 | })
27 | })
28 | }
29 |
30 | pub fn generate_csrf_key() -> Result<(), Error> {
31 | let path = PathBuf::from(configdir!("/auth/csrf.key"));
32 |
33 | if path.exists() {
34 | return Ok(());
35 | }
36 |
37 | let key = HMACKey::generate()?.to_base64()?;
38 |
39 | use nix::sys::stat::Mode;
40 |
41 | let api_user = pdm_config::api_user()?;
42 |
43 | replace_file(
44 | &path,
45 | key.as_bytes(),
46 | CreateOptions::new()
47 | .perm(Mode::from_bits_truncate(0o0640))
48 | .owner(nix::unistd::ROOT)
49 | .group(api_user.gid),
50 | true,
51 | )?;
52 |
53 | Ok(())
54 | }
55 |
--------------------------------------------------------------------------------
/server/src/auth/certs.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{format_err, Error};
2 | use std::path::PathBuf;
3 |
4 | use proxmox_dns_api::read_etc_resolv_conf;
5 |
6 | use pdm_buildcfg::configdir;
7 |
8 | pub const API_KEY_FN: &str = configdir!("/auth/api.key");
9 | pub const API_CERT_FN: &str = configdir!("/auth/api.pem");
10 |
11 | /// Update self signed node certificate.
12 | pub fn update_self_signed_cert(force: bool) -> Result<(), Error> {
13 | let key_path = PathBuf::from(API_KEY_FN);
14 | let cert_path = PathBuf::from(API_CERT_FN);
15 |
16 | if key_path.exists() && cert_path.exists() && !force {
17 | return Ok(());
18 | }
19 |
20 | let resolv_conf = read_etc_resolv_conf(None)?.config;
21 |
22 | let (priv_key, cert) = proxmox_acme_api::create_self_signed_cert(
23 | "Proxmox Datacenter Manager",
24 | proxmox_sys::nodename(),
25 | resolv_conf.search.as_deref(),
26 | )?;
27 |
28 | let cert_pem = cert.to_pem()?;
29 | let priv_pem = priv_key.private_key_to_pem_pkcs8()?;
30 |
31 | set_api_certificate(&cert_pem, &priv_pem)?;
32 |
33 | Ok(())
34 | }
35 |
36 | pub(crate) fn set_api_certificate(cert_pem: &[u8], key_pem: &[u8]) -> Result<(), Error> {
37 | let key_path = PathBuf::from(API_KEY_FN);
38 | let cert_path = PathBuf::from(API_CERT_FN);
39 |
40 | proxmox_product_config::replace_privileged_config(key_path, key_pem)
41 | .map_err(|err| format_err!("error writing certificate private key - {}", err))?;
42 | proxmox_product_config::replace_privileged_config(cert_path, cert_pem)
43 | .map_err(|err| format_err!("error writing certificate file - {}", err))?;
44 |
45 | Ok(())
46 | }
47 |
--------------------------------------------------------------------------------
/server/src/test_support/temp.rs:
--------------------------------------------------------------------------------
1 | use std::path::{Path, PathBuf};
2 |
3 | use anyhow::Error;
4 |
5 | use proxmox_sys::fs::CreateOptions;
6 |
7 | /// Temporary file that be cleaned up when dropped.
8 | pub struct NamedTempFile {
9 | path: PathBuf,
10 | }
11 |
12 | impl NamedTempFile {
13 | /// Create a new temporary file.
14 | ///
15 | /// The file will be created with the passed [`CreateOptions`].
16 | pub fn new(options: CreateOptions) -> Result {
17 | let base = std::env::temp_dir().join("test");
18 | let (_, path) = proxmox_sys::fs::make_tmp_file(base, options)?;
19 |
20 | Ok(Self { path })
21 | }
22 |
23 | /// Return the [`Path`] to the temporary file.
24 | pub fn path(&self) -> &Path {
25 | &self.path
26 | }
27 | }
28 |
29 | impl Drop for NamedTempFile {
30 | fn drop(&mut self) {
31 | let _ = std::fs::remove_file(&self.path);
32 | }
33 | }
34 |
35 | /// Temporary directory that is cleaned up when dropped.
36 | pub struct NamedTempDir {
37 | path: PathBuf,
38 | }
39 |
40 | impl NamedTempDir {
41 | /// Create a new temporary directory.
42 | ///
43 | /// The directory will be created with `0o700` permissions.
44 | pub fn new() -> Result {
45 | let path = proxmox_sys::fs::make_tmp_dir("/tmp", None)?;
46 |
47 | Ok(Self { path })
48 | }
49 |
50 | /// Return the [`Path`] to the temporary directory.
51 | pub fn path(&self) -> &Path {
52 | &self.path
53 | }
54 | }
55 |
56 | impl Drop for NamedTempDir {
57 | fn drop(&mut self) {
58 | let _ = std::fs::remove_dir_all(&self.path);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/server/src/api/pve/storage.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 |
3 | use proxmox_router::{list_subdirs_api_method, Permission, Router, SubdirMap};
4 | use proxmox_schema::api;
5 | use proxmox_sortable_macro::sortable;
6 |
7 | use pdm_api_types::remotes::REMOTE_ID_SCHEMA;
8 | use pdm_api_types::{NODE_SCHEMA, PRIV_RESOURCE_AUDIT, PVE_STORAGE_ID_SCHEMA};
9 |
10 | use super::connect_to_remote;
11 |
12 | pub const ROUTER: Router = Router::new()
13 | .get(&list_subdirs_api_method!(STORAGE_SUBDIR))
14 | .subdirs(STORAGE_SUBDIR);
15 |
16 | #[sortable]
17 | const STORAGE_SUBDIR: SubdirMap = &sorted!([
18 | ("rrddata", &super::rrddata::STORAGE_RRD_ROUTER),
19 | ("status", &Router::new().get(&API_METHOD_GET_STATUS)),
20 | ]);
21 |
22 | #[api(
23 | input: {
24 | properties: {
25 | remote: { schema: REMOTE_ID_SCHEMA },
26 | node: { schema: NODE_SCHEMA, },
27 | storage: { schema: PVE_STORAGE_ID_SCHEMA, },
28 | },
29 | },
30 | returns: { type: pve_api_types::StorageStatus },
31 | access: {
32 | permission: &Permission::Privilege(&["resource", "{remote}", "storage", "{storage}"], PRIV_RESOURCE_AUDIT, false),
33 | },
34 | )]
35 | /// Get the status of a qemu VM from a remote. If a node is provided, the VM must be on that
36 | /// node, otherwise the node is determined automatically.
37 | pub async fn get_status(
38 | remote: String,
39 | node: String,
40 | storage: String,
41 | ) -> Result {
42 | let (remotes, _) = pdm_config::remotes::config()?;
43 |
44 | let pve = connect_to_remote(&remotes, &remote)?;
45 |
46 | Ok(pve.storage_status(&node, &storage).await?)
47 | }
48 |
--------------------------------------------------------------------------------
/ui/images/icon-memory.svg:
--------------------------------------------------------------------------------
1 |
2 |
41 |
--------------------------------------------------------------------------------
/ui/src/dashboard/mod.rs:
--------------------------------------------------------------------------------
1 | use pwt::css;
2 | use pwt::prelude::*;
3 | use pwt::widget::{Column, Fa, Row};
4 |
5 | mod top_entities;
6 | pub use top_entities::create_top_entities_panel;
7 |
8 | mod subscription_info;
9 | pub use subscription_info::create_subscription_panel;
10 |
11 | mod subscriptions_list;
12 | pub use subscriptions_list::SubscriptionsList;
13 |
14 | mod remote_panel;
15 | pub use remote_panel::create_remote_panel;
16 |
17 | mod guest_panel;
18 | pub use guest_panel::create_guest_panel;
19 |
20 | mod node_status_panel;
21 | use node_status_panel::create_node_panel;
22 |
23 | mod sdn_zone_panel;
24 | pub use sdn_zone_panel::create_sdn_panel;
25 |
26 | mod status_row;
27 | pub use status_row::DashboardStatusRow;
28 |
29 | mod filtered_tasks;
30 |
31 | mod pbs_datastores_panel;
32 | pub use pbs_datastores_panel::create_pbs_datastores_panel;
33 |
34 | mod tasks;
35 | pub use tasks::create_task_summary_panel;
36 |
37 | pub mod view;
38 |
39 | mod refresh_config_edit;
40 | pub use refresh_config_edit::create_refresh_config_edit_window;
41 |
42 | mod resource_tree;
43 | pub use resource_tree::create_resource_tree;
44 |
45 | fn loading_column() -> Column {
46 | Column::new()
47 | .padding(4)
48 | .class(css::FlexFit)
49 | .class(css::JustifyContent::Center)
50 | .class(css::AlignItems::Center)
51 | .with_child(html! {})
52 | }
53 |
54 | /// Create a consistent title component for the given title and icon
55 | fn create_title_with_icon(icon: &str, title: String) -> Html {
56 | Row::new()
57 | .class(css::AlignItems::Center)
58 | .gap(2)
59 | .with_child(Fa::new(icon))
60 | .with_child(title)
61 | .into()
62 | }
63 |
--------------------------------------------------------------------------------
/ui/debian/rules:
--------------------------------------------------------------------------------
1 | #!/usr/bin/make -f
2 | # See debhelper(7) (uncomment to enable)
3 | # output every command that modifies files on the build system.
4 | DH_VERBOSE = 1
5 |
6 | include /usr/share/dpkg/pkg-info.mk
7 |
8 | export BUILD_MODE=release
9 |
10 | CARGO=/usr/share/cargo/bin/cargo
11 |
12 | export CFLAGS CXXFLAGS CPPFLAGS LDFLAGS
13 | export DEB_HOST_RUST_TYPE DEB_HOST_GNU_TYPE
14 | export CARGO_HOME = $(CURDIR)/debian/cargo_home
15 |
16 | export DEB_CARGO_CRATE=proxmox-datacenter-manager-ui_$(DEB_VERSION_UPSTREAM)
17 | export DEB_CARGO_PACKAGE=proxmox-datacenter-manager-ui
18 |
19 | %:
20 | dh $@
21 |
22 | override_dh_auto_configure:
23 | @perl -ne 'if (/^version\s*=\s*"(\d+(?:\.\d+)+)"/) { my $$v_cargo = $$1; my $$v_deb = "$(DEB_VERSION_UPSTREAM)"; \
24 | die "ERROR: d/changelog <-> Cargo.toml version mismatch: $$v_cargo != $$v_deb\n" if $$v_cargo ne $$v_deb; exit(0); }' Cargo.toml
25 | $(CARGO) prepare-debian $(CURDIR)/debian/cargo_registry --link-from-system
26 | echo "\nlto=\"fat\"" >> debian/cargo_home/config.toml
27 | echo "\nopt-level=\"s\"" >> debian/cargo_home/config.toml
28 | echo "\ncodegen-units=1" >> debian/cargo_home/config.toml
29 | # patch cargo_home config to use lld with wasm, otherwise the build fails
30 | echo "\n[target.wasm32-unknown-unknown]" >> debian/cargo_home/config.toml
31 | cat debian/cargo_home/config.toml | sed "s/linker=[^']\+/linker=rust-lld/" | grep "^rustflags = " >> debian/cargo_home/config.toml
32 | dh_auto_configure
33 |
34 | override_dh_strip:
35 | dh_strip
36 | for exe in $$(find \
37 | debian/*/usr \
38 | -executable -type f); do \
39 | debian/scripts/elf-strip-unused-dependencies.sh "$$exe" || true; \
40 | done
41 |
42 | override_dh_missing:
43 | dh_missing --fail-missing
44 |
--------------------------------------------------------------------------------
/server/src/api/nodes/mod.rs:
--------------------------------------------------------------------------------
1 | //! Server/Node Configuration and Administration
2 |
3 | use proxmox_router::{list_subdirs_api_method, Router, SubdirMap};
4 | use proxmox_sortable_macro::sortable;
5 |
6 | pub mod apt;
7 | pub mod certificates;
8 | pub mod config;
9 | pub mod dns;
10 | pub mod journal;
11 | pub mod network;
12 | pub mod report;
13 | pub mod rrddata;
14 | pub mod sdn;
15 | pub mod status;
16 | pub mod subscription;
17 | pub mod syslog;
18 | pub mod tasks;
19 | pub mod termproxy;
20 | pub mod time;
21 | pub mod vncwebsocket;
22 |
23 | use anyhow::Error;
24 | use serde_json::{json, Value};
25 |
26 | use proxmox_schema::api;
27 |
28 | #[api]
29 | /// List Nodes (only for compatibility)
30 | pub fn list_nodes() -> Result {
31 | Ok(json!([ { "node": proxmox_sys::nodename().to_string() } ]))
32 | }
33 |
34 | pub const ROUTER: Router = Router::new()
35 | .get(&API_METHOD_LIST_NODES)
36 | .match_all("node", &ITEM_ROUTER);
37 |
38 | pub const ITEM_ROUTER: Router = Router::new()
39 | .get(&list_subdirs_api_method!(SUBDIRS))
40 | .subdirs(SUBDIRS);
41 |
42 | #[sortable]
43 | pub const SUBDIRS: SubdirMap = &sorted!([
44 | ("apt", &apt::ROUTER),
45 | ("certificates", &certificates::ROUTER),
46 | ("config", &config::ROUTER),
47 | ("dns", &dns::ROUTER),
48 | ("journal", &journal::ROUTER),
49 | ("network", &network::ROUTER),
50 | ("report", &report::ROUTER),
51 | ("rrdata", &rrddata::ROUTER),
52 | ("sdn", &sdn::ROUTER),
53 | ("subscription", &subscription::ROUTER),
54 | ("status", &status::ROUTER),
55 | ("syslog", &syslog::ROUTER),
56 | ("tasks", &tasks::ROUTER),
57 | ("termproxy", &termproxy::ROUTER),
58 | ("time", &time::ROUTER),
59 | ("vncwebsocket", &vncwebsocket::ROUTER),
60 | ]);
61 |
--------------------------------------------------------------------------------
/debian/rules:
--------------------------------------------------------------------------------
1 | #!/usr/bin/make -f
2 | # See debhelper(7) (uncomment to enable)
3 | # output every command that modifies files on the build system.
4 | DH_VERBOSE = 1
5 |
6 | include /usr/share/dpkg/pkg-info.mk
7 | include /usr/share/rustc/architecture.mk
8 |
9 | export BUILD_MODE=release
10 |
11 | CARGO=/usr/share/cargo/bin/cargo
12 |
13 | export CFLAGS CXXFLAGS CPPFLAGS LDFLAGS
14 | export DEB_HOST_RUST_TYPE DEB_HOST_GNU_TYPE
15 | export CARGO_HOME = $(CURDIR)/debian/cargo_home
16 |
17 | export DEB_CARGO_CRATE=proxmox-datacenter-manager_$(DEB_VERSION_UPSTREAM)
18 | export DEB_CARGO_PACKAGE=proxmox-datacenter-manager
19 |
20 | export DEB_VERSION DEB_VERSION_UPSTREAM
21 |
22 | %:
23 | dh $@
24 |
25 | override_dh_auto_configure:
26 | @perl -ne 'if (/^version\s*=\s*"(\d+(?:\.\d+)+)"/) { my $$v_cargo = $$1; my $$v_deb = "$(DEB_VERSION_UPSTREAM)"; \
27 | die "ERROR: d/changelog <-> Cargo.toml version mismatch: $$v_cargo != $$v_deb\n" if $$v_cargo ne $$v_deb; exit(0); }' Cargo.toml
28 | $(CARGO) prepare-debian $(CURDIR)/debian/cargo_registry --link-from-system
29 | dh_auto_configure
30 |
31 | override_dh_strip:
32 | dh_strip
33 | for exe in $$(find \
34 | debian/proxmox-datacenter-manager/usr \
35 | debian/proxmox-datacenter-manager-client/usr \
36 | -executable -type f); do \
37 | debian/scripts/elf-strip-unused-dependencies.sh "$$exe" || true; \
38 | done
39 |
40 | override_dh_installsystemd:
41 | dh_installsystemd -pproxmox-datacenter-manager proxmox-datacenter-manager-daily-update.timer
42 | # note: we start/try-reload-restart services manually in postinst
43 | dh_installsystemd --no-start --no-restart-after-upgrade --no-stop-on-upgrade
44 |
45 | override_dh_missing:
46 | dh_missing --fail-missing
47 |
48 | override_dh_compress:
49 | dh_compress -X.pdf
50 |
--------------------------------------------------------------------------------
/docs/system-requirements.rst:
--------------------------------------------------------------------------------
1 | System Requirements
2 | -------------------
3 |
4 | We recommend using high quality server hardware when running Proxmox Datacenter Manager in
5 | production. While no managed remote or resource depends on Proxmox Datacenter Manager to run, you
6 | might find that Proxmox Datacenter Manager will become a convenient and critical tool in your
7 | operations fast enough.
8 |
9 | .. _minimum_system_requirements:
10 |
11 | Minimum Server Requirements, for Evaluation
12 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13 |
14 | These minimum requirements are for evaluation purposes only and should not be used in production.
15 |
16 | * CPU: 64bit (*x86-64* or *AMD64*), 1+ Cores
17 |
18 | * Memory (RAM): 1 GiB RAM
19 |
20 | * Hard drive: more than 10 GB of space.
21 |
22 | * Network card (NIC)
23 |
24 |
25 | Recommended Server System Requirements
26 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27 |
28 | * CPU: Modern AMD or Intel 64-bit based CPU, with at least 2 cores
29 |
30 | * Memory: minimum 4 GiB for the OS.
31 |
32 | * OS storage:
33 |
34 | * 40 GB, or more, free storage space
35 | * Use a hardware RAID with battery protected write cache (*BBU*) or a redundant ZFS setup (ZFS is
36 | not compatible with a hardware RAID controller).
37 |
38 | * Redundant Multi-GBit/s network interface cards (NICs)
39 |
40 |
41 |
42 | Supported Web Browsers for Accessing the Web Interface
43 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
44 |
45 | To access the server's web-based user interface, we recommend using one of the following browsers:
46 |
47 | * Firefox, a release from the current year, or the latest Extended Support Release
48 | * Chrome, a release from the current year
49 | * Microsoft's currently supported version of Edge
50 | * Safari, a release from the current year
51 |
--------------------------------------------------------------------------------
/ui/index.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | {{ NodeName }} - Proxmox Datacenter Manager
12 |
13 |
18 |
26 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/ui/images/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/debian/proxmox-datacenter-manager.postinst:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | set -e
4 |
5 | #DEBHELPER#
6 |
7 | case "$1" in
8 | configure)
9 | # modeled after dh_systemd_start output
10 | systemctl --system daemon-reload >/dev/null || true
11 | if [ -n "$2" ]; then
12 | _dh_action=try-reload-or-restart
13 | else
14 | _dh_action=start
15 | fi
16 | deb-systemd-invoke $_dh_action 'proxmox-datacenter-api.service' 'proxmox-datacenter-privileged-api.service' >/dev/null || true
17 |
18 | # For migrations or upgrade steps that can safely be automated.
19 | # NOTE: check if '/proxmox_install_mode' exist to avoid doing something if in the ISO installer environment.
20 |
21 | cfgdir="/etc/proxmox-datacenter-manager"
22 |
23 | if test -n "$2" && dpkg --compare-versions "$2" 'lt' '1.0~'; then
24 | if test -f "$cfgdir/ldap_passwords.json"; then
25 | echo "migrating legacy 'ldap_passwords.json' location to 'access/ldap-passwords.json'"
26 | mv "$cfgdir/ldap_passwords.json" "$cfgdir/access/ldap-passwords.json" || \
27 | echo "moving '$cfgdir/ldap_passwords.json' to '$cfgdir/access/ldap-passwords.json' failed with code $?"
28 | fi
29 | fi
30 |
31 | BETA_SOURCES="/etc/apt/sources.list.d/pdm-test.sources"
32 | if test -f "$BETA_SOURCES" && dpkg --compare-versions "$2" 'lt' '1.0.1' && dpkg --compare-versions "$2" 'gt' '0.0~~'; then
33 | printf "\nNOTE: Remove the pdm-test repository, which was added during the beta phase.\nYou can (re-)add repositories on the web UI (Administration -> Repositories)\n\n"
34 | rm -v "$BETA_SOURCES" || true
35 | fi
36 |
37 | ;;
38 |
39 | abort-upgrade|abort-remove|abort-deconfigure)
40 | ;;
41 |
42 | *)
43 | echo "postinst called with unknown argument \`$1'" >&2
44 | exit 1
45 | ;;
46 | esac
47 |
48 | exit 0
49 |
--------------------------------------------------------------------------------
/server/src/auth/key.rs:
--------------------------------------------------------------------------------
1 | use std::path::PathBuf;
2 | use std::sync::OnceLock;
3 |
4 | use anyhow::Error;
5 |
6 | use proxmox_auth_api::{PrivateKey, PublicKey};
7 | use proxmox_sys::fs::file_get_contents;
8 | use proxmox_sys::fs::{replace_file, CreateOptions};
9 |
10 | use pdm_buildcfg::configdir;
11 |
12 | pub fn public_auth_key() -> &'static PublicKey {
13 | static KEY: OnceLock = OnceLock::new();
14 | KEY.get_or_init(|| {
15 | let pem = file_get_contents(configdir!("/auth/authkey.pub")).unwrap();
16 | PublicKey::from_pem(&pem).unwrap()
17 | })
18 | }
19 |
20 | pub fn private_auth_key() -> &'static PrivateKey {
21 | static KEY: OnceLock = OnceLock::new();
22 |
23 | KEY.get_or_init(|| {
24 | let pem = file_get_contents(configdir!("/auth/authkey.key")).unwrap();
25 | PrivateKey::from_pem(&pem).unwrap()
26 | })
27 | }
28 |
29 | pub fn generate_auth_key() -> Result<(), Error> {
30 | let priv_path = PathBuf::from(configdir!("/auth/authkey.key"));
31 |
32 | let mut public_path = priv_path.clone();
33 | public_path.set_extension("pub");
34 |
35 | if priv_path.exists() && public_path.exists() {
36 | return Ok(());
37 | }
38 |
39 | let key = proxmox_auth_api::PrivateKey::generate_ec()?;
40 |
41 | use nix::sys::stat::Mode;
42 |
43 | replace_file(
44 | &priv_path,
45 | &key.private_key_to_pem()?,
46 | CreateOptions::new().perm(Mode::from_bits_truncate(0o0600)),
47 | true,
48 | )?;
49 |
50 | let api_user = pdm_config::api_user()?;
51 |
52 | replace_file(
53 | &public_path,
54 | &key.public_key_to_pem()?,
55 | CreateOptions::new()
56 | .perm(Mode::from_bits_truncate(0o0640))
57 | .owner(nix::unistd::ROOT)
58 | .group(api_user.gid),
59 | true,
60 | )?;
61 |
62 | Ok(())
63 | }
64 |
--------------------------------------------------------------------------------
/ui/src/dashboard/resource_tree.rs:
--------------------------------------------------------------------------------
1 | use pwt::css;
2 | use pwt::prelude::*;
3 | use pwt::props::{ContainerBuilder, WidgetBuilder, WidgetStyleBuilder};
4 | use pwt::widget::form::Field;
5 | use pwt::widget::Column;
6 | use pwt::widget::Panel;
7 | use pwt::widget::Row;
8 | use pwt::widget::Toolbar;
9 |
10 | use crate::widget::RedrawController;
11 | use crate::widget::ResourceTree;
12 |
13 | #[derive(Properties, Clone, PartialEq)]
14 | struct ResourceTreeWithSearchProps {
15 | redraw_controller: RedrawController,
16 | }
17 |
18 | #[function_component]
19 | fn ResourceTreeWithSearch(props: &ResourceTreeWithSearchProps) -> Html {
20 | let search = use_state(String::new);
21 |
22 | Column::new()
23 | .class(css::FlexFit)
24 | .with_child(
25 | Toolbar::new()
26 | .with_child(tr!("Search"))
27 | .with_child(Field::new().on_change({
28 | let search = search.clone();
29 | move |value| search.set(value)
30 | })),
31 | )
32 | .with_child(
33 | // use another flex layout with base width to work around the data tables dynamic
34 | // column size that does not decrease
35 | Row::new().class(css::FlexFit).with_child(
36 | ResourceTree::new()
37 | .redraw_controller(props.redraw_controller.clone())
38 | .search_term(search.to_string())
39 | .flex(1.0)
40 | .width(250)
41 | .height(500)
42 | .class(css::FlexFit),
43 | ),
44 | )
45 | .into()
46 | }
47 |
48 | pub fn create_resource_tree(redraw_controller: RedrawController) -> Panel {
49 | Panel::new()
50 | .class(css::FlexFit)
51 | .title(tr!("Resources"))
52 | .with_child(html! {})
53 | }
54 |
--------------------------------------------------------------------------------
/server/src/api/pbs/node.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 |
3 | use proxmox_router::{list_subdirs_api_method, Permission, Router, SubdirMap};
4 | use proxmox_schema::api;
5 | use proxmox_sortable_macro::sortable;
6 |
7 | use pdm_api_types::remotes::REMOTE_ID_SCHEMA;
8 | use pdm_api_types::PRIV_RESOURCE_AUDIT;
9 |
10 | use pbs_api_types::NODE_SCHEMA;
11 |
12 | use crate::connection;
13 | use crate::pbs_client::get_remote;
14 |
15 | pub const ROUTER: Router = Router::new()
16 | .get(&list_subdirs_api_method!(SUBDIRS))
17 | .subdirs(SUBDIRS);
18 |
19 | #[sortable]
20 | const SUBDIRS: SubdirMap = &sorted!([
21 | ("apt", &crate::api::remote_updates::APT_ROUTER),
22 | (
23 | "subscription",
24 | &Router::new().get(&API_METHOD_GET_SUBSCRIPTION)
25 | ),
26 | (
27 | "termproxy",
28 | &Router::new().post(&crate::api::remote_shell::API_METHOD_SHELL_TICKET)
29 | ),
30 | (
31 | "vncwebsocket",
32 | &Router::new().upgrade(&crate::api::remote_shell::API_METHOD_SHELL_WEBSOCKET)
33 | ),
34 | ]);
35 |
36 | #[api(
37 | input: {
38 | properties: {
39 | remote: { schema: REMOTE_ID_SCHEMA },
40 | node: { schema: NODE_SCHEMA }, // not used, always localhost
41 | },
42 | },
43 | access: {
44 | permission: &Permission::Privilege(&["resource", "{remote}"], PRIV_RESOURCE_AUDIT, false),
45 | description: "The user needs to have at least the `Resource.Audit` privilege on `/resource/{remote}`."
46 | },
47 | returns: { type: proxmox_subscription::SubscriptionInfo }
48 | )]
49 | /// Get subscription for the PBS remote
50 | async fn get_subscription(remote: String) -> Result {
51 | let (remotes, _) = pdm_config::remotes::config()?;
52 | let remote = get_remote(&remotes, &remote)?;
53 | Ok(connection::make_pbs_client(remote)?
54 | .get_subscription()
55 | .await?)
56 | }
57 |
--------------------------------------------------------------------------------
/cli/admin/src/support_status.rs:
--------------------------------------------------------------------------------
1 | use serde_json::{json, Value};
2 |
3 | use proxmox_router::cli::{
4 | format_and_print_result_full, get_output_format, CliCommand, CliCommandMap,
5 | CommandLineInterface,
6 | };
7 | use proxmox_router::{ApiHandler, RpcEnvironment};
8 | use proxmox_schema::api;
9 |
10 | pub fn cli() -> CommandLineInterface {
11 | CliCommandMap::new()
12 | .insert("get", CliCommand::new(&API_METHOD_SUPPORT_STATUS))
13 | .insert("update", CliCommand::new(&API_METHOD_UPDATE_SUPPORT_STATUS))
14 | .into()
15 | }
16 |
17 | #[api]
18 | /// Get the support status information.
19 | async fn support_status(
20 | param: Value,
21 | rpcenv: &mut dyn RpcEnvironment,
22 | ) -> Result<(), anyhow::Error> {
23 | let info = &server::api::nodes::subscription::API_METHOD_GET_SUBSCRIPTION;
24 |
25 | let mut data = match info.handler {
26 | ApiHandler::Async(handler) => (handler)(json!({}), info, rpcenv).await?,
27 | _ => unreachable!(),
28 | };
29 |
30 | let output_format = get_output_format(¶m);
31 |
32 | format_and_print_result_full(
33 | &mut data,
34 | &info.returns,
35 | &output_format,
36 | &Default::default(),
37 | );
38 | Ok(())
39 | }
40 |
41 | #[api]
42 | /// Update the support status information.
43 | async fn update_support_status(
44 | param: Value,
45 | rpcenv: &mut dyn RpcEnvironment,
46 | ) -> Result<(), anyhow::Error> {
47 | let info = &server::api::nodes::subscription::API_METHOD_CHECK_SUBSCRIPTION;
48 |
49 | let mut data = match info.handler {
50 | ApiHandler::Async(handler) => (handler)(json!({}), info, rpcenv).await?,
51 | _ => unreachable!(),
52 | };
53 |
54 | let output_format = get_output_format(¶m);
55 |
56 | format_and_print_result_full(
57 | &mut data,
58 | &info.returns,
59 | &output_format,
60 | &Default::default(),
61 | );
62 | Ok(())
63 | }
64 |
--------------------------------------------------------------------------------
/cli/client/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "proxmox-datacenter-manager-client"
3 | description = "Proxmox Datacenter Manager command line client"
4 | homepage = "https://www.proxmox.com"
5 |
6 | version.workspace = true
7 | edition.workspace = true
8 | license.workspace = true
9 | repository.workspace = true
10 |
11 | [dependencies]
12 | anyhow.workspace = true
13 | hex.workspace = true
14 | http.workspace = true
15 | libc.workspace = true
16 | log.workspace = true
17 | nix.workspace = true
18 | once_cell.workspace = true
19 | openssl.workspace = true
20 | serde.workspace = true
21 | serde_cbor.workspace = true
22 | serde_json.workspace = true
23 | serde_plain.workspace = true
24 | tokio = { workspace = true, features = [ "time" ] }
25 | webauthn-rs-core.workspace = true
26 | xdg.workspace = true
27 |
28 | pbs-api-types.workspace = true
29 |
30 | pdm-api-types.workspace = true
31 | pdm-buildcfg.workspace = true
32 | pdm-client.workspace = true
33 | pdm-ui-shared.workspace = true
34 |
35 | proxmox-access-control.workspace = true
36 | proxmox-async.workspace = true
37 | proxmox-base64.workspace = true
38 | proxmox-client = { workspace = true, features = [ "hyper-client", "webauthn" ] }
39 | proxmox-config-digest.workspace = true
40 | proxmox-fido2.workspace = true
41 | proxmox-human-byte.workspace = true
42 | proxmox-log.workspace = true
43 | proxmox-login.workspace = true
44 | proxmox-router = { workspace = true, features = [ "cli" ], default-features = false }
45 | proxmox-rrd-api-types.workspace = true
46 | proxmox-schema = { workspace = true, features = [ "api-macro" ] }
47 | proxmox-section-config.workspace = true
48 | proxmox-sys.workspace = true
49 | proxmox-time.workspace = true
50 | proxmox-tfa = { workspace = true, features = [ "types" ] }
51 |
52 | # for the pve API types:
53 | pve-api-types.workspace = true
54 |
55 | # when root wants to log into localhost, we just create a ticket directly
56 | proxmox-auth-api = { workspace = true, features = [ "api", "ticket" ] }
57 |
--------------------------------------------------------------------------------
/lib/pdm-api-types/src/node_config.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | use proxmox_schema::{api, Updater};
4 |
5 | use crate::{
6 | Translation, EMAIL_SCHEMA, HTTP_PROXY_SCHEMA, OPENSSL_CIPHERS_TLS_1_2_SCHEMA,
7 | OPENSSL_CIPHERS_TLS_1_3_SCHEMA,
8 | };
9 |
10 | #[api(
11 | properties: {
12 | "http-proxy": {
13 | schema: HTTP_PROXY_SCHEMA,
14 | optional: true,
15 | },
16 | "email-from": {
17 | schema: EMAIL_SCHEMA,
18 | optional: true,
19 | },
20 | "ciphers-tls-1.3": {
21 | schema: OPENSSL_CIPHERS_TLS_1_3_SCHEMA,
22 | optional: true,
23 | },
24 | "ciphers-tls-1.2": {
25 | schema: OPENSSL_CIPHERS_TLS_1_2_SCHEMA,
26 | optional: true,
27 | },
28 | "default-lang" : {
29 | schema: Translation::API_SCHEMA,
30 | optional: true,
31 | },
32 | },
33 | )]
34 | #[derive(Deserialize, Serialize, Updater)]
35 | #[serde(rename_all = "kebab-case")]
36 | /// Node specific configuration.
37 | pub struct NodeConfig {
38 | #[serde(skip_serializing_if = "Option::is_none")]
39 | pub http_proxy: Option,
40 |
41 | #[serde(skip_serializing_if = "Option::is_none")]
42 | pub email_from: Option,
43 |
44 | /// List of TLS ciphers for TLS 1.3 that will be used by the proxy. (Proxy has to be restarted for changes to take effect)
45 | #[serde(skip_serializing_if = "Option::is_none", rename = "ciphers-tls-1.3")]
46 | pub ciphers_tls_1_3: Option,
47 |
48 | /// List of TLS ciphers for TLS <= 1.2 that will be used by the proxy. (Proxy has to be restarted for changes to take effect)
49 | #[serde(skip_serializing_if = "Option::is_none", rename = "ciphers-tls-1.2")]
50 | pub ciphers_tls_1_2: Option,
51 |
52 | /// Default language used in the GUI
53 | #[serde(skip_serializing_if = "Option::is_none")]
54 | pub default_lang: Option,
55 | }
56 |
--------------------------------------------------------------------------------
/ui/images/icon-cpu.svg:
--------------------------------------------------------------------------------
1 |
2 |
50 |
--------------------------------------------------------------------------------
/ui/src/sdn/evpn/mod.rs:
--------------------------------------------------------------------------------
1 | mod evpn_panel;
2 | pub use evpn_panel::EvpnPanel;
3 |
4 | mod remote_tree;
5 | pub use remote_tree::RemoteTree;
6 |
7 | mod vrf_tree;
8 | pub use vrf_tree::VrfTree;
9 |
10 | mod add_vnet;
11 | pub use add_vnet::AddVnetWindow;
12 |
13 | mod add_zone;
14 | pub use add_zone::AddZoneWindow;
15 |
16 | mod zone_status;
17 | pub use zone_status::ZoneStatusTable;
18 |
19 | mod vnet_status;
20 | pub use vnet_status::VnetStatusTable;
21 |
22 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)]
23 | pub struct EvpnRouteTarget {
24 | asn: u32,
25 | vni: u32,
26 | }
27 |
28 | impl std::str::FromStr for EvpnRouteTarget {
29 | type Err = anyhow::Error;
30 |
31 | fn from_str(value: &str) -> Result {
32 | if let Some((asn, vni)) = value.split_once(':') {
33 | return Ok(Self {
34 | asn: asn.parse()?,
35 | vni: vni.parse()?,
36 | });
37 | }
38 |
39 | anyhow::bail!("could not parse EVPN route target!")
40 | }
41 | }
42 |
43 | impl std::fmt::Display for EvpnRouteTarget {
44 | fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
45 | write!(f, "{}:{}", self.asn, self.vni)
46 | }
47 | }
48 |
49 | #[derive(Debug, Clone, PartialEq, Default)]
50 | #[repr(transparent)]
51 | pub struct NodeList(Vec);
52 |
53 | impl std::ops::Deref for NodeList {
54 | type Target = Vec;
55 |
56 | fn deref(&self) -> &Self::Target {
57 | &self.0
58 | }
59 | }
60 |
61 | impl std::str::FromStr for NodeList {
62 | type Err = anyhow::Error;
63 |
64 | fn from_str(value: &str) -> Result {
65 | if value.is_empty() {
66 | anyhow::bail!("node list cannot be an empty string");
67 | }
68 |
69 | Ok(Self(value.split(",").map(String::from).collect()))
70 | }
71 | }
72 |
73 | impl FromIterator for NodeList {
74 | fn from_iter>(iter: I) -> Self {
75 | Self(iter.into_iter().collect())
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/lib/pdm-config/src/node.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 | use openssl::ssl::{SslAcceptor, SslMethod};
3 |
4 | use proxmox_schema::ApiType;
5 |
6 | use proxmox_http::ProxyConfig;
7 |
8 | use pdm_api_types::ConfigDigest;
9 |
10 | use pdm_buildcfg::configdir;
11 | use proxmox_product_config::{open_api_lockfile, replace_config, ApiLockGuard};
12 |
13 | use pdm_api_types::NodeConfig;
14 |
15 | const CONF_FILE: &str = configdir!("/node.cfg");
16 | const LOCK_FILE: &str = configdir!("/.node.lck");
17 |
18 | pub fn lock() -> Result {
19 | open_api_lockfile(LOCK_FILE, None, true)
20 | }
21 |
22 | /// Read the Node Config.
23 | pub fn config() -> Result<(NodeConfig, ConfigDigest), Error> {
24 | let content = proxmox_sys::fs::file_read_optional_string(CONF_FILE)?.unwrap_or_default();
25 |
26 | let digest = openssl::sha::sha256(content.as_bytes());
27 | let data: NodeConfig = proxmox_simple_config::from_str(&content, &NodeConfig::API_SCHEMA)?;
28 |
29 | Ok((data, digest.into()))
30 | }
31 |
32 | /// Write the Node Config, requires the write lock to be held.
33 | pub fn save_config(config: &NodeConfig) -> Result<(), Error> {
34 | validate_node_config(config)?;
35 |
36 | let raw = proxmox_simple_config::to_bytes(config, &NodeConfig::API_SCHEMA)?;
37 | replace_config(CONF_FILE, &raw)
38 | }
39 |
40 | /// Returns the parsed ProxyConfig
41 | pub fn get_http_proxy_config(config: &NodeConfig) -> Option {
42 | if let Some(http_proxy) = &config.http_proxy {
43 | ProxyConfig::parse_proxy_url(http_proxy).ok()
44 | } else {
45 | None
46 | }
47 | }
48 |
49 | // Validate the configuration.
50 | fn validate_node_config(config: &NodeConfig) -> Result<(), Error> {
51 | let mut dummy_acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
52 | if let Some(ciphers) = config.ciphers_tls_1_3.as_deref() {
53 | dummy_acceptor.set_ciphersuites(ciphers)?;
54 | }
55 | if let Some(ciphers) = config.ciphers_tls_1_2.as_deref() {
56 | dummy_acceptor.set_cipher_list(ciphers)?;
57 | }
58 |
59 | Ok(())
60 | }
61 |
--------------------------------------------------------------------------------
/server/src/bin/docgen.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{bail, Error};
2 |
3 | use proxmox_schema::format::dump_enum_properties;
4 | use proxmox_schema::ApiType;
5 | use proxmox_section_config::{dump_section_config, typed::ApiSectionDataEntry};
6 |
7 | use pdm_api_types::PRIVILEGES;
8 |
9 | use server::api;
10 |
11 | fn get_args() -> (String, Vec) {
12 | let mut args = std::env::args();
13 | let prefix = args.next().unwrap();
14 | let prefix = prefix.rsplit('/').next().unwrap().to_string(); // without path
15 | let args: Vec = args.collect();
16 |
17 | (prefix, args)
18 | }
19 |
20 | fn main() -> Result<(), Error> {
21 | let (_prefix, args) = get_args();
22 |
23 | if args.is_empty() {
24 | bail!("missing arguments");
25 | }
26 |
27 | for arg in args.iter() {
28 | let text = match arg.as_ref() {
29 | "apidata.js" => generate_api_tree(),
30 | "domains.cfg" => dump_section_config(&pdm_config::domains::CONFIG),
31 | //TODO: needs pub changes in proxmox-access-control
32 | //"user.cfg" => dump_section_config(&proxmox_access_control::user::CONFIG)
33 | "remotes.cfg" => dump_section_config(pdm_api_types::remotes::Remote::section_config()),
34 | "views.cfg" => {
35 | dump_section_config(pdm_api_types::views::ViewConfigEntry::section_config())
36 | }
37 | "config::acl::Role" => dump_enum_properties(&pdm_api_types::Role::API_SCHEMA)?,
38 | _ => bail!("docgen: got unknown type"),
39 | };
40 | println!("{}", text);
41 | }
42 |
43 | Ok(())
44 | }
45 |
46 | fn generate_api_tree() -> String {
47 | let mut tree = Vec::new();
48 |
49 | let mut data = proxmox_docgen::generate_api_tree(&api::ROUTER, ".", PRIVILEGES);
50 | data["path"] = "/".into();
51 | // hack: add invisible space to sort as first entry
52 | data["text"] = "Management API (HTTP)".into();
53 | tree.push(data);
54 |
55 | format!(
56 | "var apiSchema = {};",
57 | serde_json::to_string_pretty(&tree).unwrap()
58 | )
59 | }
60 |
--------------------------------------------------------------------------------
/server/src/api/config/notes.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 |
3 | use proxmox_config_digest::ConfigDigest;
4 | use proxmox_router::{Permission, Router, RpcEnvironment};
5 | use proxmox_schema::api;
6 |
7 | use pdm_api_types::{PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
8 | use pdm_buildcfg::configdir;
9 |
10 | pub const ROUTER: Router = Router::new()
11 | .get(&API_METHOD_GET_NOTES)
12 | .put(&API_METHOD_UPDATE_NOTES);
13 |
14 | pub const NOTES_FILENAME: &str = configdir!("/notes.md");
15 |
16 | fn read_notes() -> Result<(String, ConfigDigest), Error> {
17 | let notes = proxmox_sys::fs::file_read_optional_string(NOTES_FILENAME)?.unwrap_or_default();
18 | let digest = openssl::sha::sha256(notes.as_bytes());
19 |
20 | Ok((notes, digest.into()))
21 | }
22 |
23 | #[api(
24 | access: {
25 | permission: &Permission::Privilege(&["system"], PRIV_SYS_AUDIT, false),
26 | },
27 | returns: {
28 | description: "Notes, utf8 encoded markdown file.",
29 | type: String,
30 | },
31 | protected: true,
32 | )]
33 | /// Get notes.
34 | pub fn get_notes(rpcenv: &mut dyn RpcEnvironment) -> Result {
35 | let (notes, digest) = read_notes()?;
36 | rpcenv["digest"] = digest.to_hex().into();
37 | Ok(notes)
38 | }
39 |
40 | #[api(
41 | input: {
42 | properties: {
43 | notes: {
44 | description: "New notes text.",
45 | type: String,
46 | },
47 | digest: {
48 | type: ConfigDigest,
49 | optional: true,
50 | },
51 | },
52 | },
53 | access: {
54 | // fixme: maybe we want something else here ...
55 | permission: &Permission::Privilege(&["system", "notes"], PRIV_SYS_MODIFY, false),
56 | }
57 | )]
58 | /// Update Notes
59 | pub fn update_notes(notes: String, digest: Option) -> Result<(), Error> {
60 | if digest.is_some() {
61 | let (_old, expected_digest) = read_notes()?;
62 | expected_digest.detect_modification(digest.as_ref())?;
63 | }
64 | proxmox_product_config::replace_config(NOTES_FILENAME, notes.as_bytes())
65 | }
66 |
--------------------------------------------------------------------------------
/server/src/bin/proxmox-datacenter-manager-banner.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{format_err, Error};
2 |
3 | use std::fmt::Write;
4 | use std::fs;
5 | use std::net::ToSocketAddrs;
6 | use std::os::unix::prelude::OsStrExt;
7 |
8 | use nix::sys::utsname::uname;
9 |
10 | fn nodename() -> Result {
11 | let uname = uname().map_err(|err| format_err!("uname() failed - {err}"))?; // save on stack to avoid to_owned() allocation below
12 | std::str::from_utf8(uname.nodename().as_bytes())?
13 | .split('.')
14 | .next()
15 | .ok_or_else(|| format_err!("Failed to split FQDN to get hostname"))
16 | .map(|s| s.to_owned())
17 | }
18 |
19 | fn main() {
20 | let nodename = match nodename() {
21 | Ok(value) => value,
22 | Err(err) => {
23 | eprintln!("Failed to retrieve hostname: {err}");
24 | "INVALID".to_string()
25 | }
26 | };
27 |
28 | let addr = format!("{nodename}:8443");
29 |
30 | let mut banner = format!(
31 | "
32 | {:-<78}
33 |
34 | Welcome to the Proxmox Datacenter Manager. Please use your web browser to
35 | configure this server - connect to:
36 |
37 | ",
38 | ""
39 | );
40 |
41 | let msg = match addr.to_socket_addrs() {
42 | Ok(saddrs) => {
43 | let saddrs: Vec<_> = saddrs
44 | .filter_map(|s| match !s.ip().is_loopback() {
45 | true => Some(format!(" https://{s}/")),
46 | false => None,
47 | })
48 | .collect();
49 |
50 | if !saddrs.is_empty() {
51 | saddrs.join("\n")
52 | } else {
53 | format!("hostname '{nodename}' does not resolve to any non-loopback address",)
54 | }
55 | }
56 | Err(e) => format!("could not resolve hostname '{nodename}': {e}"),
57 | };
58 | banner += &msg;
59 |
60 | // unwrap will never fail for write!:
61 | // https://github.com/rust-lang/rust/blob/1.39.0/src/liballoc/string.rs#L2318-L2331
62 | write!(&mut banner, "\n\n{:-<78}\n\n", "").unwrap();
63 |
64 | fs::write("/etc/issue", banner.as_bytes()).expect("Unable to write banner to issue file");
65 | }
66 |
--------------------------------------------------------------------------------
/ui/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "pdm-ui"
3 | version = "1.0.1"
4 | edition = "2021"
5 | license = "AGPL-3"
6 | repository = "https://git.proxmox.com/?p=proxmox-datacenter-manager.git"
7 |
8 | [workspace]
9 | resolver = "2"
10 |
11 | [dependencies]
12 | anyhow = "1.0"
13 | futures = "0.3"
14 | gloo-net = "0.4"
15 | gloo-timers = "0.3"
16 | gloo-utils = "0.2"
17 | http = "1"
18 | js-sys = "0.3.69"
19 | log = "0.4.6"
20 | percent-encoding = "2.1"
21 | serde = { version = "1.0", features = ["derive"] }
22 | serde_json = "1.0"
23 | wasm-bindgen = "0.2.92"
24 | wasm-bindgen-futures = "0.4"
25 | wasm-logger = "0.2"
26 | web-sys = { version = "0.3", features = ["Location", "DataTransfer"] }
27 | yew = { version = "0.21", features = ["csr"] }
28 | yew-router = { version = "0.18" }
29 |
30 | pwt = "0.7.1"
31 | pwt-macros = "0.5"
32 |
33 | proxmox-yew-comp = { version = "0.8.1", features = ["apt", "dns", "network", "rrd"] }
34 |
35 | proxmox-access-control = { version = "1.1", features = []}
36 | proxmox-acme-api = "1"
37 | proxmox-deb-version = "0.1"
38 | proxmox-client = "1"
39 | proxmox-human-byte = "1"
40 | proxmox-login = "1"
41 | proxmox-schema = "5"
42 | proxmox-subscription = { version = "1.0.1", features = ["api-types"], default-features = false }
43 | proxmox-rrd-api-types = "1"
44 | proxmox-node-status = "1"
45 | pbs-api-types = { version = "1.0.3", features = [ "enum-fallback" ] }
46 |
47 | pdm-api-types = { version = "1.0", path = "../lib/pdm-api-types" }
48 | pdm-client = { version = "1.0", path = "../lib/pdm-client" }
49 |
50 | pdm-ui-shared = { version = "1.0", path = "../lib/pdm-ui-shared" }
51 |
52 | pdm-search = { version = "0.2", path = "../lib/pdm-search" }
53 |
54 | [patch.crates-io]
55 | # proxmox-client = { path = "../../proxmox/proxmox-client" }
56 | # proxmox-human-byte = { path = "../../proxmox/proxmox-human-byte" }
57 | # proxmox-login = { path = "../../proxmox/proxmox-login" }
58 | # proxmox-rrd-api-types = { path = "../../proxmox/proxmox-rrd-api-types" }
59 | # proxmox-schema = { path = "../../proxmox/proxmox-schema" }
60 | # proxmox-yew-comp = { path = "../../proxmox-yew-comp" }
61 | # pwt = { path = "../../proxmox-yew-widget-toolkit" }
62 | # pwt-macros = { path = "../../proxmox-yew-widget-toolkit/pwt-macros" }
63 |
--------------------------------------------------------------------------------
/docs/views.rst:
--------------------------------------------------------------------------------
1 | .. _views:
2 |
3 | Views
4 | =====
5 |
6 | Views allow you to add an interactive view on a selected set of resources.
7 |
8 | Resource Selection
9 | ------------------
10 |
11 | The resource selection is controlled by an include-exclude filter system.
12 |
13 | You define what resources to consider for including which then get passed through an exclude list to
14 | single specific types out again.
15 |
16 | This way you can, for example, easily configure to include all virtual machine resources, but then
17 | exclude any such VM that resides on a specific remote.
18 |
19 | Filter Types
20 | ^^^^^^^^^^^^
21 |
22 | .. todo auto-generate below list
23 |
24 | The following lists of filter types are available to be used in include or exclude lists.
25 |
26 | - The `resource-type` filter allows you to filter by a specific resource type.
27 | The following types are available:
28 |
29 | - `datastore`: A Proxmox Backup Server datastore.
30 | - `lxc`: A LXC container.
31 | - `node`: A Proxmox VE or Proxmox Backup Server node.
32 | - `qemu`: A QEMU virtual machine.
33 | - `sdn-zone`: A SDN zone.
34 | - `storage`: A Proxmox VE storage
35 |
36 | - The `resource-pool` filter allows you to include or exclude only resources that are located in a
37 | specific resource pool-name.
38 | - The `tag` filter allows you to filter resources that are tagged with a specific tag-name.
39 | - The `remote` filter allows you to filter resources located on a specific remote.
40 | - The `resource-id` filter allows you to filter resources with a specific ID.
41 |
42 |
43 | Each filter can be prefixed with an optional `:` prefix. Currently there is only
44 | the `exact` matching behavior available. This behavior is the default if no prefix is provided.
45 |
46 |
47 | Customizable Dashboard
48 | ----------------------
49 |
50 | You can create customizable dashboards for a views from a set of pre-defined widgets.
51 | Only resources matching your include minus the ones matching your exclude filters will be displayed
52 | in these widgets.
53 |
54 |
55 | Access Control
56 | --------------
57 |
58 | You can grant permissions on specific views. With such a permission the user can operate on the
59 | view and all its selected resources.
60 |
--------------------------------------------------------------------------------
/cli/client/src/metric_collection.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 | use pdm_api_types::remotes::REMOTE_ID_SCHEMA;
3 | use proxmox_router::cli::{
4 | format_and_print_result, CliCommand, CliCommandMap, CommandLineInterface, OutputFormat,
5 | };
6 | use proxmox_schema::api;
7 |
8 | use crate::{client, env};
9 |
10 | pub fn cli() -> CommandLineInterface {
11 | CliCommandMap::new()
12 | .insert(
13 | "trigger",
14 | CliCommand::new(&API_METHOD_TRIGGER_METRIC_COLLECTION),
15 | )
16 | .insert(
17 | "status",
18 | CliCommand::new(&API_METHOD_METRIC_COLLECTION_STATUS),
19 | )
20 | .into()
21 | }
22 |
23 | #[api(
24 | input: {
25 | properties: {
26 | remote: {
27 | schema: REMOTE_ID_SCHEMA,
28 | optional: true,
29 | },
30 | }
31 | }
32 | )]
33 | /// Trigger metric collection. If a remote is passed, only this remote will be collected, otherwise
34 | /// all.
35 | async fn trigger_metric_collection(remote: Option) -> Result<(), Error> {
36 | client()?
37 | .trigger_metric_collection(remote.as_deref())
38 | .await?;
39 | Ok(())
40 | }
41 |
42 | #[api]
43 | /// Show metric collection status.
44 | async fn metric_collection_status() -> Result<(), Error> {
45 | let result = client()?.get_metric_collection_status().await?;
46 |
47 | let output_format = env().format_args.output_format;
48 | if output_format == OutputFormat::Text {
49 | for remote_status in result {
50 | let timestamp = if let Some(last_collection) = remote_status.last_collection {
51 | proxmox_time::strftime_local("%a, %d %b %Y %T %z", last_collection)?
52 | } else {
53 | "never".into()
54 | };
55 |
56 | let status = if let Some(err) = &remote_status.error {
57 | err
58 | } else {
59 | "ok"
60 | };
61 |
62 | println!("{}: {status}", remote_status.remote);
63 | println!(" last successful: {timestamp}");
64 | println!();
65 | }
66 | } else {
67 | format_and_print_result(&result, &output_format.to_string());
68 | }
69 | Ok(())
70 | }
71 |
--------------------------------------------------------------------------------
/server/src/api/nodes/dns.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Error;
2 | use serde_json::Value;
3 |
4 | use proxmox_router::{ApiMethod, Permission, Router, RpcEnvironment};
5 | use proxmox_schema::api;
6 |
7 | use pdm_api_types::{ConfigDigest, NODE_SCHEMA, PRIV_SYS_AUDIT, PRIV_SYS_MODIFY};
8 |
9 | use proxmox_dns_api::{DeletableResolvConfProperty, ResolvConf, ResolvConfWithDigest};
10 |
11 | #[api(
12 | protected: true,
13 | input: {
14 | description: "Update DNS settings.",
15 | properties: {
16 | node: {
17 | schema: NODE_SCHEMA,
18 | },
19 | update: {
20 | type: ResolvConf,
21 | flatten: true,
22 | },
23 | delete: {
24 | description: "List of properties to delete.",
25 | type: Array,
26 | optional: true,
27 | items: {
28 | type: DeletableResolvConfProperty,
29 | }
30 | },
31 | digest: {
32 | type: ConfigDigest,
33 | optional: true,
34 | },
35 | },
36 | },
37 | access: {
38 | permission: &Permission::Privilege(&["system", "network", "dns"], PRIV_SYS_MODIFY, false),
39 | }
40 | )]
41 | /// Update DNS settings
42 | pub fn update_dns(
43 | update: ResolvConf,
44 | delete: Option>,
45 | digest: Option,
46 | ) -> Result<(), Error> {
47 | proxmox_dns_api::update_dns(update, delete, digest)
48 | }
49 |
50 | #[api(
51 | input: {
52 | properties: {
53 | node: {
54 | schema: NODE_SCHEMA,
55 | },
56 | },
57 | },
58 | returns: {
59 | type: ResolvConfWithDigest,
60 | },
61 | access: {
62 | permission: &Permission::Privilege(&["system", "network", "dns"], PRIV_SYS_AUDIT, false),
63 | }
64 | )]
65 | /// Read DNS settings.
66 | pub fn get_dns(
67 | _param: Value,
68 | _info: &ApiMethod,
69 | _rpcenv: &mut dyn RpcEnvironment,
70 | ) -> Result {
71 | proxmox_dns_api::read_etc_resolv_conf(None)
72 | }
73 |
74 | pub const ROUTER: Router = Router::new()
75 | .get(&API_METHOD_GET_DNS)
76 | .put(&API_METHOD_UPDATE_DNS);
77 |
--------------------------------------------------------------------------------
/ui/src/remotes/mod.rs:
--------------------------------------------------------------------------------
1 | mod wizard_page_connect;
2 | use wizard_page_connect::WizardPageConnect;
3 |
4 | mod wizard_page_nodes;
5 | use wizard_page_nodes::WizardPageNodes;
6 |
7 | mod wizard_page_summary;
8 | pub use wizard_page_summary::WizardPageSummary;
9 |
10 | mod wizard_page_info;
11 | pub use wizard_page_info::WizardPageInfo;
12 |
13 | mod add_wizard;
14 | pub use add_wizard::AddWizard;
15 |
16 | mod node_url_list;
17 | pub use node_url_list::NodeUrlList;
18 |
19 | mod edit_remote;
20 |
21 | mod config;
22 | pub use config::{create_remote, RemoteConfigPanel};
23 |
24 | mod tasks;
25 | pub use tasks::RemoteTaskList;
26 |
27 | mod updates;
28 | pub use updates::UpdateTree;
29 |
30 | mod firewall;
31 | pub use firewall::FirewallTree;
32 |
33 | use yew::{function_component, Html};
34 |
35 | use pwt::prelude::*;
36 | use pwt::{
37 | props::StorageLocation,
38 | state::NavigationContainer,
39 | widget::{MiniScrollMode, TabBarItem, TabPanel},
40 | };
41 |
42 | #[function_component(RemotesPanel)]
43 | pub fn system_configuration() -> Html {
44 | let panel = TabPanel::new()
45 | .state_id(StorageLocation::session("RemotesPanelState"))
46 | .class(pwt::css::FlexFit)
47 | .router(true)
48 | .scroll_mode(MiniScrollMode::Arrow)
49 | .with_item_builder(
50 | TabBarItem::new()
51 | .key("configuration")
52 | .label(tr!("Configuration"))
53 | .icon_class("fa fa-cogs"),
54 | |_| RemoteConfigPanel::new().into(),
55 | )
56 | .with_item_builder(
57 | TabBarItem::new()
58 | .key("tasks")
59 | .label(tr!("Tasks"))
60 | .icon_class("fa fa-book"),
61 | |_| RemoteTaskList::new().into(),
62 | )
63 | .with_item_builder(
64 | TabBarItem::new()
65 | .key("updates")
66 | .label(tr!("Updates"))
67 | .icon_class("fa fa-refresh"),
68 | |_| UpdateTree::new().into(),
69 | )
70 | .with_item_builder(
71 | TabBarItem::new()
72 | .key("firewall")
73 | .label(tr!("Firewall"))
74 | .icon_class("fa fa-shield"),
75 | |_| FirewallTree::new().into(),
76 | );
77 |
78 | NavigationContainer::new().with_child(panel).into()
79 | }
80 |
--------------------------------------------------------------------------------
/ui/src/configuration/permission_path_selector.rs:
--------------------------------------------------------------------------------
1 | use std::rc::Rc;
2 |
3 | use yew::html::IntoPropValue;
4 |
5 | use pwt::prelude::*;
6 | use pwt::widget::form::Combobox;
7 |
8 | use pwt_macros::{builder, widget};
9 |
10 | static PREDEFINED_PATHS: &[&str] = &[
11 | "/",
12 | "/access",
13 | "/access/acl",
14 | "/access/users",
15 | "/resource",
16 | "/system",
17 | "/system/certificates",
18 | "/system/disks",
19 | "/system/log",
20 | "/system/network",
21 | "/system/network/dns",
22 | "/system/network/interfaces",
23 | "/system/notifications",
24 | "/system/services",
25 | "/system/status",
26 | "/system/tasks",
27 | "/system/time",
28 | "/view",
29 | ];
30 |
31 | #[widget(comp=PdmPermissionPathSelector, @input, @element)]
32 | #[derive(Clone, PartialEq, Properties)]
33 | #[builder]
34 | pub struct PermissionPathSelector {
35 | /// Default value
36 | #[builder(IntoPropValue, into_prop_value)]
37 | #[prop_or_default]
38 | default: Option,
39 | }
40 |
41 | impl PermissionPathSelector {
42 | pub(super) fn new() -> Self {
43 | yew::props!(Self {})
44 | }
45 | }
46 |
47 | enum Msg {}
48 |
49 | struct PdmPermissionPathSelector {
50 | items: Rc>,
51 | }
52 |
53 | impl PdmPermissionPathSelector {}
54 |
55 | impl Component for PdmPermissionPathSelector {
56 | type Message = Msg;
57 | type Properties = PermissionPathSelector;
58 |
59 | fn create(_ctx: &Context) -> Self {
60 | // TODO: fetch resources & remotes from the backend to improve the pre-defined selection of
61 | // acl paths
62 | Self {
63 | items: Rc::new(
64 | PREDEFINED_PATHS
65 | .iter()
66 | .map(|i| AttrValue::from(*i))
67 | .collect(),
68 | ),
69 | }
70 | }
71 |
72 | fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool {
73 | false
74 | }
75 |
76 | fn view(&self, ctx: &Context) -> Html {
77 | let props = ctx.props();
78 | Combobox::new()
79 | .with_std_props(&props.std_props)
80 | .with_input_props(&props.input_props)
81 | .default(props.default.clone())
82 | .items(Rc::clone(&self.items))
83 | .editable(true)
84 | .into()
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/ui/src/pbs/datastore.rs:
--------------------------------------------------------------------------------
1 | use std::rc::Rc;
2 |
3 | use yew::{
4 | virtual_dom::{VComp, VNode},
5 | Component, Properties,
6 | };
7 |
8 | use pwt::{css::FlexFit, props::WidgetBuilder, tr, widget::TabBarItem};
9 |
10 | use pbs_api_types::DataStoreConfig;
11 |
12 | use crate::pbs::SnapshotList;
13 |
14 | mod overview;
15 | use overview::DataStoreOverview;
16 |
17 | #[derive(Properties, PartialEq)]
18 | pub struct DatastorePanel {
19 | remote: String,
20 | config: DataStoreConfig,
21 | }
22 |
23 | impl DatastorePanel {
24 | pub fn new(remote: String, config: DataStoreConfig) -> Self {
25 | yew::props!(Self { remote, config })
26 | }
27 | }
28 |
29 | impl From for VNode {
30 | fn from(val: DatastorePanel) -> Self {
31 | VComp::new::(Rc::new(val), None).into()
32 | }
33 | }
34 |
35 | #[doc(hidden)]
36 | struct DatastorePanelComp {}
37 |
38 | impl Component for DatastorePanelComp {
39 | type Message = ();
40 | type Properties = DatastorePanel;
41 |
42 | fn create(_ctx: &yew::Context) -> Self {
43 | Self {}
44 | }
45 |
46 | fn view(&self, ctx: &yew::Context) -> yew::Html {
47 | let props = ctx.props();
48 | pwt::widget::TabPanel::new()
49 | .router(true)
50 | .class(FlexFit)
51 | .title(tr!("Datastore {0}", props.config.name))
52 | .with_item_builder(
53 | TabBarItem::new()
54 | .key("overview")
55 | .label(tr!("Overview"))
56 | .icon_class("fa fa-tachometer"),
57 | {
58 | let remote = props.remote.clone();
59 | let config = props.config.clone();
60 | move |_| DataStoreOverview::new(remote.clone(), config.clone()).into()
61 | },
62 | )
63 | .with_item_builder(
64 | TabBarItem::new()
65 | .key("content")
66 | .label(tr!("Content"))
67 | .icon_class("fa fa-th"),
68 | {
69 | let remote = props.remote.clone();
70 | let name = props.config.name.clone();
71 | move |_| SnapshotList::new(remote.clone(), name.clone()).into()
72 | },
73 | )
74 | .into()
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/ui/images/icon-sdn.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
71 |
--------------------------------------------------------------------------------
/ui/src/certificates.rs:
--------------------------------------------------------------------------------
1 | use pwt::prelude::*;
2 | use pwt::props::StorageLocation;
3 | use pwt::state::NavigationContainer;
4 | use pwt::widget::{Container, MiniScrollMode, TabBarItem, TabPanel};
5 |
6 | use proxmox_yew_comp::acme::{
7 | AcmeAccountsPanel, AcmeDomainsPanel, AcmePluginsPanel, CertificateList,
8 | };
9 |
10 | #[function_component(CertificatesPanel)]
11 | pub fn certificates_panel() -> Html {
12 | let panel = TabPanel::new()
13 | .state_id(StorageLocation::session("CertificatesState"))
14 | .class(pwt::css::FlexFit)
15 | //.title("Certificates")
16 | .router(true)
17 | .scroll_mode(MiniScrollMode::Arrow)
18 | .with_item_builder(
19 | TabBarItem::new()
20 | .key("certificate_List")
21 | .label(tr!("Certificates")),
22 | |_| {
23 | Container::new()
24 | .class("pwt-content-spacer")
25 | .class(pwt::css::FlexFit)
26 | .with_child(CertificateList::new())
27 | .into()
28 | },
29 | )
30 | .with_item_builder(
31 | TabBarItem::new()
32 | .key("acme_domains")
33 | // TRANSLATORS: ACME Stands for Automatic Certificate Management Environment
34 | .label(tr!("ACME Domains")),
35 | |_| {
36 | Container::new()
37 | .class("pwt-content-spacer")
38 | .class(pwt::css::FlexFit)
39 | .with_child(AcmeDomainsPanel::new().url("/config/certificate"))
40 | .into()
41 | },
42 | )
43 | .with_item_builder(
44 | TabBarItem::new()
45 | .key("acme_accounts")
46 | .label(tr!("ACME Accounts")),
47 | |_| {
48 | Container::new()
49 | .class("pwt-content-spacer")
50 | .class(pwt::css::FlexFit)
51 | .with_child(AcmeAccountsPanel::new())
52 | .into()
53 | },
54 | )
55 | .with_item_builder(
56 | TabBarItem::new()
57 | .key("acme_plugins")
58 | .label(tr!("Challenge Plugins")),
59 | |_| {
60 | Container::new()
61 | .class("pwt-content-spacer")
62 | .class(pwt::css::FlexFit)
63 | .with_child(AcmePluginsPanel::new())
64 | .into()
65 | },
66 | );
67 |
68 | NavigationContainer::new().with_child(panel).into()
69 | }
70 |
--------------------------------------------------------------------------------
/docs/remotes.rst:
--------------------------------------------------------------------------------
1 | Remotes
2 | =======
3 |
4 | Proxmox Datacenter Manager allows you to add arbitrary Proxmox VE nodes or clusters and Proxmox
5 | Backup Server instances as remotes. This allows for a structured, unified overview of every host,
6 | VM, container, and datastore across different locations.
7 |
8 | Resource Operation
9 | ------------------
10 |
11 | Through the Proxmox Datacenter Manager, administrators can manage the lifecycle of virtual workloads
12 | at scale. Supported operations include starting, stopping, and rebooting guests across the inventory
13 | without the need to log in to individual nodes.
14 |
15 | Additionally, the platform supports live migration of guests. This capability extends to migrations
16 | between independent clusters, facilitating load balancing and planned maintenance while maintaining
17 | high availability.
18 |
19 | Data Collection
20 | ---------------
21 |
22 | Collecting data like RRD metrics, worker task status, logs, and other operational information is a
23 | primary function of Proxmox Datacenter Manager. The system aggregates metrics to provide insight
24 | into usage, performance, and infrastructure growth.
25 |
26 | This allows for introspection into the server fleet, providing a central overview but also allowing
27 | you to explore specific remotes or resources. Dashboards and RRD graphs visualize this data to
28 | assist in detecting trends, optimizing resource allocation, and planning future capacity.
29 |
30 | Proxmox VE Remote
31 | -----------------
32 |
33 | Proxmox VE remotes integrate virtualization clusters and independent nodes into the central
34 | management view. Once added, the interface displays the hierarchy of hosts, virtual machines,
35 | containers, and storage resources, searchable via the central interface.
36 |
37 | Specific management capabilities available for Proxmox VE remotes include:
38 |
39 | * **Update Management**: A centralized panel provides an overview of available updates across the
40 | infrastructure and allows for the rollout of patches directly from the Datacenter Manager
41 | interface.
42 | * **SDN Capabilities**: Administrators can configure EVPN zones and VNets across multiple remotes to
43 | manage network overlays and administrative tasks.
44 |
45 | Proxmox Backup Server Remote
46 | ----------------------------
47 |
48 | Proxmox Backup Server instances can be managed as remotes to oversee backup infrastructure alongside
49 | virtualization hosts. The interface provides a consolidated overview of different datastores,
50 | displaying content and storage utilization.
51 |
52 | Metrics from Proxmox Backup Server remotes are integrated directly into the central dashboard
53 | widgets, including RRD graphs for performance and usage monitoring.
54 |
--------------------------------------------------------------------------------
/docs/api-viewer/Makefile:
--------------------------------------------------------------------------------
1 | DOCDIR = /usr/share/doc/proxmox-datacenter-manager
2 |
3 | API_VIEWER_SOURCES= \
4 | index.html \
5 | apidoc.css \
6 | apidoc-dark.css \
7 | apidoc.js \
8 |
9 | API_VIEWER_JS_FILES := \
10 | apidata.js \
11 | /usr/share/javascript/extjs/ext-all.js \
12 | /usr/share/javascript/proxmox-widget-toolkit-dev/APIViewer.js
13 |
14 | API_VIEWER_CSS_FILES := \
15 | /usr/share/javascript/extjs/theme-crisp/resources/theme-crisp-all_1.css \
16 | /usr/share/javascript/extjs/theme-crisp/resources/theme-crisp-all_2.css \
17 | /usr/share/javascript/proxmox-widget-toolkit/css/ext6-pmx.css \
18 |
19 | API_VIEWER_DARK_CSS_FILES := /usr/share/javascript/proxmox-widget-toolkit/themes/theme-proxmox-dark.css
20 |
21 | IMAGES := \
22 | dd/drop-no.png \
23 | grid/col-move-bottom.png \
24 | grid/col-move-top.png \
25 | grid/columns.png \
26 | grid/group-collapse.png \
27 | grid/group-expand.png \
28 | grid/hd-pop.png \
29 | grid/hmenu-asc.png \
30 | grid/hmenu-desc.png \
31 | grid/sort_asc.png \
32 | grid/sort_desc.png \
33 | menu/default-checked.png \
34 | menu/default-menu-parent.png \
35 | menu/default-unchecked.png \
36 | tools/tool-sprites.png \
37 | tree/elbow-end-minus.png \
38 | tree/elbow-end-plus.png \
39 | tree/elbow-end.png \
40 | tree/elbow-line.png \
41 | tree/elbow-minus.png \
42 | tree/elbow-plus.png \
43 | tree/elbow.png \
44 | tree/folder-open.png \
45 | tree/folder.png \
46 | tree/leaf.png \
47 |
48 | IMAGES_DIR := grid tools tree dd menu col
49 |
50 | API_VIEWER_IMAGES_FILES := $(addprefix /usr/share/javascript/extjs/theme-crisp/resources/images/, $(IMAGES))
51 |
52 | ifeq ($(BUILD_MODE), release)
53 | COMPILEDIR := ../../target/release
54 | else
55 | COMPILEDIR := ../../target/debug
56 | endif
57 |
58 | all:
59 |
60 | .PHONY: install
61 | install: $(API_VIEWER_SOURCES)
62 | install -dm 0755 $(DESTDIR)$(DOCDIR)/html/api-viewer
63 | install -m 0644 $(API_VIEWER_SOURCES) $(DESTDIR)$(DOCDIR)/html/api-viewer
64 | install -dm 0755 $(addprefix $(DESTDIR)$(DOCDIR)/html/api-viewer/images/, $(IMAGES_DIR))
65 | install -m 0644 $(API_VIEWER_IMAGES_FILES) $(DESTDIR)$(DOCDIR)/html/api-viewer/images/
66 | cd $(DESTDIR)$(DOCDIR)/html/api-viewer/images; for f in $(IMAGES); do \
67 | mv $$(basename $$f) $$f; \
68 | done
69 |
70 | apidata.js: $(COMPILEDIR)/docgen
71 | $(COMPILEDIR)/docgen apidata.js >$@
72 |
73 | apidoc.js: $(API_VIEWER_JS_FILES)
74 | cat $(API_VIEWER_JS_FILES) >$@.tmp
75 | mv $@.tmp $@
76 |
77 | apidoc.css: $(API_VIEWER_CSS_FILES)
78 | cat $(API_VIEWER_CSS_FILES) >$@.tmp
79 | mv $@.tmp $@
80 |
81 | apidoc-dark.css: $(API_VIEWER_DARK_CSS_FILES)
82 | cat $(API_VIEWER_DARK_CSS_FILES) >$@.tmp
83 | mv $@.tmp $@
84 |
85 | .PHONY: clean
86 | clean:
87 | rm -f apidata.js apidoc.js apidoc.css apidoc-dark.css *.tmp
88 |
--------------------------------------------------------------------------------
/ui/src/pve/remote/mod.rs:
--------------------------------------------------------------------------------
1 | use std::rc::Rc;
2 |
3 | use proxmox_yew_comp::NotesView;
4 | use yew::virtual_dom::{VComp, VNode};
5 |
6 | use pwt::css::{AlignItems, ColorScheme};
7 | use pwt::prelude::*;
8 | use pwt::props::{ContainerBuilder, WidgetBuilder};
9 | use pwt::widget::{Fa, Row, TabBarItem, TabPanel};
10 |
11 | use crate::remotes::RemoteTaskList;
12 |
13 | #[derive(Clone, Debug, Eq, PartialEq, Properties)]
14 | pub struct PveRemotePanel {
15 | /// The remote to show
16 | pub remote: String,
17 | }
18 |
19 | impl PveRemotePanel {
20 | pub fn new(remote: String) -> Self {
21 | yew::props!(Self { remote })
22 | }
23 | }
24 |
25 | impl From for VNode {
26 | fn from(val: PveRemotePanel) -> Self {
27 | VComp::new::(Rc::new(val), None).into()
28 | }
29 | }
30 |
31 | struct PveRemotePanelComp;
32 |
33 | impl yew::Component for PveRemotePanelComp {
34 | type Message = ();
35 | type Properties = PveRemotePanel;
36 |
37 | fn create(_ctx: &yew::Context) -> Self {
38 | Self
39 | }
40 |
41 | fn view(&self, ctx: &yew::Context) -> yew::Html {
42 | let props = ctx.props();
43 |
44 | let title: Html = Row::new()
45 | .gap(2)
46 | .class(AlignItems::Baseline)
47 | .with_child(Fa::new("building"))
48 | .with_child(tr! {"Remote '{0}'", props.remote})
49 | .into();
50 |
51 | TabPanel::new()
52 | .router(true)
53 | .class(pwt::css::FlexFit)
54 | .title(title)
55 | .class(ColorScheme::Neutral)
56 | .with_item_builder(
57 | TabBarItem::new()
58 | .key("tasks_view")
59 | .label(tr!("Remote Tasks"))
60 | .icon_class("fa fa-list"),
61 | {
62 | let remote = props.remote.clone();
63 | move |_| RemoteTaskList::new().remote(remote.clone()).into()
64 | },
65 | )
66 | .with_item_builder(
67 | TabBarItem::new()
68 | .key("notes_view")
69 | .label(tr!("Notes"))
70 | .icon_class("fa fa-sticky-note-o"),
71 | {
72 | let remote = props.remote.clone();
73 | move |_| {
74 | NotesView::edit_property(
75 | format!("/pve/remotes/{remote}/options"),
76 | "description",
77 | )
78 | .on_submit(None)
79 | .into()
80 | }
81 | },
82 | )
83 | .into()
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | include ../defines.mk
2 |
3 | GENERATED_SYNOPSIS := \
4 | proxmox-datacenter-manager-admin/synopsis.rst \
5 | proxmox-datacenter-manager-client/synopsis.rst \
6 | config/remotes/config.rst \
7 | config/views/config.rst \
8 |
9 | MAN1_PAGES := \
10 | proxmox-datacenter-manager-admin.1 \
11 | proxmox-datacenter-manager-client.1 \
12 | proxmox-datacenter-api.1 \
13 | proxmox-datacenter-privileged-api.1 \
14 |
15 | MAN5_PAGES := \
16 | remotes.cfg.5 \
17 | views.cfg.5 \
18 |
19 | # Sphinx documentation setup
20 | SPHINXOPTS =
21 | SPHINXBUILD = sphinx-build
22 | BUILDDIR = output
23 |
24 | ifeq ($(BUILD_MODE), release)
25 | COMPILEDIR := ../target/release
26 | SPHINXOPTS += -t release
27 | else
28 | COMPILEDIR := ../target/debug
29 | SPHINXOPTS += -t devbuild
30 | endif
31 |
32 | # Sphinx internal variables.
33 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(SPHINXOPTS) .
34 |
35 | all: $(MAN1_PAGES) $(MAN5_PAGES)
36 |
37 | config/%/config.rst: $(COMPILEDIR)/docgen
38 | $(COMPILEDIR)/docgen $*.cfg >$@
39 |
40 | %/synopsis.rst: $(COMPILEDIR)/%
41 | $< printdoc > $@
42 |
43 | $(MAN1_PAGES) $(MAN5_PAGES): man-pages
44 |
45 | .PHONY: man-pages
46 | man-pages: $(GENERATED_SYNOPSIS)
47 | $(SPHINXBUILD) $(SPHINXOPTS) -b man ./ $(BUILDDIR)/man
48 |
49 | .PHONY: html
50 | html: $(GENERATED_SYNOPSIS) images/proxmox-logo.svg _static/custom.css _static/custom.css conf.py
51 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
52 | install -m 0644 _static/custom.js _static/custom.css images/proxmox-logo.svg $(BUILDDIR)/html/_static/
53 | install -dm 0755 $(BUILDDIR)/html/api-viewer
54 | $(MAKE) -C api-viewer DESTDIR=../ DOCDIR=$(BUILDDIR) install
55 | @echo
56 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
57 |
58 | .PHONY: latexpdf
59 | latexpdf: $(GENERATED_SYNOPSIS)
60 | @echo "Requires python3-sphinx, texlive-xetex, xindy and texlive-fonts-extra"
61 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
62 | @echo "Running LaTeX files through xelatex..."
63 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
64 | @echo "xelatex finished; the PDF files are in $(BUILDDIR)/latex."
65 |
66 | clean:
67 | $(MAKE) -C api-viewer clean
68 | rm -r -f *~ *.1 $(BUILDDIR) $(GENERATED_SYNOPSIS)
69 |
70 | install_manual_pages: man-pages
71 | install -dm755 $(DESTDIR)$(MAN1DIR)
72 | for i in $(MAN1_PAGES); do install -m755 $(BUILDDIR)/man/$$i $(DESTDIR)$(MAN1DIR)/ ; done
73 | install -dm755 $(DESTDIR)$(MAN5DIR)
74 | for i in $(MAN5_PAGES); do install -m755 $(BUILDDIR)/man/$$i $(DESTDIR)$(MAN5DIR)/ ; done
75 |
76 | install_html: html
77 | install -dm755 $(DESTDIR)$(DOCDIR)
78 | rsync -a $(BUILDDIR)/html $(DESTDIR)$(DOCDIR)
79 |
80 | install_pdf: latexpdf
81 | install -dm755 $(DESTDIR)$(DOCDIR)
82 | install -m 0644 output/latex/ProxmoxDatacenterManager.pdf $(DESTDIR)$(DOCDIR)/proxmox-datacenter-manager.pdf
83 |
84 | ifneq ($(filter nodoc,$(DEB_BUILD_PROFILES)),)
85 |
86 | install: install_manual_pages
87 |
88 | else
89 |
90 | install: install_manual_pages install_html install_pdf
91 |
92 | endif
93 |
--------------------------------------------------------------------------------
/lib/pdm-config/src/setup.rs:
--------------------------------------------------------------------------------
1 | //! Setup methods.
2 |
3 | //use std::os::fd::OwnedFd;
4 | use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};
5 |
6 | use anyhow::{bail, format_err, Context as _, Error};
7 | use nix::fcntl::OFlag;
8 | use nix::sys::stat::Mode;
9 | use nix::unistd::{Gid, Uid};
10 |
11 | use pdm_buildcfg::configdir;
12 | use proxmox_sys::fs::CreateOptions;
13 |
14 | pub fn create_configdir() -> Result<(), Error> {
15 | let api_user = crate::api_user()?;
16 |
17 | let cfgdir = pdm_buildcfg::CONFIGDIR;
18 | mkdir_perms(cfgdir, api_user.uid, api_user.gid, 0o1770)?;
19 | mkdir_perms(configdir!("/auth"), nix::unistd::ROOT, api_user.gid, 0o750)?;
20 | mkdir_perms(
21 | configdir!("/access"),
22 | nix::unistd::ROOT,
23 | api_user.gid,
24 | 0o750,
25 | )?;
26 |
27 | Ok(())
28 | }
29 |
30 | pub fn mkdir_perms(dir: &str, uid: Uid, gid: Gid, mode: u32) -> Result<(), Error> {
31 | let nix_mode = Mode::from_bits(mode).expect("bad mode bits for nix crate");
32 | match nix::unistd::mkdir(dir, nix_mode) {
33 | Ok(()) => (),
34 | Err(nix::errno::Errno::EEXIST) => {
35 | CreateOptions::new()
36 | .owner(uid)
37 | .group(gid)
38 | .perm(nix_mode)
39 | .check(dir)?;
40 |
41 | check_permissions(dir, uid, gid, mode)
42 | .map_err(|err| format_err!("unexpected permissions directory '{dir}': {err}"))?;
43 | return Ok(());
44 | }
45 | Err(err) => bail!("unable to create directory '{dir}' - {err}",),
46 | }
47 |
48 | let fd = nix::fcntl::open(dir, OFlag::O_DIRECTORY, Mode::empty())
49 | .map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })
50 | .map_err(|err| format_err!("unable to open created directory '{dir}' - {err}"))?;
51 | // umask defaults to 022 so make sure the mode is fully honowed:
52 | nix::sys::stat::fchmod(fd.as_raw_fd(), nix_mode)
53 | .map_err(|err| format_err!("unable to set mode for directory '{dir}' - {err}"))?;
54 | nix::unistd::fchown(fd.as_raw_fd(), Some(uid), Some(gid))
55 | .map_err(|err| format_err!("unable to set ownership directory '{dir}' - {err}"))?;
56 |
57 | Ok(())
58 | }
59 |
60 | fn check_permissions(dir: &str, uid: Uid, gid: Gid, mode: u32) -> Result<(), Error> {
61 | let uid = uid.as_raw();
62 | let gid = gid.as_raw();
63 |
64 | let nix::sys::stat::FileStat {
65 | st_uid,
66 | st_gid,
67 | st_mode,
68 | ..
69 | } = nix::sys::stat::stat(dir).with_context(|| format!("failed to stat {dir:?}"))?;
70 |
71 | if st_uid != uid {
72 | log::error!("bad owner on {dir:?} ({st_uid} != {uid})");
73 | }
74 | if st_gid != gid {
75 | log::error!("bad group on {dir:?} ({st_gid} != {gid})");
76 | }
77 | let perms = st_mode & !nix::sys::stat::SFlag::S_IFMT.bits();
78 | if perms != mode {
79 | log::error!("bad permissions on {dir:?} (0o{perms:o} != 0o{mode:o})");
80 | }
81 |
82 | Ok(())
83 | }
84 |
--------------------------------------------------------------------------------
/ui/images/proxmox_logo_white.svg:
--------------------------------------------------------------------------------
1 |
2 |
21 |
--------------------------------------------------------------------------------
/ui/images/proxmox_logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
26 |
--------------------------------------------------------------------------------
/docs/dev/pdm-permission-system.md:
--------------------------------------------------------------------------------
1 | # ACL-Object path & Privileges
2 |
3 |
4 | ## `/system/{network,updates,disks,...}`
5 |
6 | For basic PDM system management.
7 |
8 | Privileges:
9 |
10 | - System.Audit
11 | - System.Modify
12 |
13 | ## `/resource/{remote-id}/{resource-type=guest,storage}/{resource-id}`
14 |
15 | To see, manage or modify specific resources. Keep resource-types rather minimal for now, e.g., no
16 | SDN or the node (host) for now, require the rest.
17 |
18 | - Resource.Audit -> read-only
19 | - Resource.Manage -> Migrate, Start, Stop, ...
20 | - Resource.Modify -> Change config or state of resource
21 | - Resource.Migrate -> Remote Migration
22 | - Resource.Delete -> Delete guests
23 |
24 | In the future we might extend this to something like:
25 |
26 | - Resource.Guest.Modify -> limited to guest related API calls and parameters on privilege level
27 | - Resource.Storage.Modify -> limited to storage related API calls and parameters on privilege level
28 | - Resource.User.Modify (once we integrated user and access control management of remotes, something
29 | for the mid/long-term future)
30 |
31 | The no-subtype ones, e.g. Resource.Modify, are seen as super-set of the per-resource type one.
32 | Should only be really evaluated after public feedback about the beta.
33 |
34 | ## `/access/{user,realm,acl}`
35 |
36 | To see or modify specific resources. Keep resource-types rather minimal for now, e.g., no
37 | SDN or the node (host) for now, require the rest.
38 |
39 | - Access.Audit -> read-only
40 | - Access.Modify -> Change config or state of resource
41 |
42 | We could also create sub-types to provide more flexibility, like:
43 | - Access.ACL.Modify
44 | - Access.User.Modify
45 |
46 | The biggest value from having a separate ACL and User modification privilege would be the ability to
47 | ensure on role-level that a user cannot give themselves more permissions.
48 |
49 | While that would speak for having this from the beginning, it's not a must from a technical POV, it
50 | could be still added later on, as it's an extension.
51 |
52 | # Roles
53 |
54 | - Administrator -> all, ideally only to allow permission modifications by default.
55 | - Auditor
56 | - SystemAdministrator
57 | - SystemAuditor
58 | - ResourceAdministrator
59 | - ResourceAuditor
60 | - AccessAuditor
61 | - ... can be extended in the future.
62 |
63 | ## Use cases to Support (TODO: is this still relevant?)
64 |
65 | - Simplest, one or more admin working on equal terms and using PDM to manage resources owned by the
66 | same entity (e.g., company)
67 | They want a simple way to add one API-token per Proxmox product to PDM
68 | - More complex admin hierarchy, or (support) staff involved, where some need to manage parts of
69 | their Proxmox infra, and some need to only audit part of the Proxmox infra, possibly on partially
70 | overlapping hosts sets.
71 | Flexible groups are required, some way to distinguish between admin/audit user while not blowing
72 | up complexity of different credentials to add for each Proxmox project
73 |
74 | IOW., we want to have a somewhat flexible system while not blowing out (potential) complexity out of
75 | proportions.
76 |
--------------------------------------------------------------------------------
/ui/src/widget/remote_endpoint_selector.rs:
--------------------------------------------------------------------------------
1 | use std::rc::Rc;
2 |
3 | use wasm_bindgen::UnwrapThrowExt;
4 | use yew::{
5 | html::{IntoEventCallback, IntoPropValue},
6 | AttrValue, Callback, Component, Properties,
7 | };
8 |
9 | use pwt::{
10 | props::{FieldBuilder, WidgetBuilder},
11 | widget::form::Combobox,
12 | };
13 | use pwt_macros::{builder, widget};
14 |
15 | use crate::RemoteList;
16 |
17 | #[widget(comp=PdmEndpointSelector, @input)]
18 | #[derive(Clone, Properties, PartialEq)]
19 | #[builder]
20 | pub struct EndpointSelector {
21 | /// The default value
22 | #[builder(IntoPropValue, into_prop_value)]
23 | #[prop_or_default]
24 | pub default: Option,
25 |
26 | /// Change callback
27 | #[builder_cb(IntoEventCallback, into_event_callback, String)]
28 | #[prop_or_default]
29 | pub on_change: Option>,
30 |
31 | /// The remote to list Endpoints from
32 | #[builder(IntoPropValue, into_prop_value)]
33 | #[prop_or_default]
34 | pub remote: AttrValue,
35 | }
36 |
37 | impl EndpointSelector {
38 | pub fn new(remote: AttrValue) -> Self {
39 | yew::props!(Self { remote })
40 | }
41 | }
42 |
43 | pub struct PdmEndpointSelector {
44 | endpoints: Rc>,
45 | }
46 |
47 | impl PdmEndpointSelector {
48 | fn update_endpoint_list(&mut self, ctx: &yew::Context) {
49 | let (remotes, _): (RemoteList, _) = ctx
50 | .link()
51 | .context(ctx.link().callback(|_| ()))
52 | .unwrap_throw();
53 |
54 | let remote_id = ctx.props().remote.as_str();
55 |
56 | for remote in remotes.iter() {
57 | if remote.id != remote_id {
58 | continue;
59 | }
60 |
61 | let endpoints = remote
62 | .nodes
63 | .iter()
64 | .map(|endpoint| AttrValue::from(endpoint.hostname.clone()))
65 | .collect();
66 | self.endpoints = Rc::new(endpoints);
67 | break;
68 | }
69 | }
70 | }
71 |
72 | impl Component for PdmEndpointSelector {
73 | type Message = ();
74 | type Properties = EndpointSelector;
75 |
76 | fn create(ctx: &yew::Context) -> Self {
77 | let mut this = Self {
78 | endpoints: Rc::new(Vec::new()),
79 | };
80 |
81 | this.update_endpoint_list(ctx);
82 | this
83 | }
84 |
85 | fn changed(&mut self, ctx: &yew::Context, old_props: &Self::Properties) -> bool {
86 | if ctx.props().remote != old_props.remote {
87 | self.update_endpoint_list(ctx);
88 | }
89 | true
90 | }
91 |
92 | fn view(&self, ctx: &yew::Context) -> yew::Html {
93 | let props = ctx.props();
94 | Combobox::new()
95 | .with_std_props(&props.std_props)
96 | .with_input_props(&props.input_props)
97 | .on_change(props.on_change.clone())
98 | .default(props.default.clone())
99 | .items(self.endpoints.clone())
100 | .into()
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/ui/debian/control:
--------------------------------------------------------------------------------
1 | Source: proxmox-datacenter-manager-ui
2 | Section: admin
3 | Priority: optional
4 | Build-Depends: debhelper-compat (= 13),
5 | dh-cargo (>= 25),
6 | cargo:native,
7 | esbuild,
8 | fonts-font-awesome,
9 | librust-anyhow-1+default-dev,
10 | librust-futures-0.3+default-dev,
11 | librust-gloo-net-0.4+default-dev,
12 | librust-gloo-timers-0.3+default-dev,
13 | librust-gloo-utils-0.2+default-dev,
14 | librust-http-1+default-dev (>=1.2~~),
15 | librust-js-sys-0.3+default-dev (>= 0.3.69-~~),
16 | librust-log-0.4+default-dev (>= 0.4.6-~~),
17 | librust-pbs-api-types-1+default-dev (>= 1.0.3~~),
18 | librust-percent-encoding-2+default-dev (>= 2.1-~~),
19 | librust-proxmox-acme-api-1+default-dev,
20 | librust-proxmox-client-1+default-dev,
21 | librust-proxmox-deb-version-0.1-dev,
22 | librust-proxmox-human-byte-1+default-dev (>= 0.1.3-~~),
23 | librust-proxmox-login-1+default-dev,
24 | librust-proxmox-rrd-api-types-1+default-dev,
25 | librust-proxmox-schema-5+default-dev,
26 | librust-proxmox-yew-comp-0.8+apt-dev (>= 0.8.1-~~),
27 | librust-proxmox-yew-comp-0.8+default-dev (>= 0.8.1-~~),
28 | librust-proxmox-yew-comp-0.8+dns-dev (>= 0.8.1-~~),
29 | librust-proxmox-yew-comp-0.8+network-dev (>= 0.8.1-~~),
30 | librust-proxmox-yew-comp-0.8+rrd-dev (>= 0.8.1-~~),
31 | librust-pwt-0.7+default-dev,
32 | librust-pwt-macros-0.5+default-dev,
33 | librust-serde-1+default-dev,
34 | librust-serde-1+derive-dev,
35 | librust-serde-json-1+default-dev,
36 | librust-wasm-bindgen-0.2+default-dev (>= 0.2.92-~~),
37 | librust-wasm-bindgen-futures-0.4+default-dev,
38 | librust-wasm-logger-0.2+default-dev,
39 | librust-web-sys-0.3+default-dev,
40 | librust-web-sys-0.3+location-dev,
41 | librust-yew-0.21+csr-dev,
42 | librust-yew-0.21+default-dev,
43 | librust-yew-router-0.18+default-dev,
44 | proxmox-wasm-builder,
45 | rust-grass,
46 | rust-llvm,
47 | Maintainer: Proxmox Support Team
48 | Standards-Version: 4.6.1
49 | Vcs-Git: git://git.proxmox.com/git/proxmox-datacenter-manager.git
50 | Vcs-Browser: https://git.proxmox.com/?p=proxmox-datacenter-manager.git;a=summary
51 | Homepage: https://www.proxmox.com
52 | Rules-Requires-Root: no
53 |
54 | Package: proxmox-datacenter-manager-ui
55 | Architecture: any
56 | Multi-Arch: allowed
57 | Depends: fonts-font-awesome,
58 | pdm-i18n,
59 | pve-xtermjs,
60 | ${misc:Depends},
61 | ${shlibs:Depends},
62 | Recommends: proxmox-datacenter-manager,
63 | Description: Web UI to for the Proxmox Datacenter Manager
64 | This package provides the web UI of the Proxmox Datacenter Manager (PDM)
65 | which allows one to add multiple Proxmox VE and Proxmox Backup Server
66 | remotes and to manage these remotes from a central UI.
67 |
--------------------------------------------------------------------------------
/server/src/api/mod.rs:
--------------------------------------------------------------------------------
1 | //! Common API endpoints
2 |
3 | use anyhow::{bail, Error};
4 | use pdm_api_types::{remotes::RemoteType, RemoteUpid};
5 | use serde_json::{json, Value};
6 |
7 | use proxmox_router::{list_subdirs_api_method, Permission, Router, SubdirMap};
8 | use proxmox_schema::api;
9 | use proxmox_sortable_macro::sortable;
10 |
11 | pub mod access;
12 | pub mod config;
13 | pub mod metric_collection;
14 | pub mod nodes;
15 | pub mod pbs;
16 | pub mod pve;
17 | pub mod remote_shell;
18 | pub mod remote_tasks;
19 | pub mod remote_updates;
20 | pub mod remotes;
21 | pub mod resources;
22 | mod rrd_common;
23 | pub mod sdn;
24 |
25 | #[sortable]
26 | const SUBDIRS: SubdirMap = &sorted!([
27 | ("access", &access::ROUTER),
28 | ("config", &config::ROUTER),
29 | ("ping", &Router::new().get(&API_METHOD_PING)),
30 | ("pve", &pve::ROUTER),
31 | ("pbs", &pbs::ROUTER),
32 | ("remotes", &remotes::ROUTER),
33 | ("resources", &resources::ROUTER),
34 | ("nodes", &nodes::ROUTER),
35 | ("sdn", &sdn::ROUTER),
36 | ("version", &Router::new().get(&API_METHOD_VERSION)),
37 | ]);
38 |
39 | pub const ROUTER: Router = Router::new()
40 | .get(&list_subdirs_api_method!(SUBDIRS))
41 | .subdirs(SUBDIRS);
42 |
43 | #[api(
44 | access: {
45 | description: "Anyone can access this, just a cheap check if the API daemon is online.",
46 | permission: &Permission::World,
47 | },
48 | returns: {
49 | type: String,
50 | description: "The string \"pong\"."
51 | }
52 | )]
53 | /// A simple ping method. returns "pong"
54 | fn ping() -> Result {
55 | Ok("pong".to_string())
56 | }
57 |
58 | #[api(
59 | access: {
60 | description: "Any valid user can access this.",
61 | permission: &Permission::Anybody,
62 | },
63 | returns: {
64 | type: Object,
65 | description: "Version information.",
66 | properties: {
67 | version: {
68 | type: String,
69 | description: "The version string."
70 | },
71 | release: {
72 | type: String,
73 | description: "The package release.",
74 | },
75 | repoid: {
76 | type: String,
77 | description: "The repoid."
78 | }
79 | }
80 | }
81 | )]
82 | /// Return the program's version/release info
83 | fn version() -> Result {
84 | Ok(json!({
85 | "version": pdm_buildcfg::PROXMOX_PKG_VERSION,
86 | "release": pdm_buildcfg::PROXMOX_PKG_RELEASE,
87 | "repoid": pdm_buildcfg::PROXMOX_PKG_REPOID
88 | }))
89 | }
90 |
91 | /// Check a [`RemoteUpid`] matches the expected remote name and type.
92 | pub(crate) fn verify_upid(
93 | remote: &str,
94 | remote_type: RemoteType,
95 | upid: &RemoteUpid,
96 | ) -> Result<(), Error> {
97 | if upid.remote() != remote {
98 | bail!(
99 | "remote '{remote}' does not match remote in upid ('{}')",
100 | upid.remote()
101 | );
102 | }
103 | if upid.remote_type() != remote_type {
104 | bail!("upid does not belong to a {remote_type} remote");
105 | }
106 |
107 | Ok(())
108 | }
109 |
--------------------------------------------------------------------------------
/server/src/api/rrd_common.rs:
--------------------------------------------------------------------------------
1 | use std::{collections::BTreeMap, time::Duration};
2 |
3 | use anyhow::{bail, Error};
4 |
5 | use proxmox_rrd_api_types::{RrdMode, RrdTimeframe};
6 |
7 | use crate::metric_collection::{self, rrd_cache};
8 |
9 | /// Trait common to all RRD-stored metric objects (nodes, datastores, qemu, lxc, etc.)
10 | pub trait DataPoint {
11 | /// Create a new data point with a given timestamp
12 | fn new(time: u64) -> Self;
13 | /// Returns the names of the underlying (stringly typed) fields in the RRD
14 | fn fields() -> &'static [&'static str];
15 | /// Set a member by its field identifier
16 | fn set_field(&mut self, name: &str, value: f64);
17 | }
18 |
19 | pub fn create_datapoints_from_rrd(
20 | basedir: &str,
21 | timeframe: RrdTimeframe,
22 | mode: RrdMode,
23 | ) -> Result, Error> {
24 | let mut timemap = BTreeMap::new();
25 | let mut last_resolution = None;
26 |
27 | let cache = rrd_cache::get_cache();
28 |
29 | for name in T::fields() {
30 | let (start, resolution, data) = match cache.extract_data(basedir, name, timeframe, mode)? {
31 | Some(data) => data.into(),
32 | None => continue,
33 | };
34 |
35 | if let Some(expected_resolution) = last_resolution {
36 | if resolution != expected_resolution {
37 | bail!("got unexpected RRD resolution ({resolution} != {expected_resolution})",);
38 | }
39 | } else {
40 | last_resolution = Some(resolution);
41 | }
42 |
43 | let mut t = start;
44 |
45 | for value in data {
46 | let entry = timemap.entry(t).or_insert_with(|| T::new(t));
47 | if let Some(value) = value {
48 | entry.set_field(name, value);
49 | }
50 |
51 | t += resolution;
52 | }
53 | }
54 |
55 | Ok(timemap.into_values().collect())
56 | }
57 |
58 | /// Get RRD datapoints for a given remote/RRD path.
59 | ///
60 | /// If `timeframe` is set to [`RrdTimeframe::Hour`], then this function will trigger
61 | /// metric collection for this remote and wait for its completion, up to a timeout of five
62 | /// seconds. If the timeout is exceeded, we simply go ahead and return what is in the database at
63 | /// the moment, which might have a gap for the last couple minutes.
64 | pub async fn get_rrd_datapoints(
65 | remote: String,
66 | basepath: String,
67 | timeframe: RrdTimeframe,
68 | mode: RrdMode,
69 | ) -> Result, Error> {
70 | const WAIT_FOR_NEWEST_METRIC_TIMEOUT: Duration = Duration::from_secs(5);
71 |
72 | if timeframe == RrdTimeframe::Hour {
73 | // Let's wait for a limited time for the most recent metrics. If the connection to the remote
74 | // is super slow or if the metric collection tasks currently busy with collecting other
75 | // metrics, we just return the data we already have, not the newest one.
76 | let _ = tokio::time::timeout(WAIT_FOR_NEWEST_METRIC_TIMEOUT, async {
77 | metric_collection::trigger_metric_collection(Some(remote), true).await
78 | })
79 | .await;
80 | }
81 |
82 | tokio::task::spawn_blocking(move || create_datapoints_from_rrd(&basepath, timeframe, mode))
83 | .await?
84 | }
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Proxmox Datacenter Manager
2 |
3 | A stand-alone API + GUI product with the following main features for multiple instances of Proxmox
4 | VE and Proxmox Backup Server in one central place.
5 |
6 | ## Feature Overview
7 |
8 | - Connect & display an arbitrary amount of independent nodes or clusters ("Remotes")
9 | - View the status and load of all resources, which includes nodes, virtual guests, storages,
10 | datastores and so on. Proxmox Datacenter Manager provides a dashboard that tries to present
11 | information such that potential problematic outliers can be found easily.
12 | - Customizable dashboards ("views") showing a configurable subset of resources
13 | - Basic management of the guest resources
14 | - Resource graphs
15 | - Basic power management (start, reboot, shutdown)
16 | - Remote shell for Proxmox VE and Proxmox Backup Server remotes
17 | - Global overview over available system updates for managed remotes
18 | - Firewall overview for all managed remotes
19 | - Basic SDN overview for all managed remotes
20 | - Remote migration of virtual guests between different datacenters
21 | Advertising use of ZFS & Ceph backed replication for quicker transfer on actual migration
22 | - View configuration health state (subscription, APT repositories, pending updates, ...)
23 | - User management / access control
24 | - Users/API token
25 | - Support for LDAP and Active Directory
26 | - Support for OpenID Connect
27 | - Support for complex Two-Factor Authentication
28 | - ACME/Let's Encrypt
29 |
30 | - A non-exhaustive list of features planned for future releases is:
31 | - Management of more configuration (e.g. backup jobs, notification policies, package repos, HA)
32 | - Active-standby architecture for standby instances of PDM to avoid single point of failure.
33 | - Integration of other projects, like Proxmox Mail Gateway, and potentially also Proxmox Offline Mirror.
34 | - Off-site replication copies of guest for manual recovery on DC failure (not HA!)
35 | - ... to be determined from user feedback and feature requests.
36 |
37 | ## Technology Overview
38 |
39 | ### Backend
40 | - Implemented in the Rust programming language, reusing code from Proxmox Backup Server where possible
41 | - A for Proxmox projects standard dual-stack of API daemons. One as main API daemon running as
42 | unprivileged users and one privileged daemon running as root. Contrary to other projects the
43 | privileged daemon exclusively listens on a file based UNIX socket, thus restricting attack surface
44 | even further.
45 | - The backend listens on port 8443 (TLS only)
46 | - The code for the backend server is located in the `server/` directory.
47 |
48 | ### Frontend
49 |
50 | - The Web UI communicates with the backend server via a JSON-based REST API.
51 | - The UI is implemented in Rust, using [Yew](https://yew.rs/) and the
52 | [proxmox-yew-widget-toolkit](https://git.proxmox.com/?p=ui/proxmox-yew-widget-toolkit.git;a=summary).
53 | The Rust code is compiled to WebAssembly.
54 | - The code for the UI is located in the `ui/` directory.
55 |
56 | ### CLI tools
57 |
58 | There are two CLI tools to manage Proxmox Datacenter Manager.
59 | - `proxmox-datacenter-manager-client`: client using the PDM API, can be used to
60 | control local or remote PDM instances
61 | - `proxmox-datacenter-manager-admin`: root-only, local administration tool
62 |
63 | Their implementation can be found in `cli/admin` and `cli/client`, respectively.
64 |
65 |
66 | ## Documentation
67 |
68 | Documentation (user-facing as well as developer-facing) can be found in `docs/`.
69 |
--------------------------------------------------------------------------------
/docs/installation.rst:
--------------------------------------------------------------------------------
1 | .. _install_pdm:
2 |
3 | Installation
4 | ============
5 |
6 | `Proxmox Datacenter Manager`_ can either be installed with a graphical
7 | installer or on top of Debian_ from the provided package repository.
8 |
9 | .. include:: system-requirements.rst
10 |
11 | .. include:: installation-media.rst
12 |
13 | Using our provided disk image (ISO file) is the recommended installation
14 | method, as it includes a convenient installer, a complete Debian system as well
15 | as all necessary packages for the Proxmox Datacenter Manager.
16 |
17 | Once you have created an :ref:`installation_medium`, the booted :ref:`installer
18 | ` will guide you through the setup process. It will help
19 | you to partition your disks, apply basic settings such as the language, time
20 | zone and network configuration, and finally install all required packages
21 | within minutes.
22 |
23 | As an alternative to the interactive installer, advanced users may wish to
24 | install Proxmox Datacenter Manager :ref:`unattended `.
25 |
26 | With sufficient Debian knowledge, you can also install Proxmox Datacenter
27 | Manager :ref:`on top of Debian ` yourself.
28 |
29 | .. todo
30 | .. include:: using-the-installer.rst
31 |
32 | .. _install_pdm_unattended:
33 |
34 | Install Proxmox Datacenter Manager Unattended
35 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36 |
37 | It is possible to install Proxmox Datacenter Manager automatically in an
38 | unattended manner. This enables you to fully automate the setup process on
39 | bare-metal. Once the installation is complete and the host has booted up,
40 | automation tools like Ansible can be used to further configure the installation.
41 |
42 | The necessary options for the installer must be provided in an answer file.
43 | This file allows the use of filter rules to determine which disks and network
44 | cards should be used.
45 |
46 | To use the automated installation, it is first necessary to prepare an
47 | installation ISO. For more details and information on the unattended
48 | installation see `our wiki
49 | `_.
50 |
51 | .. _install_pdm_on_debian:
52 |
53 | Install Proxmox Datacenter Manager on Debian
54 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55 |
56 | Proxmox ships as a set of Debian packages which can be installed on top of a
57 | standard Debian installation. After configuring the
58 | :ref:`sysadmin_package_repositories`, you need to run:
59 |
60 | .. code-block:: console
61 |
62 | # apt update
63 | # apt install proxmox-datacenter-manager proxmox-datacenter-manager-ui
64 |
65 | The above commands keep the current (Debian) kernel and install a minimal set
66 | of required packages.
67 |
68 | You can install the Proxmox default kernel with ZFS support by using:
69 |
70 | .. code-block:: console
71 |
72 | # apt update
73 | # apt install proxmox-default-kernel
74 |
75 | ..
76 | add meta package
77 |
78 | .. caution:: Installing Proxmox Datacenter Manager on top of an existing Debian_
79 | installation looks easy, but it assumes that the base system and local
80 | storage have been set up correctly. In general this is not trivial, especially
81 | when LVM_ or ZFS_ is used. The network configuration is completely up to you
82 | as well.
83 |
84 | .. Note:: You can access the web interface of the Proxmox Datacenter Manager with
85 | your web browser, using HTTPS on port 8443. For example at
86 | ``https://:8443``
87 |
88 | .. include:: package-repositories.rst
89 |
--------------------------------------------------------------------------------
/docs/faq.rst:
--------------------------------------------------------------------------------
1 | FAQ
2 | ===
3 |
4 | What distribution is Proxmox Datacenter Manager (PDM) based on?
5 | ---------------------------------------------------------------
6 |
7 | Proxmox Datacenter Manager is based on `Debian GNU/Linux `_.
8 |
9 |
10 | Will Proxmox Datacenter Manager run on a 32-bit processor?
11 | ----------------------------------------------------------
12 |
13 | Proxmox Datacenter Manager only supports 64-bit CPUs (AMD or Intel). There are no future plans to
14 | support 32-bit processors.
15 |
16 |
17 | .. _faq-support-table:
18 |
19 | How long will my Proxmox Datacenter Manager version be supported?
20 | -----------------------------------------------------------------
21 |
22 | .. csv-table::
23 | :file: faq-release-support-table.csv
24 | :widths: 30 26 13 13 18
25 | :header-rows: 1
26 |
27 | How can I upgrade Proxmox Datacenter Manager to the next point release?
28 | -----------------------------------------------------------------------
29 |
30 | Minor version upgrades, for example upgrading from Proxmox Datacenter Manager in rersion 1.0 to 1.1
31 | or 1.3, can be done just like any normal update.
32 |
33 | But, you should still check the `release notes `_ for any
34 | relevant notable, or breaking change.
35 |
36 | For the update itself use either the Web UI *Administration -> Updates* panel or through the CLI
37 | with:
38 |
39 | .. code-block:: console
40 |
41 | apt update
42 | apt full-upgrade
43 |
44 | .. note:: Always ensure you correctly setup the :ref:`package repositories
45 | ` and only continue with the actual upgrade if `apt update` did
46 | not hit any error.
47 |
48 | ..
49 | .. _faq-upgrade-major:
50 |
51 | How can I upgrade Proxmox Datacenter Manager to the next major release?
52 | -----------------------------------------------------------------------
53 |
54 | Major version upgrades, for example going from Proxmox Datacenter Manager 1.3 to 2.1, are also
55 | supported.
56 | They must be carefully planned and tested and should **never** be started without having successfully
57 | tested backups.
58 |
59 | Although the specific upgrade steps depend on your respective setup, we provide general instructions
60 | and advice of how a upgrade should be performed:
61 |
62 | * `Upgrade from Proxmox Datacenter Manager 1 to 2 `_
63 |
64 | .. _faq-subscription:
65 |
66 | Is there a dedicated subscription for the Proxmox Datacenter Manager?
67 | ---------------------------------------------------------------------
68 |
69 | No, there is not. However, your existing Basic or higher subscription for Proxmox VE and Proxmox
70 | Backup Server remotes includes access to the Proxmox Datacenter Manager Enterprise Repository and
71 | support at no extra cost.
72 |
73 | .. _faq-enterprise-support:
74 |
75 | How can I get Enterprise Support for the Proxmox Datacenter Manager?
76 | --------------------------------------------------------------------
77 |
78 | Existing customers with active Basic or higher subscriptions for their Proxmox remotes also gain
79 | access to the Proxmox Datacenter Manager enterprise repository and support.
80 |
81 | .. _faq-enterprise-repository:
82 |
83 | How can I get access to the Proxmox Datacenter Manager Enterprise Repository?
84 | -----------------------------------------------------------------------------
85 |
86 | The Proxmox Datacenter Manager can use the enterprise repository if at least 80% of the configured
87 | remote nodes have a valid Basic or higher subscription.
88 |
--------------------------------------------------------------------------------
/server/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "server"
3 | description = "Proxmox Datacenter Manager Common API parts"
4 | homepage = "https://www.proxmox.com"
5 |
6 | version.workspace = true
7 | edition.workspace = true
8 | license.workspace = true
9 | repository.workspace = true
10 |
11 | [dependencies]
12 | anyhow.workspace = true
13 | async-stream.workspace = true
14 | async-trait.workspace = true
15 | const_format.workspace = true
16 | futures.workspace = true
17 | hex.workspace = true
18 | http.workspace = true
19 | http-body-util.workspace = true
20 | hyper.workspace = true
21 | hyper-util.workspace = true
22 | libc.workspace = true
23 | log.workspace = true
24 | nix.workspace = true
25 | once_cell.workspace = true
26 | openssl.workspace = true
27 | percent-encoding.workspace = true
28 | serde.workspace = true
29 | serde_json.workspace = true
30 | serde_plain.workspace = true
31 | syslog.workspace = true
32 | tokio = { workspace = true, features = [ "fs", "io-util", "io-std", "macros", "net", "parking_lot", "process", "rt", "rt-multi-thread", "signal", "time" ] }
33 | tokio-stream.workspace = true
34 | tracing.workspace = true
35 | url.workspace = true
36 | zstd.workspace = true
37 |
38 | proxmox-access-control = { workspace = true, features = [ "api" ] }
39 | proxmox-async.workspace = true
40 | proxmox-auth-api = { workspace = true, features = [ "api", "ticket", "pam-authenticator", "password-authenticator" ] }
41 | proxmox-base64.workspace = true
42 | proxmox-daemon.workspace = true
43 | proxmox-docgen.workspace = true
44 | proxmox-http = { workspace = true, features = [ "client-trait", "proxmox-async" ] } # pbs-client doesn't use these
45 | proxmox-lang.workspace = true
46 | proxmox-ldap.workspace = true
47 | proxmox-log.workspace = true
48 | proxmox-login.workspace = true
49 | proxmox-openid.workspace = true
50 | proxmox-rest-server = { workspace = true, features = [ "templates" ] }
51 | proxmox-router = { workspace = true, features = [ "cli", "server"] }
52 | proxmox-rrd.workspace = true
53 | proxmox-rrd-api-types.workspace = true
54 | proxmox-schema = { workspace = true, features = [ "api-macro" ] }
55 | proxmox-section-config.workspace = true
56 | proxmox-serde = { workspace = true, features = [ "serde_json" ] }
57 | proxmox-sortable-macro.workspace = true
58 | proxmox-subscription = { workspace = true, features = [ "api-types", "impl" ] }
59 | proxmox-sys = { workspace = true, features = [ "timer" ] }
60 | proxmox-systemd.workspace = true
61 | proxmox-tfa = { workspace = true, features = [ "api" ] }
62 | proxmox-time.workspace = true
63 | proxmox-uuid.workspace = true
64 |
65 | proxmox-apt = { workspace = true, features = [ "cache" ] }
66 | proxmox-apt-api-types.workspace = true
67 |
68 | proxmox-client = { workspace = true, features = [ "hyper-client"] }
69 |
70 | proxmox-config-digest = { workspace = true, features = [ "openssl" ] }
71 | proxmox-product-config.workspace = true
72 | proxmox-dns-api = { workspace = true, features = [ "impl" ] }
73 | proxmox-time-api = { workspace = true, features = [ "impl" ] }
74 | proxmox-network-api = { workspace = true, features = [ "impl" ] }
75 | proxmox-syslog-api = { workspace = true, features = [ "impl" ] }
76 | proxmox-acme-api = { workspace = true, features = [ "impl" ] }
77 | proxmox-node-status = { workspace = true, features = [ "api" ] }
78 |
79 | pdm-api-types.workspace = true
80 | pdm-buildcfg.workspace = true
81 | pdm-config.workspace = true
82 | pdm-search.workspace = true
83 |
84 | pve-api-types = { workspace = true, features = [ "client" ] }
85 | pbs-api-types.workspace = true
86 |
87 | [lints.rust.unexpected_cfgs]
88 | level = "warn"
89 | check-cfg = ['cfg(remote_config, values("faked"))']
90 |
--------------------------------------------------------------------------------
/ui/css/pdm.scss:
--------------------------------------------------------------------------------
1 | .pdm-type-icon {
2 | position: relative;
3 |
4 | .status-icon {
5 | text-shadow: 0px 0px 2px var(--pwt-color-background);
6 | position: absolute;
7 | right: -8px;
8 | bottom: 0px;
9 | font-size: 80%;
10 | }
11 | }
12 |
13 | .pve-guest-icon {
14 | position: relative;
15 |
16 | .status-icon {
17 | text-shadow: 0px 0px 2px var(--pwt-color-background);
18 | position: absolute;
19 | right: -6px;
20 | bottom: 1px;
21 | font-size: 80%;
22 | }
23 | }
24 |
25 | .pve-tags {
26 | text-overflow: ellipsis;
27 | .pve-tag {
28 | border-radius: var(--pwt-button-corner-shape);
29 | padding-inline: var(--pwt-spacer-2)
30 | }
31 | }
32 |
33 | .line-usage-graph {
34 | border-radius: var(--pwt-button-corner-shape);
35 | height: map-get($line-height, "label-large");
36 | }
37 |
38 | .fa-cpu::before {
39 | content: " ";
40 | background-image: url(./images/icon-cpu.svg);
41 | background-size: 16px 16px;
42 | background-repeat: no-repeat;
43 | width: 16px;
44 | height: 16px;
45 | vertical-align: bottom;
46 | display: inline-block;
47 | }
48 |
49 | .fa-memory::before {
50 | content: " ";
51 | background-image: url(./images/icon-memory.svg);
52 | background-size: 16px 16px;
53 | background-repeat: no-repeat;
54 | width: 16px;
55 | height: 16px;
56 | vertical-align: bottom;
57 | display: inline-block;
58 | }
59 |
60 | .fa-cdrom::before {
61 | content: " ";
62 | background-image: url(./images/icon-cd-drive.svg);
63 | background-size: 16px 16px;
64 | background-repeat: no-repeat;
65 | width: 16px;
66 | height: 16px;
67 | vertical-align: bottom;
68 | display: inline-block;
69 | }
70 |
71 | .fa-sdn-vnet::before {
72 | content: " ";
73 | mask-image: url(./images/icon-sdn-vnet.svg);
74 | mask-size: 16px 16px;
75 | mask-repeat: no-repeat;
76 | background-color: var(--pwt-color);
77 | width: 16px;
78 | height: 16px;
79 | vertical-align: bottom;
80 | display: inline-block;
81 | }
82 |
83 | .fa-sdn:before {
84 | content: " ";
85 | mask-image: url(../images/icon-sdn.svg);
86 | mask-size: 16px 16px;
87 | mask-repeat: no-repeat;
88 | background-color: var(--pwt-color);
89 | width: 16px;
90 | height: 16px;
91 | vertical-align: middle;
92 | display: inline-block;
93 | }
94 |
95 | .pwt-nav-menu .pwt-nav-link.active{
96 | .fa-sdn:before,
97 | .fa-sdn-vnet:before {
98 | background-color: var(--pwt-accent-color);
99 | }
100 | }
101 |
102 | .pwt-panel-header-text{
103 | .fa-sdn:before,
104 | .fa-sdn-vnet:before {
105 | background-color: var(--pwt-accent-color-background);
106 | }
107 | }
108 |
109 | :root.pwt-dark-mode {
110 | .fa-cdrom,
111 | .fa-memory,
112 | .fa-cpu {
113 | filter: invert(90%);
114 | }
115 | }
116 |
117 | .proxmox-content-spacer {
118 | @include color-scheme-vars("surface");
119 | color: var(--pwt-color);
120 | background-color: var(--pwt-color-background);
121 |
122 | padding: var(--pwt-spacer-2);
123 | gap: var(--pwt-spacer-2);
124 |
125 | display: flex;
126 | flex-direction: column;
127 |
128 | & > * {
129 | @include color-scheme-vars("neutral");
130 | border: 1px solid var(--pwt-color-border);
131 | color: var(--pwt-color);
132 | background-color: var(--pwt-color-background);
133 | }
134 | }
135 |
136 | .dragging-item {
137 | opacity: 0.5;
138 | }
139 |
--------------------------------------------------------------------------------
/cli/admin/src/main.rs:
--------------------------------------------------------------------------------
1 | use serde_json::{json, Value};
2 |
3 | use proxmox_router::cli::{
4 | default_table_format_options, format_and_print_result_full, get_output_format, run_cli_command,
5 | CliCommand, CliCommandMap, CliEnvironment, ColumnConfig, OUTPUT_FORMAT,
6 | };
7 | use proxmox_router::RpcEnvironment;
8 |
9 | use proxmox_schema::api;
10 |
11 | mod remotes;
12 | mod support_status;
13 |
14 | fn main() {
15 | //pbs_tools::setup_libc_malloc_opts(); // TODO: move from PBS to proxmox-sys and uncomment
16 |
17 | let api_user = pdm_config::api_user().expect("cannot get api user");
18 | let priv_user = pdm_config::priv_user().expect("cannot get privileged user");
19 | proxmox_product_config::init(api_user, priv_user);
20 |
21 | proxmox_access_control::init::init(
22 | &pdm_api_types::AccessControlConfig,
23 | pdm_buildcfg::configdir!("/access"),
24 | )
25 | .expect("failed to setup access control config");
26 |
27 | proxmox_log::Logger::from_env("PDM_LOG", proxmox_log::LevelFilter::INFO)
28 | .stderr()
29 | .init()
30 | .expect("failed to set up logger");
31 |
32 | server::context::init().expect("could not set up server context");
33 |
34 | let cmd_def = CliCommandMap::new()
35 | .insert("remote", remotes::cli())
36 | .insert(
37 | "report",
38 | CliCommand::new(&API_METHOD_GENERATE_SYSTEM_REPORT),
39 | )
40 | .insert("support-status", support_status::cli())
41 | .insert("versions", CliCommand::new(&API_METHOD_GET_VERSIONS));
42 |
43 | let mut rpcenv = CliEnvironment::new();
44 | rpcenv.set_auth_id(Some("root@pam".into()));
45 |
46 | run_cli_command(
47 | cmd_def,
48 | rpcenv,
49 | Some(|future| proxmox_async::runtime::main(future)),
50 | );
51 | }
52 |
53 | #[api(
54 | input: {
55 | properties: {
56 | verbose: {
57 | type: Boolean,
58 | optional: true,
59 | default: false,
60 | description: "Output verbose package information. It is ignored if output-format is specified.",
61 | },
62 | "output-format": {
63 | schema: OUTPUT_FORMAT,
64 | optional: true,
65 | }
66 | }
67 | }
68 | )]
69 | /// List package versions for important Proxmox Datacenter Manager packages.
70 | async fn get_versions(verbose: bool, param: Value) -> Result {
71 | let output_format = get_output_format(¶m);
72 |
73 | let packages = server::api::nodes::apt::get_versions()?;
74 | let mut packages = json!(if verbose {
75 | &packages[..]
76 | } else {
77 | &packages[1..2]
78 | });
79 |
80 | let options = default_table_format_options()
81 | .disable_sort()
82 | .noborder(true) // just not helpful for version info which gets copy pasted often
83 | .column(ColumnConfig::new("Package"))
84 | .column(ColumnConfig::new("Version"))
85 | .column(ColumnConfig::new("ExtraInfo").header("Extra Info"));
86 | let return_type = &server::api::nodes::apt::API_METHOD_GET_VERSIONS.returns;
87 |
88 | format_and_print_result_full(&mut packages, return_type, &output_format, &options);
89 |
90 | Ok(Value::Null)
91 | }
92 |
93 | #[api]
94 | /// Generate the system report.
95 | async fn generate_system_report() -> Result<(), anyhow::Error> {
96 | let report = server::api::nodes::report::generate_system_report()?;
97 | print!("{report}");
98 |
99 | Ok(())
100 | }
101 |
--------------------------------------------------------------------------------
/server/src/api/nodes/sdn.rs:
--------------------------------------------------------------------------------
1 | use anyhow::{anyhow, Error};
2 | use http::StatusCode;
3 |
4 | use pdm_api_types::{remotes::REMOTE_ID_SCHEMA, sdn::SDN_ID_SCHEMA, NODE_SCHEMA};
5 | use proxmox_router::{list_subdirs_api_method, Router, SubdirMap};
6 | use proxmox_schema::api;
7 | use pve_api_types::{SdnVnetMacVrf, SdnZoneIpVrf};
8 |
9 | use crate::api::pve::{connect, get_remote};
10 |
11 | mod zones {
12 | use super::*;
13 |
14 | const ZONE_SUBDIRS: SubdirMap = &[("ip-vrf", &Router::new().get(&API_METHOD_GET_IP_VRF))];
15 |
16 | const ZONE_ROUTER: Router = Router::new()
17 | .get(&list_subdirs_api_method!(ZONE_SUBDIRS))
18 | .subdirs(ZONE_SUBDIRS);
19 |
20 | pub const ROUTER: Router = Router::new().match_all("zone", &ZONE_ROUTER);
21 |
22 | #[api(
23 | input: {
24 | properties: {
25 | remote: { schema: REMOTE_ID_SCHEMA },
26 | node: { schema: NODE_SCHEMA },
27 | zone: { schema: SDN_ID_SCHEMA },
28 | },
29 | },
30 | returns: { type: SdnZoneIpVrf },
31 | )]
32 | /// Get the IP-VRF for an EVPN zone for a node on a given remote
33 | async fn get_ip_vrf(
34 | remote: String,
35 | node: String,
36 | zone: String,
37 | ) -> Result, Error> {
38 | let (remote_config, _) = pdm_config::remotes::config()?;
39 | let remote = get_remote(&remote_config, &remote)?;
40 | let client = connect(remote)?;
41 |
42 | client
43 | .get_zone_ip_vrf(&node, &zone)
44 | .await
45 | .map_err(|err| match err {
46 | proxmox_client::Error::Api(StatusCode::NOT_IMPLEMENTED, _msg) => {
47 | anyhow!("remote {} does not support the zone ip-vrf API call, please upgrade to the newest version!", remote.id)
48 | }
49 | _ => err.into()
50 | })
51 | }
52 | }
53 |
54 | mod vnets {
55 | use super::*;
56 |
57 | const VNET_SUBDIRS: SubdirMap = &[("mac-vrf", &Router::new().get(&API_METHOD_GET_MAC_VRF))];
58 |
59 | const VNET_ROUTER: Router = Router::new()
60 | .get(&list_subdirs_api_method!(VNET_SUBDIRS))
61 | .subdirs(VNET_SUBDIRS);
62 |
63 | pub const ROUTER: Router = Router::new().match_all("vnet", &VNET_ROUTER);
64 |
65 | #[api(
66 | input: {
67 | properties: {
68 | remote: { schema: REMOTE_ID_SCHEMA },
69 | node: { schema: NODE_SCHEMA },
70 | vnet: { schema: SDN_ID_SCHEMA },
71 | },
72 | },
73 | returns: { type: SdnVnetMacVrf },
74 | )]
75 | /// Get the MAC-VRF for an EVPN vnet for a node on a given remote
76 | async fn get_mac_vrf(
77 | remote: String,
78 | node: String,
79 | vnet: String,
80 | ) -> Result, Error> {
81 | let (remote_config, _) = pdm_config::remotes::config()?;
82 | let remote = get_remote(&remote_config, &remote)?;
83 | let client = connect(&remote)?;
84 |
85 | client
86 | .get_vnet_mac_vrf(&node, &vnet)
87 | .await
88 | .map_err(|err| match err {
89 | proxmox_client::Error::Api(StatusCode::NOT_IMPLEMENTED, _msg) => {
90 | anyhow!("remote {} does not support the vnet mac-vrf API call, please upgrade to the newest version!", remote.id)
91 | }
92 | _ => err.into()
93 | })
94 | }
95 | }
96 |
97 | const SUBDIRS: SubdirMap = &[("vnets", &vnets::ROUTER), ("zones", &zones::ROUTER)];
98 |
99 | pub const ROUTER: Router = Router::new()
100 | .get(&list_subdirs_api_method!(SUBDIRS))
101 | .subdirs(SUBDIRS);
102 |
--------------------------------------------------------------------------------
/lib/pdm-buildcfg/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Exports configuration data from the build system
2 |
3 | pub const PROXMOX_PKG_VERSION: &str = concat!(
4 | env!("CARGO_PKG_VERSION_MAJOR"),
5 | ".",
6 | env!("CARGO_PKG_VERSION_MINOR"),
7 | );
8 | pub const PROXMOX_PKG_RELEASE: &str = env!("CARGO_PKG_VERSION_PATCH");
9 | pub const PROXMOX_PKG_REPOID: &str = env!("REPOID");
10 |
11 | /// The configured configuration directory
12 | pub const CONFIGDIR: &str = "/etc/proxmox-datacenter-manager";
13 | pub const JS_DIR: &str = "/usr/share/javascript/proxmox-datacenter-manager";
14 |
15 | // FIXME use (and add) pdm user?
16 | /// Unix system user used by proxmox-datacenter-api
17 | pub const BACKUP_USER_NAME: &str = "www-data";
18 | /// Unix system group used by proxmox-datacenter-api
19 | pub const BACKUP_GROUP_NAME: &str = "www-data";
20 |
21 | #[macro_export]
22 | macro_rules! PDM_RUN_DIR_M {
23 | () => {
24 | "/run/proxmox-datacenter-manager"
25 | };
26 | }
27 |
28 | #[macro_export]
29 | macro_rules! PDM_STATE_DIR_M {
30 | () => {
31 | "/var/lib/proxmox-datacenter-manager"
32 | };
33 | }
34 |
35 | #[macro_export]
36 | macro_rules! PDM_LOG_DIR_M {
37 | () => {
38 | "/var/log/proxmox-datacenter-manager"
39 | };
40 | }
41 |
42 | #[macro_export]
43 | macro_rules! PDM_CACHE_DIR_M {
44 | () => {
45 | "/var/cache/proxmox-datacenter-manager"
46 | };
47 | }
48 |
49 | /// Default port to use for PDM services.
50 | pub const PDM_PORT: u16 = 8443;
51 |
52 | /// namespaced directory for in-memory (tmpfs) run state
53 | pub const PDM_RUN_DIR: &str = PDM_RUN_DIR_M!();
54 |
55 | /// namespaced directory for persistent cache
56 | pub const PDM_CACHE_DIR: &str = PDM_CACHE_DIR_M!();
57 |
58 | /// namespaced directory for persistent state
59 | pub const PDM_STATE_DIR: &str = PDM_STATE_DIR_M!();
60 |
61 | /// namespaced directory for persistent logging
62 | pub const PDM_LOG_DIR: &str = PDM_LOG_DIR_M!();
63 |
64 | /// logfile for all API requests handled by the api server and privileged API daemons. Note that
65 | /// not all failed logins can be logged here with full information, use the auth log for that.
66 | pub const API_ACCESS_LOG_FN: &str = concat!(PDM_LOG_DIR_M!(), "/api/access.log");
67 |
68 | /// logfile for any failed authentication, via ticket or via token, and new successful ticket
69 | /// creations. This file can be useful for fail2ban.
70 | pub const API_AUTH_LOG_FN: &str = concat!(PDM_LOG_DIR_M!(), "/api/auth.log");
71 |
72 | /// the PID filename for the unprivileged api daemon
73 | pub const PDM_API_PID_FN: &str = concat!(PDM_RUN_DIR_M!(), "/api.pid");
74 |
75 | /// the PID filename for the privileged api daemon
76 | pub const PDM_PRIVILEGED_API_PID_FN: &str = concat!(PDM_RUN_DIR_M!(), "/priv.pid");
77 |
78 | /// The privileged api socket file.
79 | pub const PDM_PRIVILEGED_API_SOCKET_FN: &str = concat!(PDM_RUN_DIR_M!(), "/priv.sock");
80 |
81 | pub const PDM_SUBSCRIPTION_FN: &str = configdir!("/subscription");
82 |
83 | pub const APT_PKG_STATE_FN: &str = concat!(PDM_STATE_DIR_M!(), "/pkg-state.json");
84 |
85 | /// Prepend configuration directory to a file name
86 | ///
87 | /// This is a simply way to get the full path for configuration files.
88 | /// #### Example:
89 | /// ```
90 | /// use pdm_buildcfg::configdir;
91 | /// let cert_path = configdir!("/api.pfx");
92 | /// ```
93 | #[macro_export]
94 | macro_rules! configdir {
95 | ($subdir:expr) => {
96 | concat!("/etc/proxmox-datacenter-manager", $subdir)
97 | };
98 | }
99 |
100 | /// Prepend the run directory to a file name.
101 | ///
102 | /// This is a simply way to get the full path for files in `/run`.
103 | #[macro_export]
104 | macro_rules! rundir {
105 | ($subdir:expr) => {
106 | concat!($crate::PDM_RUN_DIR_M!(), $subdir)
107 | };
108 | }
109 |
--------------------------------------------------------------------------------