1
|
|
|
<?php |
2
|
|
|
// This file is part of BOINC. |
3
|
|
|
// http://boinc.berkeley.edu |
4
|
|
|
// Copyright (C) 2017 University of California |
5
|
|
|
// |
6
|
|
|
// BOINC is free software; you can redistribute it and/or modify it |
7
|
|
|
// under the terms of the GNU Lesser General Public License |
8
|
|
|
// as published by the Free Software Foundation, |
9
|
|
|
// either version 3 of the License, or (at your option) any later version. |
10
|
|
|
// |
11
|
|
|
// BOINC is distributed in the hope that it will be useful, |
12
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of |
13
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
14
|
|
|
// See the GNU Lesser General Public License for more details. |
15
|
|
|
// |
16
|
|
|
// You should have received a copy of the GNU Lesser General Public License |
17
|
|
|
// along with BOINC. If not, see <http://www.gnu.org/licenses/>. |
18
|
|
|
|
19
|
|
|
// utility functions for admin web pages |
20
|
|
|
|
21
|
|
|
require_once("../inc/db_ops.inc"); |
22
|
|
|
require_once("../inc/util.inc"); |
23
|
|
|
require_once("../project/project.inc"); |
24
|
|
|
|
25
|
|
|
display_errors(); |
26
|
|
|
|
27
|
|
|
function admin_page_head($title) { |
28
|
|
|
echo "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">"; |
29
|
|
|
echo sprintf('<html><head><title>%s</title> |
30
|
|
|
<meta http-equiv="content-type" content="text/html;charset=utf-8" /> |
31
|
|
|
<link type="text/css" rel="stylesheet" href="%s/bootstrap.min.css" media="all"> |
32
|
|
|
<link type="text/css" rel="stylesheet" href="%s/custom.css" media="all"> |
33
|
|
|
</head> |
34
|
|
|
<body> |
35
|
|
|
<div class="container-fluid"> |
36
|
|
|
<h2>%s: %s</h2> |
37
|
|
|
', |
38
|
|
|
$title, |
39
|
|
|
secure_url_base(), |
40
|
|
|
secure_url_base(), |
41
|
|
|
PROJECT, |
42
|
|
|
$title |
43
|
|
|
); |
44
|
|
|
show_login_info(); |
45
|
|
|
echo "<p>"; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
function admin_page_tail() { |
49
|
|
|
echo sprintf(' |
50
|
|
|
<hr><center><a href=index.php>Main page</a></center> |
51
|
|
|
<script src="%s/jquery.min.js"></script> |
52
|
|
|
<script src="%s/bootstrap.min.js"></script> |
53
|
|
|
</div> |
54
|
|
|
</body> |
55
|
|
|
</html> |
56
|
|
|
', |
57
|
|
|
secure_url_base(), |
58
|
|
|
secure_url_base() |
59
|
|
|
); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
// TODO: get rid of all the following |
63
|
|
|
|
64
|
|
|
function print_checkbox($text,$name,$checked) { |
65
|
|
|
echo "<input type=\"checkbox\" name=\"$name\"" |
66
|
|
|
. (strlen($checked) ? " checked=\"checked\"" : "") . ">" |
67
|
|
|
. "$text\n" |
68
|
|
|
. "<p>\n"; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
function print_radio_button($text,$name,$value,$checked) { |
72
|
|
|
echo "<input type=\"radio\" name=\"$name\" value=\"$value\"" |
73
|
|
|
. (strlen($checked) ? " checked=\"checked\"" : "") . ">" |
74
|
|
|
. "$text\n" |
75
|
|
|
. "<br>\n"; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
function print_text_field($text,$name,$value) { |
79
|
|
|
echo "$text <input type=\"text\" size=\"10\" name=\"$name\" value=\"$value\">\n" |
80
|
|
|
. "<p>\n"; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
function row($x, $y) { |
84
|
|
|
echo "<tr><td width=30% valign=\"top\" align=\"right\">$x </td>\n<td>$y</td>\n</tr>\n"; |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
function c_row2($color, $x, $y) { |
88
|
|
|
echo "<tr bgcolor=\"$color\"><td align=\"right\">$x</td><td>$y</td></tr>\n"; |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
function show_profile_link_ops($user) { |
92
|
|
|
if ($user->has_profile) { |
93
|
|
|
row2("Profile", |
94
|
|
|
"<a href=\"".url_base()."view_profile.php?userid=$user->id\">View</a>" |
95
|
|
|
); |
96
|
|
|
} |
97
|
|
|
} |
98
|
|
|
|
99
|
|
|
// initialize database connection with username & password from |
100
|
|
|
// command line instead of config.xml |
101
|
|
|
// |
102
|
|
|
function db_init_cli() { |
103
|
|
|
$config = get_config(); |
104
|
|
|
$db_name = parse_config($config, "<db_name>"); |
105
|
|
|
$host = parse_config($config, "<db_host>"); |
106
|
|
|
if ($host == null) { |
107
|
|
|
$host = "localhost"; |
108
|
|
|
} |
109
|
|
|
$in = fopen("php://stdin","r"); |
110
|
|
|
print "Database username (default: owner of mysqld process): "; |
111
|
|
|
$user = rtrim(fgets($in, 80)); |
112
|
|
|
print "Database password (if any): "; |
113
|
|
|
$pass = rtrim(fgets($in, 80)); |
114
|
|
|
|
115
|
|
|
$retval = _mysql_connect($host, $user, $pass, $db_name); |
116
|
|
|
if (!$retval) { |
117
|
|
|
die("Can't connect to DB\n"); |
118
|
|
|
} |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
function print_login_form_ops($next_url='') { |
122
|
|
|
if ($next_url == '') $next_url = $_SERVER['REQUEST_URI']; |
123
|
|
|
start_table(); |
124
|
|
|
echo " |
125
|
|
|
<form method=post action=login_action.php> |
126
|
|
|
<input type=hidden name=next_url value=$next_url> |
127
|
|
|
"; |
128
|
|
|
row2("Email", "<input name=email_addr size=40>"); |
129
|
|
|
row2("Password", "<input type=password name=passwd size=40>"); |
130
|
|
|
row2(tra("Stay logged in on this computer"), '<input type="checkbox" name="stay_logged_in" checked>'); |
131
|
|
|
row2("", "<input class=\"btn btn-primary\" type=submit value=OK>"); |
132
|
|
|
end_table(); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
function get_logged_in_user_ops() { |
136
|
|
|
global $g_logged_in_user; |
137
|
|
|
if ($g_logged_in_user) return $g_logged_in_user; |
138
|
|
|
$authenticator = null; |
139
|
|
|
if (isset($_COOKIE['auth'])) $authenticator = $_COOKIE['auth']; |
140
|
|
|
|
141
|
|
|
$authenticator = BoincDb::escape_string($authenticator); |
142
|
|
|
if ($authenticator) { |
143
|
|
|
$g_logged_in_user = BoincUser::lookup("authenticator='$authenticator'"); |
144
|
|
|
} |
145
|
|
|
return $g_logged_in_user; |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
////////// functions for access control of admin web pages ///////////// |
149
|
|
|
|
150
|
|
|
// allow access only if logged in as user in a given set |
151
|
|
|
// |
152
|
|
|
function auth_ops_userid($admin_user_ids) { |
153
|
|
|
$user = get_logged_in_user_ops(); |
154
|
|
|
if (!$user) { |
155
|
|
|
admin_page_head("Log in"); |
156
|
|
|
echo "You must log in to performance admin functions.<p>\n"; |
157
|
|
|
print_login_form_ops(); |
158
|
|
|
admin_page_tail(); |
159
|
|
|
exit; |
160
|
|
|
} else if (!in_array($user->id, $admin_user_ids)) { |
161
|
|
|
admin_page_head("Log in"); |
162
|
|
|
echo " |
163
|
|
|
You must be logged in as an admin to perform admin functions. |
164
|
|
|
<p> |
165
|
|
|
<a href=logout.php>Log out</a> |
166
|
|
|
"; |
167
|
|
|
admin_page_tail(); |
168
|
|
|
exit; |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
// allow access only to users with ADMIN/DEV flags in forum prefs. |
173
|
|
|
// If you use this, make sure you know who has these privileges |
174
|
|
|
// |
175
|
|
|
function auth_ops_privilege() { |
176
|
|
|
$user = get_logged_in_user_ops(); |
177
|
|
|
if (!$user) { |
178
|
|
|
admin_page_head("Log in"); |
179
|
|
|
echo "You must log in to performance admin functions.<p>\n"; |
180
|
|
|
print_login_form_ops(); |
181
|
|
|
admin_page_tail(); |
182
|
|
|
exit; |
183
|
|
|
} |
184
|
|
|
BoincForumPrefs::lookup($user); |
185
|
|
|
if ($user->prefs->privilege(S_ADMIN) || $user->prefs->privilege(S_DEV)) { |
186
|
|
|
return; |
187
|
|
|
} |
188
|
|
|
error_page("Access denied"); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
// if project hasn't specified a policy in project.inc, |
192
|
|
|
// and no .htaccess, don't allow access |
193
|
|
|
// |
194
|
|
|
if (!function_exists('auth_ops')) { |
195
|
|
|
function auth_ops() { |
196
|
|
|
if (!file_exists(".htaccess")) { |
197
|
|
|
error_page(" |
198
|
|
|
You must protect the admin interface |
199
|
|
|
with either a .htaccess file or an auto_ops() function. |
200
|
|
|
<p> |
201
|
|
|
<a href=https://github.com/BOINC/boinc/wiki/HtmlOps>See how here</a>" |
202
|
|
|
); |
203
|
|
|
} |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
|
207
|
|
|
function admin_error_page($msg) { |
208
|
|
|
admin_page_head("Unable to handle request"); |
209
|
|
|
echo $msg; |
210
|
|
|
admin_page_tail(); |
211
|
|
|
exit; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
// given a list of app versions, |
215
|
|
|
// return a list of the current, non-deprecated ones |
216
|
|
|
// |
217
|
|
|
function current_versions($avs) { |
218
|
|
|
foreach($avs as $av) { |
219
|
|
|
foreach ($avs as $av2) { |
220
|
|
|
if ($av->id == $av2->id) continue; |
221
|
|
|
if ($av->platformid == $av2->platformid && $av->plan_class == $av2->plan_class && $av->version_num > $av2->version_num) { |
222
|
|
|
$av2->deprecated = 1; |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
$x = array(); |
227
|
|
|
foreach($avs as $av) { |
228
|
|
|
if (!$av->deprecated) $x[] = $av; |
229
|
|
|
} |
230
|
|
|
return $x; |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
// cancel WUs with IDs in a given range. |
234
|
|
|
// This means: |
235
|
|
|
// |
236
|
|
|
// - for any results w/ server state UNSENT, set server state to OVER |
237
|
|
|
// - set the CANCELLED bit in workunit.error_mask |
238
|
|
|
// |
239
|
|
|
function cancel_wus($wuid1, $wuid2) { |
240
|
|
|
$retval = BoincResult::update_aux("server_state=5, outcome=5 where server_state=2 and $wuid1<=workunitid and workunitid<=$wuid2"); |
241
|
|
|
if (!$retval) { |
242
|
|
|
error_page("Result update failed"); |
243
|
|
|
} |
244
|
|
|
$retval = BoincWorkunit::update_aux("error_mask=error_mask|16 where $wuid1<=id and id<=$wuid2"); |
245
|
|
|
if (!$retval) { |
246
|
|
|
error_page("Workunit update failed"); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
// trigger the transitioner (it will set file_delete_state) |
250
|
|
|
|
251
|
|
|
$now = time(); |
252
|
|
|
$retval = BoincWorkunit::update_aux("transition_time=$now where $wuid1<=id and id<=$wuid2"); |
253
|
|
|
return 0; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
// like above, but if a workunit has a result that's already sent, |
257
|
|
|
// don't cancel the workunit |
258
|
|
|
// |
259
|
|
|
function cancel_wus_if_unsent($id1, $id2) { |
260
|
|
|
$wus = BoincWorkunit::enum("id >= $id1 and id <= $id2"); |
261
|
|
|
foreach ($wus as $wu) { |
262
|
|
|
$results = BoincResult::enum("workunitid=$wu->id and server_state > 2"); |
263
|
|
|
if (count($results)) continue; |
264
|
|
|
$retval = BoincResult::update_aux("server_state=5, outcome=5 where workunitid=$wu->id"); |
265
|
|
|
if (!$retval) { |
266
|
|
|
error_page("result update failed"); |
267
|
|
|
} |
268
|
|
|
if (!$wu->update("error_mask=error_mask|16")) { |
269
|
|
|
error_page("WU update failed"); |
270
|
|
|
} |
271
|
|
|
} |
272
|
|
|
return 0; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
function app_version_desc($avid) { |
276
|
|
|
switch ($avid) { |
277
|
|
|
case ANON_PLATFORM_UNKNOWN: |
278
|
|
|
return "Anonymous platform: unknown type"; |
279
|
|
|
case ANON_PLATFORM_CPU: |
280
|
|
|
return "Anonymous platform: CPU"; |
281
|
|
|
case ANON_PLATFORM_NVIDIA: |
282
|
|
|
return "Anonymous platform: NVIDIA GPU"; |
283
|
|
|
case ANON_PLATFORM_ATI: |
284
|
|
|
return "Anonymous platform: AMD GPU"; |
285
|
|
|
case ANON_PLATFORM_INTEL_GPU: |
286
|
|
|
return "Anonymous platform: Intel GPU"; |
287
|
|
|
case ANON_PLATFORM_APPLE_GPU: |
288
|
|
|
return "Anonymous platform: Apple GPU"; |
289
|
|
|
} |
290
|
|
|
if ($avid <= 0) { |
291
|
|
|
return "unknown: $avid"; |
292
|
|
|
} |
293
|
|
|
$av = BoincAppVersion::lookup_id($avid); |
294
|
|
|
if ($av) { |
295
|
|
|
$p = BoincPlatform::lookup_id($av->platformid); |
296
|
|
|
if ($p) { |
297
|
|
|
return sprintf("%.2f", $av->version_num/100)." $p->name [$av->plan_class]"; |
298
|
|
|
} else { |
299
|
|
|
return sprintf("%.2f", $av->version_num/100)." MISSING PLATFORM $av->platformid [$av->plan_class]"; |
300
|
|
|
} |
301
|
|
|
} else { |
302
|
|
|
return "App version missing ($avid)"; |
303
|
|
|
} |
304
|
|
|
} |
305
|
|
|
|
306
|
|
|
////// badge-related stuff |
307
|
|
|
|
308
|
|
|
function get_badge($name, $title, $image_url) { |
309
|
|
|
$b = BoincBadge::lookup("name='$name'"); |
310
|
|
|
if ($b) return $b; |
311
|
|
|
$now = time(); |
312
|
|
|
$id = BoincBadge::insert("(create_time, type, name, title, description, image_url, level, tags, sql_rule) values ($now, 0, '$name', '$title', '', 'img/$image_url', '', '', '')"); |
313
|
|
|
$b = BoincBadge::lookup_id($id); |
314
|
|
|
if ($b) return $b; |
315
|
|
|
die("can't create badge $name\n"); |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
function assign_badge($is_user, $item, $badge) { |
319
|
|
|
$now = time(); |
320
|
|
|
if ($is_user) { |
321
|
|
|
$bbu = BoincBadgeUser::lookup("user_id=$item->id and badge_id=$badge->id"); |
322
|
|
|
if ($bbu) { |
323
|
|
|
$bbu->update("reassign_time=$now where user_id=$item->id and badge_id=$badge->id"); |
324
|
|
|
} else { |
325
|
|
|
BoincBadgeUser::insert("(create_time, user_id, badge_id, reassign_time) values ($now, $item->id, $badge->id, $now)"); |
326
|
|
|
} |
327
|
|
|
} else { |
328
|
|
|
$bbt = BoincBadgeTeam::lookup("team_id=$item->id and badge_id=$badge->id"); |
329
|
|
|
if ($bbt) { |
330
|
|
|
$bbt->update("reassign_time=$now where team_id=$item->id and badge_id=$badge->id"); |
331
|
|
|
} else { |
332
|
|
|
BoincBadgeTeam::insert("(create_time, team_id, badge_id, reassign_time) values ($now, $item->id, $badge->id, $now)"); |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
// unassign all badges except the given one |
338
|
|
|
// |
339
|
|
|
function unassign_badges($is_user, $item, $badges, $k) { |
340
|
|
|
$list = null; |
341
|
|
|
for ($i=0; $i<count($badges); $i++) { |
|
|
|
|
342
|
|
|
if ($i == $k) continue; |
343
|
|
|
$badge = $badges[$i]; |
344
|
|
|
if ($list) { |
345
|
|
|
$list .= ",$badge->id"; |
346
|
|
|
} else { |
347
|
|
|
$list = "$badge->id"; |
348
|
|
|
} |
349
|
|
|
} |
350
|
|
|
if ($is_user) { |
351
|
|
|
BoincBadgeUser::delete("user_id=$item->id and badge_id in ($list)"); |
352
|
|
|
} else { |
353
|
|
|
BoincBadgeTeam::delete("team_id=$item->id and badge_id in ($list)"); |
354
|
|
|
} |
355
|
|
|
} |
356
|
|
|
|
357
|
|
|
////// end badge-related stuff |
358
|
|
|
|
359
|
|
|
function running_from_web_server() { |
360
|
|
|
return array_key_exists("SERVER_PORT", $_SERVER); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
if (isset($cli_only)) { |
364
|
|
|
if (running_from_web_server()) { |
365
|
|
|
die("This script is intended to be run from the command line, |
366
|
|
|
not from the web server." |
367
|
|
|
); |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
if (!isset($skip_auth_ops) && array_key_exists("SERVER_PORT", $_SERVER)) { |
372
|
|
|
auth_ops(); |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
// returns true when this is a readonly ops section |
376
|
|
|
// currently a dummy because this needs to be ported from Einstein@home |
377
|
|
|
// |
378
|
|
|
function in_rops() { |
379
|
|
|
return false; |
380
|
|
|
} |
381
|
|
|
|
382
|
|
|
function cancel_wus_where($clause) { |
383
|
|
|
$q1 = "CREATE TEMPORARY TABLE tmp SELECT id FROM workunit WHERE $clause;"; |
384
|
|
|
$q2 = "UPDATE result r INNER JOIN tmp t on r.workunitid=t.id SET server_state=5, outcome=5 WHERE server_state=2;"; |
385
|
|
|
$q3 = "UPDATE workunit w INNER JOIN tmp t on w.id=t.id SET error_mask=error_mask|16, transition_time=0;"; |
386
|
|
|
$q4 = "DROP TABLE tmp;"; |
387
|
|
|
|
388
|
|
|
$db = BoincDb::get(); |
389
|
|
|
|
390
|
|
|
if (!$db->do_query($q1)) { |
391
|
|
|
echo "MySQL command '$q1' failed:<br/>unable to create temporary WU id table.<br>\n"; |
392
|
|
|
return 1; |
393
|
|
|
} else if (!$db->do_query($q2)) { |
394
|
|
|
echo "MySQL command '$q2' failed:<br/>unable to cancel unsent results.<br>\n"; |
395
|
|
|
$db->do_query($q4); |
396
|
|
|
return 2; |
397
|
|
|
} else if (!$db->do_query($q3)) { |
398
|
|
|
echo "MySQL command '$q3' failed:<br/>unable to cancel workunits and trigger transitioner.<br>\n"; |
399
|
|
|
$db->do_query($q4); |
400
|
|
|
return 3; |
401
|
|
|
} |
402
|
|
|
$db->do_query($q4); |
403
|
|
|
echo "Successfully canceled WUs WHERE '$clause'<br>\n"; |
404
|
|
|
return 0; |
405
|
|
|
} |
406
|
|
|
|
407
|
|
|
?> |
408
|
|
|
|
If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration: