]> git.proxmox.com Git - pve-http-server.git/blob - PVE/APIServer/Formatter/HTML.pm
Formatter/HTML: only display description if we have one
[pve-http-server.git] / PVE / APIServer / Formatter / HTML.pm
1 package PVE::APIServer::Formatter::HTML;
2
3 use strict;
4 use warnings;
5
6 use PVE::APIServer::Formatter;
7 use HTTP::Status;
8 use JSON;
9 use HTML::Entities;
10 use PVE::JSONSchema;
11 use PVE::APIServer::Formatter::Bootstrap;
12 use PVE::APIServer::Formatter::Standard;
13
14 my $portal_format = 'html';
15 my $portal_ct = 'text/html;charset=UTF-8';
16
17 my $baseurl = "/api2/$portal_format";
18 my $login_url = "$baseurl/access/ticket";
19
20 sub render_page {
21 my ($doc, $html, $title) = @_;
22
23 my $items = [];
24
25 push @$items, {
26 tag => 'li',
27 cn => {
28 tag => 'a',
29 href => $login_url,
30 onClick => "PVE.delete_auth_cookie();",
31 text => "Logout",
32 }};
33
34
35 my $nav = $doc->el(
36 class => "navbar navbar-inverse navbar-fixed-top",
37 role => "navigation", cn => {
38 class => "container", cn => [
39 {
40 class => "navbar-header", cn => [
41 {
42 tag => 'button',
43 type => 'button',
44 class => "navbar-toggle",
45 'data-toggle' => "collapse",
46 'data-target' => ".navbar-collapse",
47 cn => [
48 { tag => 'span', class => 'sr-only', text => "Toggle navigation" },
49 { tag => 'span', class => 'icon-bar' },
50 { tag => 'span', class => 'icon-bar' },
51 { tag => 'span', class => 'icon-bar' },
52 ],
53 },
54 {
55 tag => 'a',
56 class => "navbar-brand",
57 href => $baseurl,
58 text => $title,
59 },
60 ],
61 },
62 {
63 class => "collapse navbar-collapse",
64 cn => {
65 tag => 'ul',
66 class => "nav navbar-nav",
67 cn => $items,
68 },
69 },
70 ],
71 });
72
73 $items = [];
74 my @pcomp = split('/', $doc->{url});
75 shift @pcomp; # empty
76 shift @pcomp; # api2
77 shift @pcomp; # $format
78
79 my $href = $baseurl;
80 push @$items, { tag => 'li', cn => {
81 tag => 'a',
82 href => $href,
83 text => 'Home'}};
84
85 foreach my $comp (@pcomp) {
86 $href .= "/$comp";
87 push @$items, { tag => 'li', cn => {
88 tag => 'a',
89 href => $href,
90 text => $comp}};
91 }
92
93 my $breadcrumbs = $doc->el(tag => 'ol', class => 'breadcrumb container', cn => $items);
94
95 return $doc->body($nav . $breadcrumbs . $html);
96 }
97
98 my $login_form = sub {
99 my ($doc, $param, $errmsg) = @_;
100
101 $param = {} if !$param;
102
103 my $username = $param->{username} || '';
104 my $password = $param->{password} || '';
105
106 my $items = [
107 {
108 tag => 'label',
109 text => "Please sign in",
110 },
111 {
112 tag => 'input',
113 type => 'text',
114 class => 'form-control',
115 name => 'username',
116 value => $username,
117 placeholder => "Enter user name",
118 required => 1,
119 autofocus => 1,
120 },
121 {
122 tag => 'input',
123 type => 'password',
124 class => 'form-control',
125 name => 'password',
126 value => $password,
127 placeholder => 'Password',
128 required => 1,
129 },
130 ];
131
132 my $html = '';
133
134 $html .= $doc->alert(text => $errmsg) if ($errmsg);
135
136 $html .= $doc->el(
137 class => 'container',
138 cn => {
139 tag => 'form',
140 role => 'form',
141 method => 'POST',
142 action => $login_url,
143 cn => [
144 {
145 class => 'form-group',
146 cn => $items,
147 },
148 {
149 tag => 'button',
150 type => 'submit',
151 class => 'btn btn-lg btn-primary btn-block',
152 text => "Sign in",
153 },
154 ],
155 });
156
157 return $html;
158 };
159
160 PVE::APIServer::Formatter::register_login_formatter($portal_format, sub {
161 my ($path, $auth) = @_;
162
163 my $headers = HTTP::Headers->new(Location => $login_url);
164 return HTTP::Response->new(301, "Moved", $headers);
165 });
166
167 PVE::APIServer::Formatter::register_formatter($portal_format, sub {
168 my ($res, $data, $param, $path, $auth, $csrfgen_func, $title) = @_;
169
170 # fixme: clumsy!
171 PVE::APIServer::Formatter::Standard::prepare_response_data($portal_format, $res);
172 $data = $res->{data};
173
174 my $html = '';
175 my $doc = PVE::APIServer::Formatter::Bootstrap->new($res, $path, $auth, $csrfgen_func, $title);
176
177 if (!HTTP::Status::is_success($res->{status})) {
178 $html .= $doc->alert(text => "Error $res->{status}: $res->{message}");
179 }
180
181 my $lnk;
182
183 if (my $info = $res->{info}) {
184 $html .= $doc->el(tag => 'h3', text => 'Description');
185 $html .= $doc->el(tag => 'p', text => $info->{description});
186
187 $lnk = PVE::JSONSchema::method_get_child_link($info);
188 }
189
190 if ($lnk && $data && $data->{data} && HTTP::Status::is_success($res->{status})) {
191
192 my $href = $lnk->{href};
193 if ($href =~ m/^\{(\S+)\}$/) {
194
195 my $items = [];
196
197 my $prop = $1;
198 $path =~ s/\/+$//; # remove trailing slash
199
200 foreach my $elem (sort {$a->{$prop} cmp $b->{$prop}} @{$data->{data}}) {
201 next if !ref($elem);
202
203 if (defined(my $value = $elem->{$prop})) {
204 my $tv = to_json($elem, {pretty => 1, allow_nonref => 1, canonical => 1});
205
206 push @$items, {
207 tag => 'a',
208 class => 'list-group-item',
209 href => "$path/$value",
210 cn => [
211 {
212 tag => 'h4',
213 class => 'list-group-item-heading',
214 text => $value,
215 },
216 {
217 tag => 'pre',
218 class => 'list-group-item',
219 text => $tv,
220 },
221 ],
222 };
223 }
224 }
225
226 $html .= $doc->el(class => 'list-group', cn => $items);
227
228 } else {
229
230 my $json = to_json($data, {allow_nonref => 1, pretty => 1});
231 $html .= $doc->el(tag => 'pre', text => $json);
232 }
233
234 } else {
235
236 my $json = to_json($data, {allow_nonref => 1, pretty => 1});
237 $html .= $doc->el(tag => 'pre', text => $json);
238 }
239
240 $html = $doc->el(class => 'container', html => $html);
241
242 my $raw = render_page($doc, $html, $title);
243 return ($raw, $portal_ct);
244 });
245
246 PVE::APIServer::Formatter::register_page_formatter(
247 'format' => $portal_format,
248 method => 'GET',
249 path => "/access/ticket",
250 code => sub {
251 my ($res, $data, $param, $path, $auth, $csrfgen_func, $title) = @_;
252
253 my $doc = PVE::APIServer::Formatter::Bootstrap->new($res, $path, $auth, $csrfgen_func, $title);
254
255 my $html = &$login_form($doc);
256
257 my $raw = render_page($doc, $html, $title);
258 return ($raw, $portal_ct);
259 });
260
261 PVE::APIServer::Formatter::register_page_formatter(
262 'format' => $portal_format,
263 method => 'POST',
264 path => "/access/ticket",
265 code => sub {
266 my ($res, $data, $param, $path, $auth, $csrfgen_func, $title) = @_;
267
268 if (HTTP::Status::is_success($res->{status})) {
269 my $cookie = PVE::APIServer::Formatter::create_auth_cookie(
270 $data->{ticket}, $auth->{cookie_name});
271
272 my $headers = HTTP::Headers->new(Location => $baseurl,
273 'Set-Cookie' => $cookie);
274 return HTTP::Response->new(301, "Moved", $headers);
275 }
276
277 # Note: HTTP server redirects to 'GET /access/ticket', so below
278 # output is not really visible.
279
280 my $doc = PVE::APIServer::Formatter::Bootstrap->new($res, $path, $auth, $csrfgen_func, $title);
281
282 my $html = &$login_form($doc);
283
284 my $raw = render_page($doc, $html, $title);
285 return ($raw, $portal_ct);
286 });
287
288 1;