]> git.proxmox.com Git - pve-http-server.git/blame - PVE/APIServer/Formatter/HTML.pm
rework formatter registration
[pve-http-server.git] / PVE / APIServer / Formatter / HTML.pm
CommitLineData
63307beb
DM
1package PVE::APIServer::Formatter::HTML;
2
3use strict;
4use warnings;
5
6use PVE::APIServer::Formatter;
7use HTTP::Status;
8use JSON;
9use HTML::Entities;
10use PVE::JSONSchema;
11use PVE::APIServer::Formatter::Bootstrap;
12use PVE::APIServer::Formatter::Standard;
13
14my $portal_format = 'html';
15my $portal_ct = 'text/html;charset=UTF-8';
16
17my $baseurl = "/api2/$portal_format";
18my $login_url = "$baseurl/access/ticket";
19
20sub render_page {
21 my ($doc, $html) = @_;
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 $title = "Proxmox VE";
36
37 my $nav = $doc->el(
38 class => "navbar navbar-inverse navbar-fixed-top",
39 role => "navigation", cn => {
40 class => "container", cn => [
41 {
42 class => "navbar-header", cn => [
43 {
44 tag => 'button',
45 type => 'button',
46 class => "navbar-toggle",
47 'data-toggle' => "collapse",
48 'data-target' => ".navbar-collapse",
49 cn => [
50 { tag => 'span', class => 'sr-only', text => "Toggle navigation" },
51 { tag => 'span', class => 'icon-bar' },
52 { tag => 'span', class => 'icon-bar' },
53 { tag => 'span', class => 'icon-bar' },
54 ],
55 },
56 {
57 tag => 'a',
58 class => "navbar-brand",
59 href => $baseurl,
60 text => $title,
61 },
62 ],
63 },
64 {
65 class => "collapse navbar-collapse",
66 cn => {
67 tag => 'ul',
68 class => "nav navbar-nav",
69 cn => $items,
70 },
71 },
72 ],
73 });
74
75 $items = [];
76 my @pcomp = split('/', $doc->{url});
77 shift @pcomp; # empty
78 shift @pcomp; # api2
79 shift @pcomp; # $format
80
81 my $href = $baseurl;
82 push @$items, { tag => 'li', cn => {
83 tag => 'a',
84 href => $href,
85 text => 'Home'}};
86
87 foreach my $comp (@pcomp) {
88 $href .= "/$comp";
89 push @$items, { tag => 'li', cn => {
90 tag => 'a',
91 href => $href,
92 text => $comp}};
93 }
94
95 my $breadcrumbs = $doc->el(tag => 'ol', class => 'breadcrumb container', cn => $items);
96
97 return $doc->body($nav . $breadcrumbs . $html);
98}
99
100my $login_form = sub {
101 my ($doc, $param, $errmsg) = @_;
102
103 $param = {} if !$param;
104
105 my $username = $param->{username} || '';
106 my $password = $param->{password} || '';
107
108 my $items = [
109 {
110 tag => 'label',
111 text => "Please sign in",
112 },
113 {
114 tag => 'input',
115 type => 'text',
116 class => 'form-control',
117 name => 'username',
118 value => $username,
119 placeholder => "Enter user name",
120 required => 1,
121 autofocus => 1,
122 },
123 {
124 tag => 'input',
125 type => 'password',
126 class => 'form-control',
127 name => 'password',
128 value => $password,
129 placeholder => 'Password',
130 required => 1,
131 },
132 ];
133
134 my $html = '';
135
136 $html .= $doc->alert(text => $errmsg) if ($errmsg);
137
138 $html .= $doc->el(
139 class => 'container',
140 cn => {
141 tag => 'form',
142 role => 'form',
143 method => 'POST',
144 action => $login_url,
145 cn => [
146 {
147 class => 'form-group',
148 cn => $items,
149 },
150 {
151 tag => 'button',
152 type => 'submit',
153 class => 'btn btn-lg btn-primary btn-block',
154 text => "Sign in",
155 },
156 ],
157 });
158
159 return $html;
160};
161
162PVE::APIServer::Formatter::register_login_formatter($portal_format, sub {
163 my ($path, $auth) = @_;
164
165 my $headers = HTTP::Headers->new(Location => $login_url);
166 return HTTP::Response->new(301, "Moved", $headers);
167});
168
169PVE::APIServer::Formatter::register_formatter($portal_format, sub {
170 my ($res, $data, $param, $path, $auth) = @_;
171
172 # fixme: clumsy!
c7154375 173 PVE::APIServer::Formatter::Standard::prepare_response_data($portal_format, $res);
63307beb
DM
174 $data = $res->{data};
175
176 my $html = '';
c7154375 177 my $doc = PVE::APIServer::Formatter::Bootstrap->new($res, $path);
63307beb
DM
178
179 if (!HTTP::Status::is_success($res->{status})) {
180 $html .= $doc->alert(text => "Error $res->{status}: $res->{message}");
181 }
182
183 my $info = $res->{info};
184
185 $html .= $doc->el(tag => 'h3', text => 'Description');
186 $html .= $doc->el(tag => 'p', text => $info->{description});
187
188 my $lnk = PVE::JSONSchema::method_get_child_link($info);
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);
243 return ($raw, $portal_ct);
244});
245
c7154375 246PVE::APIServer::Formatter::register_page_formatter(
63307beb
DM
247 'format' => $portal_format,
248 method => 'GET',
249 path => "/access/ticket",
250 code => sub {
251 my ($res, $data, $param, $path, $auth) = @_;
252
c7154375 253 my $doc = PVE::APIServer::Formatter::Bootstrap->new($res, $path);
63307beb
DM
254
255 my $html = &$login_form($doc);
256
257 my $raw = render_page($doc, $html);
258 return ($raw, $portal_ct);
259 });
260
c7154375 261PVE::APIServer::Formatter::register_page_formatter(
63307beb
DM
262 'format' => $portal_format,
263 method => 'POST',
264 path => "/access/ticket",
265 code => sub {
266 my ($res, $data, $param, $path, $auth) = @_;
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);
281
282 my $html = &$login_form($doc);
283
284 my $raw = render_page($doc, $html);
285 return ($raw, $portal_ct);
286 });
287
2881;