]> git.proxmox.com Git - pve-http-server.git/blob - PVE/APIServer/Formatter/Bootstrap.pm
pass $title to formatter functions
[pve-http-server.git] / PVE / APIServer / Formatter / Bootstrap.pm
1 package PVE::APIServer::Formatter::Bootstrap;
2
3 use strict;
4 use warnings;
5 use URI::Escape;
6 use HTML::Entities;
7 use JSON;
8
9 # FIXME: cookie_name
10 # FIXME: console code??
11
12 # Helpers to generate simple html pages using Bootstrap markup.
13
14 my $jssrc = <<_EOJS;
15 PVE = {
16 delete_auth_cookie: function() {
17 document.cookie = "PVEAuthCookie=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/; secure;";
18 },
19 open_vm_console: function(node, vmid) {
20 console.log("open vm " + vmid + " on node " + node);
21
22 var downloadWithName = function(uri, name) {
23 var link = jQuery('#pve_console_anchor');
24 link.attr("href", uri);
25
26 // Note: we need to tell android the correct file name extension
27 // but we do not set 'download' tag for other environments, because
28 // It can have strange side effects (additional user prompt on firefox)
29 var andriod = navigator.userAgent.match(/Android/i) ? true : false;
30 if (andriod) {
31 link.attr("download", name);
32 }
33
34 if (document.createEvent) {
35 var evt = document.createEvent("MouseEvents");
36 evt.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
37 link.get(0).dispatchEvent(evt);
38 } else {
39 link.get(0).fireEvent('onclick');
40 }
41 };
42
43 jQuery.ajax("/api2/json/console", {
44 data: { vmid: vmid, node: node },
45 headers: { CSRFPreventionToken: PVE.CSRFPreventionToken },
46 dataType: 'json',
47 type: 'POST',
48 error: function(jqXHR, textStatus, errorThrown) {
49 // fixme: howto view JS errors ?
50 console.log("ERROR " + textStatus + ": " + errorThrown);
51 },
52 success: function(data) {
53 var raw = "[virt-viewer]\\n";
54 jQuery.each(data.data, function(k, v) {
55 raw += k + "=" + v + "\\n";
56 });
57 var url = 'data:application/x-virt-viewer;charset=UTF-8,' +
58 encodeURIComponent(raw);
59
60 downloadWithName(url, "pve-spice.vv");
61 }
62 });
63 }
64 };
65 _EOJS
66
67 sub new {
68 my ($class, $res, $url, $auth, $csrfgen_func, $title) = @_;
69
70 my $self = bless {
71 url => $url,
72 title => $title,
73 js => '',
74 };
75
76 if (my $username = $auth->{userid}) {
77 $self->{csrftoken} = &$csrfgen_func($username);
78 }
79
80 return $self;
81 }
82
83 sub body {
84 my ($self, $html) = @_;
85
86 my $jssetup = '';
87
88 if ($self->{csrftoken}) {
89 $jssetup .= "PVE.CSRFPreventionToken = '$self->{csrftoken}';\n";
90 }
91
92 return <<_EOD;
93 <!DOCTYPE html>
94 <html lang="en">
95 <head>
96 <meta charset="utf-8">
97 <meta http-equiv="X-UA-Compatible" content="IE=edge">
98 <meta name="viewport" content="width=device-width, initial-scale=1">
99 <title>$self->{title}</title>
100
101 <!-- Bootstrap -->
102 <link href="/css/bootstrap.min.css" rel="stylesheet">
103
104 <script type="text/javascript">
105 $jssrc
106 $jssetup
107 </script>
108
109 <style>
110 body {
111 padding-top: 70px;
112 }
113 </style>
114
115 <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
116 <script src="/js/jquery-3.3.1.min.js"></script>
117 <!-- Include all compiled plugins (below), or include individual files as needed -->
118 <script src="/js/bootstrap.min.js"></script>
119
120 </head>
121 <body>
122 <a class="hidden" id="pve_console_anchor"></a>
123 $html
124 <script type="text/javascript">
125 $self->{js}
126 </script>
127 </body>
128 </html>
129 _EOD
130 }
131
132 my $comp_id_counter = 0;
133
134 sub el {
135 my ($self, %param) = @_;
136
137 $param{tag} = 'div' if !$param{tag};
138
139 my $id;
140
141 my $html = "<$param{tag}";
142
143 if (wantarray) {
144 $comp_id_counter++;
145 $id = "pveid$comp_id_counter";
146 $html .= " id=$id";
147 }
148
149 my $skip = {
150 tag => 1,
151 cn => 1,
152 html => 1,
153 text => 1,
154 };
155
156 my $boolattr = {
157 required => 1,
158 autofocus => 1,
159 };
160
161 my $noescape = {
162 placeholder => 1,
163 };
164
165 foreach my $attr (keys %param) {
166 next if $skip->{$attr};
167 my $v = $noescape->{$attr} ? $param{$attr} : uri_escape_utf8($param{$attr},"[^\/\ A-Za-z0-9\-\._~]");
168 next if !defined($v);
169 if ($boolattr->{$attr}) {
170 $html .= " $attr" if $v;
171 } else {
172 $html .= " $attr=\"$v\"";
173 }
174 }
175
176 $html .= ">";
177
178
179 if (my $cn = $param{cn}) {
180 if(ref($cn) eq 'ARRAY'){
181 foreach my $rec (@$cn) {
182 $html .= $self->el(%$rec);
183 }
184 } else {
185 $html .= $self->el(%$cn);
186 }
187 } elsif ($param{html}) {
188 $html .= $param{html};
189 } elsif ($param{text}) {
190 $html .= encode_entities($param{text});
191 }
192
193 $html .= "</$param{tag}>";
194
195 return wantarray ? ($html, $id) : $html;
196 }
197
198 sub alert {
199 my ($self, %param) = @_;
200
201 return $self->el(class => "alert alert-danger", %param);
202 }
203
204 sub add_js {
205 my ($self, $js) = @_;
206
207 $self->{js} .= $js . "\n";
208 }
209
210 my $format_event_callback = sub {
211 my ($info) = @_;
212
213 my $pstr = encode_json($info->{param});
214 return "function(e){$info->{fn}.apply(e, $pstr);}";
215 };
216
217 sub button {
218 my ($self, %param) = @_;
219
220 $param{tag} = 'button';
221 $param{class} = "btn btn-default btn-xs";
222
223 if (my $click = delete $param{click}) {
224 my ($html, $id) = $self->el(%param);
225 my $cb = &$format_event_callback($click);
226 $self->add_js("jQuery('#$id').on('click', $cb);");
227 return $html;
228 } else {
229 return $self->el(%param);
230 }
231 }
232
233 1;