[pve-devel] [PATCH http-server 3/3] formatter: html: update to bootstrap 5

Dominik Csapak d.csapak at proxmox.com
Tue Jun 3 15:04:26 CEST 2025


this makes a few changes necessary, but not too much:
* include the different directory for bootstrap5
* use different navbar markup
* different classes for navbar container + items
* add classes to pre tag since it's not styled anymore in newer
  bootstrap versions
* add 'form-label' to labels
* use containers with 'mb-3' for form + buttons
* use 'd-grid' container for button instead of 'btn-block'
* add 'breadcrumb-item' where necessary

Since bootstrap 5 does not depend on jQuery anymore, use that chance to
remove it here as dependency too. For that remove the 'button'
and 'add_js' subs that were never actually used.

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 debian/control                           |   3 +-
 src/PVE/APIServer/AnyEvent.pm            |   3 +
 src/PVE/APIServer/Formatter/Bootstrap.pm |  37 +------
 src/PVE/APIServer/Formatter/HTML.pm      | 132 +++++++++++++----------
 4 files changed, 84 insertions(+), 91 deletions(-)

diff --git a/debian/control b/debian/control
index 0d0161e..4ce0368 100644
--- a/debian/control
+++ b/debian/control
@@ -15,8 +15,7 @@ Depends: libanyevent-http-perl,
          libhttp-date-perl,
          libhttp-message-perl,
          libio-socket-ssl-perl,
-         libjs-bootstrap,
-         libjs-jquery,
+         libjs-bootstrap5,
          libjson-perl,
          libnet-ip-perl,
          libpve-common-perl (>= 8.0.2),
diff --git a/src/PVE/APIServer/AnyEvent.pm b/src/PVE/APIServer/AnyEvent.pm
index b71a9a5..9aeae2f 100644
--- a/src/PVE/APIServer/AnyEvent.pm
+++ b/src/PVE/APIServer/AnyEvent.pm
@@ -2032,6 +2032,9 @@ sub new {
     my $glyphicons = '/usr/share/fonts/truetype/glyphicons/';
     add_dirs($self->{dirs}, '/js/bootstrap/fonts/' => "$glyphicons");
 
+    # libjs-bootstrap5 uses a different dir with symlinks
+    add_dirs($self->{dirs}, '/js/bootstrap5/' => "/usr/share/bootstrap-html");
+
     # init inotify
     PVE::INotify::inotify_init();
 
diff --git a/src/PVE/APIServer/Formatter/Bootstrap.pm b/src/PVE/APIServer/Formatter/Bootstrap.pm
index 0055d64..911caac 100644
--- a/src/PVE/APIServer/Formatter/Bootstrap.pm
+++ b/src/PVE/APIServer/Formatter/Bootstrap.pm
@@ -53,7 +53,7 @@ sub body {
     <title>$self->{title}</title>
 
     <!-- Bootstrap -->
-    <link href="/js/bootstrap/css/bootstrap.min.css" rel="stylesheet">
+    <link href="/js/bootstrap5/css/bootstrap.min.css" rel="stylesheet">
 
 <script type="text/javascript">
 $jssetup
@@ -65,10 +65,8 @@ body {
 }
     </style>
 
-    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
-    <script src="/js/jquery/jquery.min.js"></script>
-    <!-- Include all compiled plugins (below), or include individual files as needed -->
-    <script src="/js/bootstrap/js/bootstrap.min.js"></script>
+    <!-- Include bootstrap bundle (everything necessary to run) -->
+    <script src="/js/bootstrap5/js/bootstrap.bundle.min.js"></script>
 
   </head>
   <body>
@@ -155,33 +153,4 @@ sub alert {
     return $self->el(class => "alert alert-danger", %param);
 }
 
-sub add_js {
-    my ($self, $js) = @_;
-
-    $self->{js} .= $js . "\n";
-}
-
-my $format_event_callback = sub {
-    my ($info) = @_;
-
-    my $pstr = encode_json($info->{param});
-    return "function(e){$info->{fn}.apply(e, $pstr);}";
-};
-
-sub button {
-    my ($self, %param) = @_;
-
-    $param{tag} = 'button';
-    $param{class} = "btn btn-default btn-xs";
-
-    if (my $click = delete $param{click}) {
-	my ($html, $id) = $self->el(%param);
-	my $cb = &$format_event_callback($click);
-	$self->add_js("jQuery('#$id').on('click', $cb);");
-	return $html;
-    } else {
-	return $self->el(%param);
-    }
-}
-
 1;
diff --git a/src/PVE/APIServer/Formatter/HTML.pm b/src/PVE/APIServer/Formatter/HTML.pm
index 2ce0723..afda118 100644
--- a/src/PVE/APIServer/Formatter/HTML.pm
+++ b/src/PVE/APIServer/Formatter/HTML.pm
@@ -27,75 +27,86 @@ my $get_portal_login_url = sub {
 sub render_page {
     my ($doc, $html, $config) = @_;
 
-    my $items = [];
-
-    push @$items, {
-	tag => 'li',
-	cn => {
-	    tag => 'a',
-	    href => $get_portal_login_url->($config),
-	    onclick => "PVE.delete_auth_cookie();",
-	    text => "Logout",
-	}};
-
     my $base_url = $get_portal_base_url->($config);
 
     my $nav = $doc->el(
-	class => "navbar navbar-inverse navbar-fixed-top",
-	role => "navigation", cn => {
-	    class => "container", cn => [
+	class => "navbar navbar-dark navbar-expand-lg bg-dark fixed-top",
+	'data-bs-theme' => 'dark',
+	role => "navigation",
+	cn => {
+	    class => "container",
+	    cn => [
 		{
-		    class => "navbar-header", cn => [
-			{
-			    tag => 'button',
-			    type => 'button',
-			    class => "navbar-toggle",
-			    'data-toggle' => "collapse",
-			    'data-target' => ".navbar-collapse",
-			    cn => [
-				{ tag => 'span', class => 'sr-only', text => "Toggle navigation" },
-				{ tag => 'span', class => 'icon-bar' },
-				{ tag => 'span', class => 'icon-bar' },
-				{ tag => 'span', class => 'icon-bar' },
-			    ],
-			},
+		    tag => 'a',
+		    class => "navbar-brand",
+		    href => $base_url,
+		    text => $config->{title},
+		},
+		{
+		    tag => 'button',
+		    type => 'button',
+		    class => "navbar-toggler",
+		    'data-bs-toggle' => "collapse",
+		    'data-bs-target' => ".navbarNav",
+		    cn => [
 			{
-			    tag => 'a',
-			    class => "navbar-brand",
-			    href => $base_url,
-			    text => $config->{title},
+			    tag => 'span',
+			    class => 'navbar-toggler-icon',
 			},
 		    ],
 		},
 		{
-		    class => "collapse navbar-collapse",
+		    class => "collapse navbar-collapse navbarNav",
 		    cn => {
 			tag => 'ul',
-			class => "nav navbar-nav",
-			cn => $items,
+			class => "navbar-nav",
+			cn => [
+			    {
+				tag => 'li',
+				class => 'nav-item',
+				cn => {
+				    tag => 'a',
+				    class => 'nav-link',
+				    href => $get_portal_login_url->($config),
+				    onclick => "PVE.delete_auth_cookie",
+				    text => "Logout",
+				},
+			    }
+			],
 		    },
 		},
-	    ],
-	});
+	    ]
+	}
+    );
 
-    $items = [];
+    my $items = [];
     my @pcomp = split('/', $doc->{url});
     shift @pcomp; # empty
     shift @pcomp; # api2
     shift @pcomp; # $format
 
     my $href = $base_url;
-    push @$items, { tag => 'li', cn => {
-	tag => 'a',
-	href => $href,
-	text => 'Home'}};
-
-    foreach my $comp (@pcomp) {
-	$href .= "/".encode_entities($comp);
-	push @$items, { tag => 'li', cn => {
+    push @$items, {
+	tag => 'li',
+	class => 'breadcrumb-item',
+	cn => {
 	    tag => 'a',
 	    href => $href,
-	    text => $comp}};
+	    text => 'Home',
+	},
+    };
+
+    foreach my $comp (@pcomp) {
+	$href .= "/" . encode_entities($comp);
+	push @$items, {
+	    tag => 'li',
+	    class => 'breadcrumb-item',
+	    cn => {
+		tag => 'a',
+		href => $href,
+		text => $comp,
+	    },
+	};
     }
 
     my $breadcrumbs = $doc->el(tag => 'ol', class => 'breadcrumb container', cn => $items);
@@ -114,6 +125,7 @@ my $login_form = sub {
     my $items = [
 	{
 	    tag => 'label',
+	    class => 'form-label',
 	    text => "Please sign in",
 	},
 	{
@@ -150,14 +162,24 @@ my $login_form = sub {
 	    action => $get_portal_login_url->($config),
 	    cn => [
 		{
-		    class => 'form-group',
-		    cn => $items,
+		    class => "mb-3",
+		    cn => [
+			{
+			    class => 'form-group',
+			    cn => $items,
+			},
+		    ],
 		},
 		{
-		    tag => 'button',
-		    type => 'submit',
-		    class => 'btn btn-lg btn-primary btn-block',
-		    text => "Sign in",
+		    class => "d-grid",
+		    cn => [
+			{
+			    tag => 'button',
+			    type => 'submit',
+			    class => 'btn btn-lg btn-primary',
+			    text => "Sign in",
+			},
+		    ],
 		},
 	    ],
 	});
@@ -236,13 +258,13 @@ PVE::APIServer::Formatter::register_formatter($portal_format, sub {
 	} else {
 
 	    my $json = to_json($data, {allow_nonref => 1, pretty => 1, canonical => 1});
-	    $html .= $doc->el(tag => 'pre', text => $json);
+	    $html .= $doc->el(tag => 'pre', class => 'bg-light border rounded p-2', text => $json);
  	}
 
     } else {
 
 	my $json = to_json($data, {allow_nonref => 1, pretty => 1, canonical => 1});
-	$html .= $doc->el(tag => 'pre', text => $json);
+        $html .= $doc->el(tag => 'pre', class => 'bg-light border rounded p-2', text => $json);
     }
 
     $html = $doc->el(class => 'container', html => $html);
-- 
2.39.5





More information about the pve-devel mailing list