Issues (1953)

Branch: master

html/user/buda.php (15 issues)

1
<?php
2
// This file is part of BOINC.
3
// https://boinc.berkeley.edu
4
// Copyright (C) 2024 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
// interface for:
20
//      - viewing details of BUDA science apps and variants
21
//      - managing these if user has permission
22
//
23
// in the following, 'app' means BUDA science app
24
// and 'variant' means a variant of one of these (e.g. CPU, GPU)
25
26
require_once('../inc/util.inc');
27
require_once('../inc/sandbox.inc');
28
require_once('../inc/keywords.inc');
29
require_once('../inc/submit_util.inc');
30
require_once('../inc/buda.inc');
31
32
display_errors();
33
34
$buda_root = "../../buda_apps";
35
36
// show list of BUDA apps and variants,
37
// w/ buttons for adding and deleting
38
//
39
function app_list($notice=null) {
40
    global $buda_root;
41
    if (!is_dir($buda_root)) {
42
        mkdir($buda_root);
43
    }
44
    page_head('Manage BUDA apps');
45
    if ($notice) {
46
        echo "$notice <p>\n";
47
    }
48
    text_start();
49
    echo "
50
        <p>BUDA lets you submit Docker jobs using a web interface.
51
        <a href=https://github.com/BOINC/boinc/wiki/BUDA-overview>Learn more</a>.
52
        <p>
53
        <h3>BUDA science apps</h3>
54
    ";
55
56
    $apps = get_buda_apps();
57
    foreach ($apps as $app) {
58
        show_app($app);
59
    }
60
    echo '<hr>';
61
    show_button_small('buda.php?action=app_form', 'Add science app');
62
    text_end();
63
    page_tail();
64
}
65
66
function show_app($app_dir) {
67
    global $buda_root;
68
    $desc = null;
69
    $desc_path = "$buda_root/$app_dir/desc.json";
70
    $desc = json_decode(file_get_contents($desc_path));
71
    echo '<hr>';
72
    echo sprintf('<h3>%s</h3><p>', $desc->long_name);
73
    show_button_small(
74
        sprintf('buda.php?action=app_details&name=%s', $desc->name),
75
        'App details'
76
    );
77
    $var_dirs = get_buda_variants($app_dir);
78
    if ($var_dirs) {
79
        echo "<p>Variants:<ul>";
80
        foreach ($var_dirs as $var_dir) {
81
            $var_desc = get_buda_var_desc($app_dir, $var_dir);
82
            echo sprintf(
83
                '<li><a href=buda.php?action=variant_view&app=%s&variant=%s>%s</a>',
84
                $app_dir, $var_dir, variant_name($var_desc)
85
            );
86
        }
87
        echo '</ul>';
88
    } else {
89
        echo '<p>No variants';
90
    }
91
    echo "<p>";
92
}
93
94
function file_row($app, $variant, $dir, $f) {
95
    [$md5, $size] = parse_info_file("$dir/.md5/$f");
96
    table_row(
97
        "<a href=buda.php?action=view_file&app=$app&variant=$variant&fname=$f>$f</a>",
98
        $size,
99
        $md5
100
    );
101
}
102
103
function variant_view() {
104
    global $buda_root, $manage_access;
105
    $app = get_str('app');
106
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
107
    $variant = get_str('variant');
108
    if (!is_valid_filename($variant)) die('bad arg');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
109
    page_head("BUDA variant");
110
    $dir = "$buda_root/$app/$variant";
111
    $desc = json_decode(file_get_contents("$dir/variant.json"));
112
    start_table('table-striped');
113
    row2("BUDA App", $app);
114
    row2("Variant name", $variant);
115
    row2("CPU type", $desc->cpu_type);
116
    row2("Plan class", $desc->plan_class);
117
    end_table();
118
    echo "<h3>App files</h3>";
119
    start_table();
120
    table_header('Dockerfile', 'size', 'md5');
121
    file_row($app, $variant, $dir, $desc->dockerfile);
122
    table_header('App files', '', '');
123
    foreach ($desc->app_files as $f) {
124
        file_row($app, $variant, $dir, $f);
125
    }
126
    table_header('Auto-generated files', '', '');
127
    file_row($app, $variant, $dir, 'variant.json');
128
    end_table();
129
    echo '<hr>';
130
131
    if ($manage_access) {
132
        echo '<p>';
133
        show_button_small(
134
            "buda.php?action=variant_form&app=$app&variant=$variant",
135
            'Edit variant'
136
        );
137
        echo '<p>';
138
        show_button(
139
            "buda.php?action=variant_delete&app=$app&variant=$variant",
140
            'Delete variant',
141
            null,
142
            'btn btn-xs btn-warning'
143
        );
144
    }
145
    page_tail();
146
}
147
148
// form for creating an app variant or editing an existing one
149
//
150
function variant_form($user) {
151
    $sbitems = sandbox_select_items($user);
152
    $app = get_str('app');
153
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
154
155
    // name of variant directory, if we're editing
156
    $variant = get_str('variant', true);
157
158
    if ($variant) {
159
        global $buda_root;
160
        $variant_dir = "$buda_root/$app/$variant";
161
        $variant_desc = json_decode(
162
            file_get_contents("$variant_dir/variant.json")
163
        );
164
        if (!$variant_desc) error_page('no such variant');
165
        page_head_select2("Edit variant $variant of BUDA app $app");
166
    } else {
167
        $variant_desc = new StdClass;
168
        $variant_desc->cpu_type = 'intel';
169
        $variant_desc->plan_class = '';
170
        $variant_desc->dockerfile = '';
171
        $variant_desc->app_files = [];
172
        page_head_select2("Create a variant of BUDA app $app");
173
    }
174
    echo "
175
        Details are <a href=https://github.com/BOINC/boinc/wiki/BUDA-job-submission#adding-a-variant>here</a>.
176
    ";
177
    $sb = '<br><small>From your <a href=sandbox.php>file sandbox</a></small>';
178
    $pc = '<br><small>Specify
179
    <a href=https://github.com/BOINC/boinc/wiki/AppPlan>GPU and other host requirements</a>.<br>Leave blank if none.</small>';
180
    form_start('buda.php');
181
    form_input_hidden('app', $app);
182
    form_input_hidden('action', 'variant_action');
183
    if ($variant) {
184
        // can't change CPU type of existing variant
185
        form_input_hidden('variant', $variant);
186
        form_input_hidden('edit', 'true');
187
        $x = explode('_', $variant);
188
        $cpu_type = $x[0];
189
        form_input_hidden('cpu_type', $cpu_type);
190
    } else {
191
        form_radio_buttons(
192
            'CPU type', 'cpu_type',
193
            [
194
                ['intel', 'Intel'],
195
                ['arm', 'ARM']
0 ignored issues
show
There should be a trailing comma after the last value of an array declaration.
Loading history...
196
            ],
197
            $variant_desc->cpu_type
198
        );
199
    }
200
    form_input_text("Plan class$pc", 'plan_class', $variant_desc->plan_class);
201
    form_select("Dockerfile$sb", 'dockerfile', $sbitems, $variant_desc->dockerfile);
202
    form_select2_multi("Application files$sb", 'app_files', $sbitems, $variant_desc->app_files);
203
    form_submit('OK');
204
    form_end();
205
    page_tail();
206
}
207
208
function buda_file_phys_name($app, $variant, $md5) {
209
    return sprintf('buda_%s_%s_%s', $app, $variant, $md5);
210
}
211
212
// copy file from sandbox to variant dir, and stage to download hier
213
// return physical name, md5, and size
214
//
215
function copy_and_stage_file($user, $fname, $variant_dir, $app, $variant) {
216
    copy_sandbox_file($user, $fname, $variant_dir);
217
    [$md5, $size] = parse_info_file("$variant_dir/.md5/$fname");
218
    $phys_name = buda_file_phys_name($app, $variant, $md5);
219
    stage_file_aux("$variant_dir/$fname", $md5, $size, $phys_name);
220
    return [$phys_name, $md5, $size];
221
}
222
223
// create templates and put them in app dir
224
//
225
function create_templates($app, $desc, $dir) {
226
    // input template
227
    //
228
    $x = "<input_template>\n";
229
    $ninfiles = count($desc->input_file_names);
230
    for ($i=0; $i<$ninfiles; $i++) {
231
        $x .= "   <file_info>\n      <no_delete/>\n   </file_info>\n";
232
    }
233
    $x .= "   <workunit>\n";
234
    foreach ($desc->input_file_names as $fname) {
235
        $x .= file_ref_in($fname);
236
    }
237
238
    // replication params
239
    //
240
    $x .= sprintf("      <target_nresults>%d</target_nresults>\n",
241
        $desc->min_nsuccess
242
    );
243
    $x .= sprintf("      <min_quorum>%d</min_quorum>\n",
244
        $desc->min_nsuccess
245
    );
246
    $x .= sprintf("      <max_total_results>%d</max_total_results>\n",
247
        $desc->max_total
248
    );
249
250
    $x .= sprintf("      <max_delay>%f</max_delay>\n",
251
        $desc->max_delay_days * 86400.
252
    );
253
254
    $x .= "      <buda_app_name>$app</buda_app_name>\n";
255
    $x .= "   </workunit>\n</input_template>\n";
256
    file_put_contents("$dir/template_in", $x);
257
258
    // output template
259
    //
260
    $x = "<output_template>\n";
261
    $i = 0;
262
    foreach ($desc->output_file_names as $fname) {
263
        $x .= file_info_out($i++);
264
    }
265
    $x .= "   <result>\n";
266
    $i = 0;
267
    foreach ($desc->output_file_names as $fname) {
268
        $x .= file_ref_out($i++, $fname);
269
    }
270
    $x .= "   </result>\n</output_template>\n";
271
    file_put_contents("$dir/template_out", $x);
272
}
273
274
// return <file_info> and <file_ref> elements for given file
275
//
276
function file_xml_elements($log_name, $phys_name, $md5, $nbytes) {
277
    static $download_dir, $download_url, $fanout;
278
    if ($download_dir == null) {
279
        $download_dir = parse_element(get_config(), "<download_dir>");
280
        $download_url = parse_element(get_config(), "<download_url>");
281
        $fanout = (int)(parse_element(get_config(), "<uldl_dir_fanout>"));
282
    }
283
    $file_info = sprintf(
284
"<file_info>
285
    <sticky/>
286
    <no_delete/>
287
    <executable/>
288
    <name>%s</name>
289
    <url>%s/%s/%s</url>
290
    <md5_cksum>%s</md5_cksum>
291
    <nbytes>%d</nbytes>
292
</file_info>
293
",
294
        $phys_name,
295
        $download_url, filename_hash($phys_name, $fanout), $phys_name,
296
        $md5,
297
        $nbytes
298
    );
299
    $file_ref = sprintf(
300
"<file_ref>
301
    <file_name>%s</file_name>
302
    <open_name>%s</open_name>
303
    <copy_file/>
304
</file_ref>
305
",
306
        $phys_name,
307
        $log_name
308
    );
309
    return [$file_info, $file_ref];
310
}
311
312
// create variant
313
//
314
function variant_action($user) {
315
    global $buda_root;
316
    $cpu_type = get_str('cpu_type');
317
    $plan_class = get_str('plan_class');
318
    $variant = get_str('variant', true);
319
    if ($variant) {
320
        $creating = false;
321
    } else {
322
        $creating = true;
323
    }
324
    $variant = sprintf('%s_%s', $cpu_type, $plan_class?$plan_class:'cpu');
325
    if (!is_valid_filename($variant)) {
326
        error_page(filename_rules());
327
    }
328
    $app = get_str('app');
329
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
330
331
    $dockerfile = get_str('dockerfile');
332
    if (!is_valid_filename($dockerfile)) {
333
        error_page("Invalid dockerfile name: ".filename_rules());
334
    }
335
    $app_files = get_array('app_files');
336
    foreach ($app_files as $fname) {
337
        if (!is_valid_filename($fname)) {
338
            error_page("Invalid app file name: ".filename_rules());
339
        }
340
    }
341
342
    $dir = "$buda_root/$app/$variant";
343
    if ($creating) {
344
        if (file_exists($dir)) {
345
            error_page("Variant '$variant' already exists.");
346
        }
347
    } else {
348
        system("rm -r $dir");
349
    }
350
    mkdir($dir);
351
    mkdir("$dir/.md5");
352
353
    // collect variant params into a struct
354
    //
355
    $desc = new StdClass;
356
    $desc->dockerfile = $dockerfile;
357
    $desc->app_files = $app_files;
358
    $desc->cpu_type = $cpu_type;
359
    $desc->plan_class = $plan_class;
360
    $desc->file_infos = '';
361
    $desc->file_refs = '';
362
363
    // copy dockerfile and app files from sandbox to variant dir,
364
    // and stage them to download dir
365
    //
366
    [$pname, $md5, $nbytes] = copy_and_stage_file(
367
        $user, $dockerfile, $dir, $app, $variant
368
    );
369
    $desc->dockerfile_phys = $pname;
370
    $desc->app_files_phys = [];
371
    [$file_info, $file_ref] = file_xml_elements(
372
        'Dockerfile', $pname, $md5, $nbytes
373
    );
374
    $desc->file_infos .= $file_info;
375
    $desc->file_refs .= $file_ref;
376
377
    foreach ($app_files as $fname) {
378
        [$pname, $md5, $nbytes] = copy_and_stage_file(
379
            $user, $fname, $dir, $app, $variant
380
        );
381
        $desc->app_files_phys[] = $pname;
382
        [$file_info, $file_ref] = file_xml_elements(
383
            $fname, $pname, $md5, $nbytes
384
        );
385
        $desc->file_infos .= $file_info;
386
        $desc->file_refs .= $file_ref;
387
    }
388
389
    // write variant params to a JSON file
390
    //
391
    file_put_contents(
392
        "$dir/variant.json",
393
        json_encode($desc, JSON_PRETTY_PRINT)
394
    );
395
396
    // Note: we don't currently allow indirect file access.
397
    // If we did, we'd need to create job.toml to mount project dir
398
399
    app_list("Variant $variant added for app $app.");
400
}
401
402
function variant_delete() {
403
    global $buda_root;
404
    $app = get_str('app');
405
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
406
    $variant = get_str('variant');
407
    if (!is_valid_filename($variant)) die('bad arg');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
408
    $confirmed = get_str('confirmed', true);
409
    if ($confirmed) {
410
        $dir = "$buda_root/$app/$variant";
411
        if (!file_exists($dir)) error_page('no such variant');
412
        // delete staged files
413
        //
414
        foreach (scandir("$dir/.md5") as $fname) {
415
            if ($fname[0] == '.') continue;
416
            [$md5, $size] = parse_info_file("$dir/.md5/$fname");
417
            $phys_name = buda_file_phys_name($app, $variant, $md5);
418
            $phys_path = download_hier_path($phys_name);
419
            unlink($phys_path);
420
            unlink("$phys_path.md5");
421
        }
422
        system("rm -r $buda_root/$app/$variant", $ret);
423
        if ($ret) {
424
            error_page("delete failed");
425
        }
426
        $notice = "Variant $variant of app $app removed.";
427
        app_list($notice);
428
    } else {
429
        page_head("Confirm");
430
        echo "<p>Are you sure you want to delete variant $variant of BUDA app $app?  <p>";
431
        show_button(
432
            "buda.php?action=variant_delete&app=$app&variant=$variant&confirmed=yes",
433
            "Yes"
434
        );
435
        page_tail();
436
    }
437
}
438
439
function app_delete() {
440
    global $buda_root;
441
    $app = get_str('app');
442
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
443
    $confirmed = get_str('confirmed', true);
444
    if ($confirmed) {
445
        $dir = "$buda_root/$app";
446
        if (!file_exists($dir)) error_page('no such app');
447
        $vars = get_buda_variants($app);
448
        if ($vars) {
449
            error_page("You must delete all variants first.");
450
        }
451
        system("rm $buda_root/$app/desc.json", $ret);
452
        system("rmdir $buda_root/$app", $ret);
453
        if ($ret) {
454
            error_page('delete failed');
455
        }
456
        $notice = "App $app removed.";
457
        app_list($notice);
458
    } else {
459
        page_head('Confirm');
460
        echo "<p>Are you sure you want to delete BUDA science app $app?  <p>";
461
        show_button(
462
            "buda.php?action=app_delete&app=$app&confirmed=yes",
463
            "Yes"
464
        );
465
        page_tail();
466
    }
467
}
468
469
function app_form($desc=null) {
470
    page_head_select2($desc?"Edit BUDA app $desc->name":'Create BUDA app');
471
    form_start('buda.php');
472
    form_input_hidden('action', 'app_action');
473
    if ($desc) {
474
        form_input_hidden('edit_name', $desc->name);
475
        form_input_hidden('user_id', $desc->user_id);
476
        form_input_hidden('create_time', $desc->create_time);
477
    } else {
478
        $desc = new StdClass;
479
        $desc->long_name = null;
480
        $desc->input_file_names = [];
481
        $desc->output_file_names = [];
482
        $desc->min_nsuccess = 1;
483
        $desc->max_total = 2;
484
        $desc->max_delay_days = 7;
485
        $desc->description = null;
486
        $desc->sci_kw = null;
487
        $desc->url = null;
488
        form_input_text('Internal name<br><small>No spaces</small>', 'name');
489
    }
490
    form_input_text('User-visible name', 'long_name', $desc->long_name);
491
    form_input_text(
492
        'Input file names<br><small>Space-separated</small>',
493
        'input_file_names',
494
        implode(' ', $desc->input_file_names)
495
    );
496
    form_input_text(
497
        'Output file names<br><small>Space-separated</small>',
498
        'output_file_names',
499
        implode(' ', $desc->output_file_names)
500
    );
501
    form_input_text(
502
        'Run at most this many total instances of each job',
503
        'max_total',
504
        $desc->max_total
505
    );
506
    form_input_text(
507
        'Get this many successful instances of each job
508
            <br><small>(subject to the above limit)</small>
509
        ',
510
        'min_nsuccess',
511
        $desc->min_nsuccess
512
    );
513
    form_input_text(
514
        'Max job turnaround time, days',
515
        'max_delay_days',
516
        $desc->max_delay_days
517
    );
518
    form_input_textarea(
519
        'Description<br><small>... of what the app does and of the research goals</small>',
520
        'description',
521
        $desc->description
522
    );
523
    form_select2_multi('Science keywords',
524
        'sci_kw',
525
        keyword_select_options(KW_CATEGORY_SCIENCE),
526
        $desc->sci_kw
527
    );
528
    // don't include location keywords;
529
    // various people may submit jobs to this app
530
    form_submit('OK');
531
    form_end();
532
    page_tail();
533
}
534
535
function app_action($user) {
536
    global $buda_root;
537
    $edit_name = get_str('edit_name', true);
538
    $desc = new StdClass;
539
    if ($edit_name) {
540
        // editing existing app
541
        $dir = "$buda_root/$edit_name";
542
        $app_name = $edit_name;
543
        $desc->user_id = get_int('user_id');
544
        $desc->create_time = get_int('create_time');
545
    } else {
546
        // creating new app
547
        $app_name = get_str('name');
548
        if (!is_valid_filename($app_name)) {
549
            error_page(filename_rules());
550
        }
551
        $dir = "$buda_root/$app_name";
552
        if (file_exists($dir)) {
553
            error_page("App $app_name already exists.");
554
        }
555
        mkdir($dir);
556
        $desc->user_id = $user->id;
557
        $desc->create_time = time();
558
    }
559
    $desc->name = $app_name;
560
    $min_nsuccess = get_int('min_nsuccess');
561
    if ($min_nsuccess <= 0) {
562
        error_page('Must specify a positive number of successful instances.');
563
    }
564
    $max_total = get_int('max_total');
565
    if ($max_total <= 0) {
566
        error_page('Must specify a positive max number of instances.');
567
    }
568
    if ($min_nsuccess > $max_total) {
569
        error_page('Target # of successful instances must be <= max total');
570
    }
571
    $max_delay_days = get_str('max_delay_days');
572
    if (!is_numeric($max_delay_days)) {
573
        error_page('Must specify max delay');
574
    }
575
    $max_delay_days = floatval($max_delay_days);
576
    if ($max_delay_days <= 0) {
577
        error_page('Must specify positive max delay');
578
    }
579
580
    $input_file_names = get_str('input_file_names', true);
581
    if ($input_file_names) {
582
        $input_file_names = explode(' ', $input_file_names);
583
        foreach ($input_file_names as $fname) {
584
            if (!is_valid_filename($fname)) {
585
                error_page("Invalid input file name: ".filename_rules());
586
            }
587
        }
588
    } else {
589
        $input_file_names = [];
590
    }
591
    $output_file_names = get_str('output_file_names', true);
592
    if ($output_file_names) {
593
        $output_file_names = explode(' ', $output_file_names);
594
        foreach ($output_file_names as $fname) {
595
            if (!is_valid_filename($fname)) {
596
                error_page("Invalid output file name: ".filename_rules());
597
            }
598
        }
599
    } else {
600
        $output_file_names = [];
601
    }
602
    $desc->long_name = get_str('long_name');
603
    $desc->input_file_names = $input_file_names;
604
    $desc->output_file_names = $output_file_names;
605
    $desc->min_nsuccess = $min_nsuccess;
606
    $desc->max_total = $max_total;
607
    $desc->max_delay_days = $max_delay_days;
608
    $desc->description = get_str('description');
609
    $desc->sci_kw = array_map('intval', get_array('sci_kw'));
610
    file_put_contents("$dir/desc.json", json_encode($desc, JSON_PRETTY_PRINT));
611
612
    create_templates($app_name, $desc, $dir);
613
614
    header("Location: buda.php");
615
}
616
617
function view_file() {
618
    global $buda_root;
619
    $app = get_str('app');
620
    if (!is_valid_filename($app)) die('bad arg');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
621
    $variant = get_str('variant');
622
    if (!is_valid_filename($variant)) die('bad arg');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
623
    $fname = get_str('fname');
624
    if (!is_valid_filename($fname)) die('bad arg');
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
625
    echo "<pre>\n";
626
    $x = file_get_contents("$buda_root/$app/$variant/$fname");
627
    echo htmlspecialchars($x);
628
    echo "</pre>\n";
629
}
630
631
function handle_app_edit() {
632
    global $buda_root;
633
    $name = get_str('name');
634
    app_form(get_buda_app_desc($name));
635
}
636
637
function app_details() {
638
    global $buda_root, $manage_access;
639
    $name = get_str('name');
640
    $desc = get_buda_app_desc($name);
641
    if (!$desc) error_page("no desc file $path");
642
    page_head("BUDA app: $desc->long_name");
643
    start_table('table-striped');
644
    row2('Internal name', $desc->name);
645
    $user = BoincUser::lookup_id($desc->user_id);
646
    row2('Creator',
647
        sprintf('<a href=show_user.php?userid=%d>%s</a>',
648
            $user->id,
649
            $user->name
650
        )
651
    );
652
    row2('Created', date_str($desc->create_time));
653
    row2('Description', $desc->description);
654
    row2('Science keywords', kw_array_to_str($desc->sci_kw));
655
    row2(
656
        'Input filenames:',
657
        implode(',', $desc->input_file_names)
658
    );
659
    row2(
660
        'Output filenames:',
661
        implode(',', $desc->output_file_names)
662
    );
663
    if (!empty($desc->max_total)) {
664
        row2('Max total instances per job:', $desc->max_total);
665
    } else {
666
        row2('Max total instances per job:', '1');
667
    }
668
    if (!empty($desc->min_nsuccess)) {
669
        row2('Target successful instances per job:', $desc->min_nsuccess);
670
    } else {
671
        row2('Target successful instances per job:', '1');
672
    }
673
    if (!empty($desc->max_delay_days)) {
674
        row2('Max job turnaround time, days:', $desc->max_delay_days);
675
    } else {
676
        row2('Max job turnaround time, days:', '7');
677
    }
678
    if ($manage_access) {
679
        row2('',
680
            button_text_small(
681
                sprintf('buda.php?action=%s&name=%s', 'app_edit', $desc->name),
682
                'Edit app info'
683
            )
684
        );
685
    }
686
    $vars = get_buda_variants($name);
687
    if ($vars) {
688
        $x = [];
689
        foreach ($vars as $var) {
690
            $x[] = sprintf('<a href=buda.php?action=variant_view&app=%s&variant=%s>%s</a>',
691
                $name, $var, $var
692
            );
693
        }
694
        row2('Variants', implode('<p>', $x));
695
        if ($manage_access) {
696
            row2('',
697
                button_text_small(
698
                    "buda.php?action=variant_form&app=$name",
699
                    'Add variant'
700
                )
701
            );
702
        }
703
    } else if ($manage_access) {
704
        row2('Variants',
705
            button_text_small(
706
                "buda.php?action=variant_form&app=$name",
707
                'Add variant'
708
            )
709
        );
710
        row2('',
711
            button_text(
712
                "buda.php?action=app_delete&app=$name",
713
                "Delete app",
714
                null,
715
                'btn btn-xs btn-warning'
716
            )
717
        );
718
    }
719
    end_table();
720
    page_tail();
721
}
722
723
// Users with manage access to BUDA can add/delete apps and variants.
724
// Others can just view.
725
// Might want to refine this at some point
726
727
$user = get_logged_in_user();
0 ignored issues
show
Are you sure the assignment to $user is correct as get_logged_in_user() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
728
$buda_app = BoincApp::lookup("name='buda'");
729
if (!$buda_app) error_page('no buda app');
730
$manage_access = has_manage_access($user, $buda_app->id);
731
732
$action = get_str('action', true);
733
switch ($action) {
734
case 'app_edit':
735
    if (!$manage_access) error_page('no access');
736
    handle_app_edit(); break;
737
case 'app_form':
738
    if (!$manage_access) error_page('no access');
739
    app_form(); break;
740
case 'app_action':
741
    if (!$manage_access) error_page('no access');
742
    app_action($user); break;
743
case 'app_details':
744
    app_details(); break;
745
case 'app_delete':
746
    if (!$manage_access) error_page('no access');
747
    app_delete(); break;
748
case 'variant_view':
749
    variant_view($user); break;
0 ignored issues
show
The call to variant_view() has too many arguments starting with $user. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

749
    /** @scrutinizer ignore-call */ 
750
    variant_view($user); break;

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
750
case 'variant_form':
751
    if (!$manage_access) error_page('no access');
752
    variant_form($user); break;
753
case 'variant_action':
754
    if (!$manage_access) error_page('no access');
755
    variant_action($user);
756
    break;
757
case 'variant_delete':
758
    if (!$manage_access) error_page('no access');
759
    variant_delete();
760
    break;
761
case 'view_file':
762
    view_file(); break;
763
case null:
764
    app_list(); break;
765
default:
0 ignored issues
show
DEFAULT keyword must be indented 4 spaces from SWITCH keyword
Loading history...
DEFAULT case must have a breaking statement
Loading history...
766
    error_page("unknown action");
767
}
768
769
?>
770