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