]>
Commit | Line | Data |
---|---|---|
63307beb DM |
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) = @_; | |
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 | ||
100 | my $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 | ||
162 | PVE::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 | ||
169 | PVE::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 | 246 | PVE::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 | 261 | PVE::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 | ||
288 | 1; |