github.com/swiftstack/proxyfs@v0.0.0-20201223034610-5434d919416e/httpserver/html_templates.go (about) 1 package httpserver 2 3 // To use: fmt.Sprintf(indexDotHTMLTemplate, proxyfsVersion, globals.ipAddrTCPPort) 4 const indexDotHTMLTemplate string = `<!doctype html> 5 <html lang="en"> 6 <head> 7 <meta charset="utf-8"> 8 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 9 <link rel="stylesheet" href="/bootstrap.min.css"> 10 <link rel="stylesheet" href="/styles.css"> 11 <title>ProxyFS Management - %[2]v</title> 12 </head> 13 <body> 14 <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> 15 <a class="navbar-brand" href="#">%[2]v</a> 16 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> 17 <span class="navbar-toggler-icon"></span> 18 </button> 19 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 20 <ul class="navbar-nav mr-auto"> 21 <li class="nav-item active"> 22 <a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a> 23 </li> 24 <li class="nav-item"> 25 <a class="nav-link" href="/config">Config</a> 26 </li> 27 <li class="nav-item"> 28 <a class="nav-link" href="/metrics">StatsD/Prometheus</a> 29 </li> 30 <li class="nav-item"> 31 <a class="nav-link" href="/trigger">Triggers</a> 32 </li> 33 <li class="nav-item"> 34 <a class="nav-link" href="/volume">Volumes</a> 35 </li> 36 </ul> 37 <span class="navbar-text">Version %[1]v</span> 38 </div> 39 </nav> 40 <div class="container"> 41 <nav aria-label="breadcrumb"> 42 <ol class="breadcrumb"> 43 <li class="breadcrumb-item active" aria-current="page">Home</li> 44 </ol> 45 </nav> 46 <h1 class="display-4"> 47 ProxyFS Management 48 </h1> 49 <div class="card-deck"> 50 <div class="card mb-4"> 51 <div class="card-body"> 52 <h5 class="card-title">Configuration parameters</h5> 53 <p class="card-text">Diplays a JSON representation of the active configuration.</p> 54 </div> 55 <ul class="list-group list-group-flush"> 56 <li class="list-group-item"> 57 <a href="/config" class="card-link">Configuration Parameters</a> 58 </ul> 59 </div> 60 <div class="w-100 d-none d-sm-block d-md-none"><!-- wrap every 1 on sm--></div> 61 <div class="card mb-4"> 62 <div class="card-body"> 63 <h5 class="card-title">StatsD/Prometheus</h5> 64 <p class="card-text">Displays current statistics.</p> 65 </div> 66 <ul class="list-group list-group-flush"> 67 <li class="list-group-item"> 68 <a href="/metrics" class="card-link">StatsD/Prometheus Page</a> 69 </ul> 70 </div> 71 <div class="w-100 d-none d-sm-block d-md-none"><!-- wrap every 1 on sm--></div> 72 <div class="w-100 d-none d-md-block d-lg-none"><!-- wrap every 2 on md--></div> 73 <div class="w-100 d-none d-lg-block d-xl-none"><!-- wrap every 2 on lg--></div> 74 <div class="w-100 d-none d-xl-block"><!-- wrap every 3 on xl--></div> 75 <div class="card mb-4"> 76 <div class="card-body"> 77 <h5 class="card-title">Triggers</h5> 78 <p class="card-text">Manage triggers for simulating failures.</p> 79 </div> 80 <ul class="list-group list-group-flush"> 81 <li class="list-group-item"> 82 <a class="card-link" href="/trigger">Triggers Page</a> 83 </li> 84 </ul> 85 </div> 86 <div class="w-100 d-none d-sm-block d-md-none"><!-- wrap every 1 on sm--></div> 87 <div class="card mb-4"> 88 <div class="card-body"> 89 <h5 class="card-title">Volumes</h5> 90 <p class="card-text">Examine volumes currently active on this ProxyFS node.</p> 91 </div> 92 <ul class="list-group list-group-flush"> 93 <li class="list-group-item"> 94 <a href="/volume" class="card-link">Volume Page</a> 95 </ul> 96 </div> 97 </div> 98 </div> 99 <script src="/jquery.min.js"></script> 100 <script src="/popper.min.js"></script> 101 <script src="/bootstrap.min.js"></script> 102 </body> 103 </html> 104 ` 105 106 // To use: fmt.Sprintf(configTemplate, proxyfsVersion, globals.ipAddrTCPPort, confMapJSONString) 107 const configTemplate string = `<!doctype html> 108 <html lang="en"> 109 <head> 110 <meta charset="utf-8"> 111 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 112 <link rel="stylesheet" href="/bootstrap.min.css"> 113 <link rel="stylesheet" href="/styles.css"> 114 <title>Config - %[2]v</title> 115 </head> 116 <body> 117 <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> 118 <a class="navbar-brand" href="#">%[2]v</a> 119 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> 120 <span class="navbar-toggler-icon"></span> 121 </button> 122 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 123 <ul class="navbar-nav mr-auto"> 124 <li class="nav-item"> 125 <a class="nav-link" href="/">Home</a> 126 </li> 127 <li class="nav-item active"> 128 <a class="nav-link" href="/config">Config <span class="sr-only">(current)</span></a> 129 </li> 130 <li class="nav-item"> 131 <a class="nav-link" href="/metrics">StatsD/Prometheus</a> 132 </li> 133 <li class="nav-item"> 134 <a class="nav-link" href="/trigger">Triggers</a> 135 </li> 136 <li class="nav-item"> 137 <a class="nav-link" href="/volume">Volumes</a> 138 </li> 139 </ul> 140 <span class="navbar-text">Version %[1]v</span> 141 </div> 142 </nav> 143 <div class="container"> 144 <nav aria-label="breadcrumb"> 145 <ol class="breadcrumb"> 146 <li class="breadcrumb-item"><a href="/">Home</a></li> 147 <li class="breadcrumb-item active" aria-current="page">Config</li> 148 </ol> 149 </nav> 150 <h1 class="display-4"> 151 Config 152 </h1> 153 <pre class="code" id="json_data"></pre> 154 </div> 155 <script src="/jquery.min.js"></script> 156 <script src="/popper.min.js"></script> 157 <script src="/bootstrap.min.js"></script> 158 <script src="/jsontree.js"></script> 159 <script type="text/javascript"> 160 var json_data = %[3]v; 161 document.getElementById("json_data").innerHTML = JSONTree.create(json_data, null, 1); 162 JSONTree.collapse(); 163 </script> 164 </body> 165 </html> 166 ` 167 168 // To use: fmt.Sprintf(metricsTemplate, proxyfsVersion, globals.ipAddrTCPPort, metricsJSONString) 169 const metricsTemplate string = `<!doctype html> 170 <html lang="en"> 171 <head> 172 <meta charset="utf-8"> 173 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 174 <link rel="stylesheet" href="/bootstrap.min.css"> 175 <link rel="stylesheet" href="/styles.css"> 176 <title>Metrics - %[2]v</title> 177 </head> 178 <body> 179 <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> 180 <a class="navbar-brand" href="#">%[2]v</a> 181 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> 182 <span class="navbar-toggler-icon"></span> 183 </button> 184 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 185 <ul class="navbar-nav mr-auto"> 186 <li class="nav-item"> 187 <a class="nav-link" href="/">Home</a> 188 </li> 189 <li class="nav-item"> 190 <a class="nav-link" href="/config">Config</a> 191 </li> 192 <li class="nav-item active"> 193 <a class="nav-link" href="/metrics">StatsD/Prometheus <span class="sr-only">(current)</span></a> 194 </li> 195 <li class="nav-item"> 196 <a class="nav-link" href="/trigger">Triggers</a> 197 </li> 198 <li class="nav-item"> 199 <a class="nav-link" href="/volume">Volumes</a> 200 </li> 201 </ul> 202 <span class="navbar-text">Version %[1]v</span> 203 </div> 204 </nav> 205 <div class="container"> 206 <nav aria-label="breadcrumb"> 207 <ol class="breadcrumb"> 208 <li class="breadcrumb-item"><a href="/">Home</a></li> 209 <li class="breadcrumb-item active" aria-current="page">StatsD/Prometheus</li> 210 </ol> 211 </nav> 212 <h1 class="display-4">StatsD/Prometheus</h1> 213 <div class="text-center"> 214 <div class="btn-group btn-group-toggle" data-toggle="buttons" id="tab-bar"></div> 215 </div> 216 <br> 217 <table class="table table-sm table-striped table-hover"> 218 <tbody id="metrics-data"></tbody> 219 </table> 220 </div> 221 <script src="/jquery.min.js"></script> 222 <script src="/popper.min.js"></script> 223 <script src="/bootstrap.min.js"></script> 224 <script type="text/javascript"> 225 var json_data = %[3]v; 226 var getPrefixes = function(data, levels) { 227 var prefixes = new Set(); 228 for (var key in data) { 229 prefixes.add(key.split("_", levels).join("_")); 230 } 231 return Array.from(prefixes); 232 }; 233 var getTabBarButtonMarkup = function(prefix, text, active) { 234 var button_markup = ""; 235 button_markup += " <label class=\"btn btn-sm btn-primary" + (active ? " active" : "") + "\" onclick=\"newPrefixSelected('" + prefix + "');\">\n"; 236 button_markup += " <input type=\"radio\" name=\"options\" id=\"option-" + prefix + "\"> " + text + "\n"; 237 button_markup += " </label>\n"; 238 return button_markup; 239 }; 240 var buildTabBarWithPrefixes = function(tab_bar_id, prefixes, selected_prefix) { 241 var tab_bar_markup = ""; 242 tab_bar_markup += getTabBarButtonMarkup("", "All", selected_prefix == ""); 243 var prefixes_length = prefixes.length; 244 for (var i = 0; i < prefixes_length; i++) { 245 var prefix = prefixes[i]; 246 tab_bar_markup += getTabBarButtonMarkup(prefix, prefix, selected_prefix == prefix); 247 } 248 document.getElementById(tab_bar_id).innerHTML = tab_bar_markup; 249 }; 250 var filterDataByPrefix = function(data, prefix) { 251 var prefix_to_search = prefix; 252 if (prefix_to_search !== "") { 253 prefix_to_search += "_"; 254 } 255 var filtered = {}; 256 for (var key in data) { 257 if (key.startsWith(prefix_to_search)) { 258 filtered[key] = data[key]; 259 } 260 } 261 return filtered; 262 }; 263 var getTableMarkupWithData = function(data) { 264 var table_markup = ""; 265 for (var key in data) { 266 table_markup += " <tr>\n"; 267 table_markup += " <th scope=\"row\">" + key + "</th>\n"; 268 table_markup += " <td class=\"text-right\"><pre class=\"no-margin\">" + data[key] + "</pre></td>\n"; 269 table_markup += " </tr>\n"; 270 } 271 return table_markup; 272 }; 273 var newPrefixSelected = function(prefix) { 274 var filteredData = filterDataByPrefix(json_data, prefix); 275 document.getElementById("metrics-data").innerHTML = getTableMarkupWithData(filteredData); 276 window.location.hash = prefix; 277 }; 278 var prefixes = getPrefixes(json_data, 2); 279 var anchor = window.location.hash; 280 if (anchor !== "") { 281 anchor = anchor.substr(1); 282 if (prefixes.indexOf(anchor) == -1) { 283 anchor = ""; 284 } 285 } 286 buildTabBarWithPrefixes("tab-bar", prefixes, anchor); 287 newPrefixSelected(anchor); 288 </script> 289 </body> 290 </html> 291 ` 292 293 // To use: fmt.Sprintf(volumeListTopTemplate, proxyfsVersion, globals.ipAddrTCPPort) 294 const volumeListTopTemplate string = `<!doctype html> 295 <html lang="en"> 296 <head> 297 <meta charset="utf-8"> 298 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 299 <link rel="stylesheet" href="/bootstrap.min.css"> 300 <link rel="stylesheet" href="/styles.css"> 301 <title>Volumes - %[2]v</title> 302 </head> 303 <body> 304 <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> 305 <a class="navbar-brand" href="#">%[2]v</a> 306 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> 307 <span class="navbar-toggler-icon"></span> 308 </button> 309 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 310 <ul class="navbar-nav mr-auto"> 311 <li class="nav-item"> 312 <a class="nav-link" href="/">Home</a> 313 </li> 314 <li class="nav-item"> 315 <a class="nav-link" href="/config">Config</a> 316 </li> 317 <li class="nav-item"> 318 <a class="nav-link" href="/metrics">StatsD/Prometheus</a> 319 </li> 320 <li class="nav-item"> 321 <a class="nav-link" href="/trigger">Triggers</a> 322 </li> 323 <li class="nav-item active"> 324 <a class="nav-link" href="/volume">Volumes <span class="sr-only">(current)</span></a> 325 </li> 326 </ul> 327 <span class="navbar-text">Version %[1]v</span> 328 </div> 329 </nav> 330 <div class="container"> 331 <nav aria-label="breadcrumb"> 332 <ol class="breadcrumb"> 333 <li class="breadcrumb-item"><a href="/">Home</a></li> 334 <li class="breadcrumb-item active" aria-current="page">Volumes</li> 335 </ol> 336 </nav> 337 338 <h1 class="display-4">Volumes</h1> 339 <table class="table table-sm table-striped table-hover"> 340 <thead> 341 <tr> 342 <th scope="col">Volume Name</th> 343 <th class="fit"> </th> 344 <th class="fit"> </th> 345 <th class="fit"> </th> 346 <th class="fit"> </th> 347 <th class="fit"> </th> 348 </tr> 349 </thead> 350 <tbody> 351 ` 352 353 // To use: fmt.Sprintf(volumeListPerVolumeTemplate, volumeName) 354 const volumeListPerVolumeTemplate string = ` <tr> 355 <td>%[1]v</td> 356 <td class="fit"><a href="/volume/%[1]v/snapshot" class="btn btn-sm btn-primary">SnapShots</a></td> 357 <td class="fit"><a href="/volume/%[1]v/fsck-job" class="btn btn-sm btn-primary">FSCK jobs</a></td> 358 <td class="fit"><a href="/volume/%[1]v/scrub-job" class="btn btn-sm btn-primary">SCRUB jobs</a></td> 359 <td class="fit"><a href="/volume/%[1]v/layout-report" class="btn btn-sm btn-primary">Layout Report</a></td> 360 <td class="fit"><a href="/volume/%[1]v/extent-map" class="btn btn-sm btn-primary">Extent Map</a></td> 361 </tr> 362 ` 363 364 const volumeListBottom string = ` </tbody> 365 </table> 366 </div> 367 <script src="/jquery.min.js"></script> 368 <script src="/popper.min.js"></script> 369 <script src="/bootstrap.min.js"></script> 370 </body> 371 </html> 372 ` 373 374 // To use: fmt.Sprintf(snapShotsTopTemplate, proxyfsVersion, globals.ipAddrTCPPort, volumeName) 375 const snapShotsTopTemplate string = `<!doctype html> 376 <html lang="en"> 377 <head> 378 <meta charset="utf-8"> 379 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 380 <link rel="stylesheet" href="/bootstrap.min.css"> 381 <link rel="stylesheet" href="/styles.css"> 382 <link href="/open-iconic/font/css/open-iconic-bootstrap.min.css" rel="stylesheet"> 383 <title>%[2]v SnapShots - %[2]v</title> 384 </head> 385 <body> 386 <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> 387 <a class="navbar-brand" href="#">%[2]v</a> 388 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> 389 <span class="navbar-toggler-icon"></span> 390 </button> 391 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 392 <ul class="navbar-nav mr-auto"> 393 <li class="nav-item"> 394 <a class="nav-link" href="/">Home</a> 395 </li> 396 <li class="nav-item"> 397 <a class="nav-link" href="/config">Config</a> 398 </li> 399 <li class="nav-item"> 400 <a class="nav-link" href="/metrics">StatsD/Prometheus</a> 401 </li> 402 <li class="nav-item"> 403 <a class="nav-link" href="/trigger">Triggers</a> 404 </li> 405 <li class="nav-item active"> 406 <a class="nav-link" href="/volume">Volumes <span class="sr-only">(current)</span></a> 407 </li> 408 </ul> 409 <span class="navbar-text">Version %[1]v</span> 410 </div> 411 </nav> 412 <div class="container"> 413 <nav aria-label="breadcrumb"> 414 <ol class="breadcrumb"> 415 <li class="breadcrumb-item"><a href="/">Home</a></li> 416 <li class="breadcrumb-item"><a href="/volume">Volumes</a></li> 417 <li class="breadcrumb-item active" aria-current="page">SnapShots %[3]v</li> 418 </ol> 419 </nav> 420 <div id="alert-area"></div> 421 <h1 class="display-4"> 422 SnapShots 423 <small class="text-muted">%[3]v</small> 424 </h1> 425 <form class="float-right" onsubmit="return createSnapShot();"> 426 <div class="input-group mb-3"> 427 <input type="text" name="name" class="form-control form-control-sm mb-2" id="new-snapshot-name" placeholder="New snapshot name" aria-label="Name" autofocus="autofocus"> 428 <div class="input-group-append"> 429 <button type="submit" class="btn btn-sm btn-primary mb-2">Create snapshot</span></button> 430 </div> 431 </div> 432 </form> 433 <table class="table table-sm table-striped table-hover"> 434 <thead> 435 <tr> 436 <th scope="col" id="header-id" class="fit clickable">ID</th> 437 <th scope="col" id="header-timestamp" class="w-25 clickable">Time</th> 438 <th scope="col" id="header-name" class="clickable">Name</th> 439 <th class="fit"> </th> 440 </tr> 441 </thead> 442 <tbody> 443 ` 444 445 // To use: fmt.Sprintf(snapShotsPerSnapShotTemplate, id, timeStamp.Format(time.RFC3339), name) 446 const snapShotsPerSnapShotTemplate string = ` <tr> 447 <td>%[1]v</td> 448 <td>%[2]v</td> 449 <td>%[3]v</td> 450 <td class="fit"><a href="#" class="btn btn-sm btn-danger" onclick="deleteSnapShot(%[1]v);"><span class="oi oi-trash" title="Delete" aria-hidden="true"></a></td> 451 </tr> 452 ` 453 454 // To use: fmt.Sprintf(snapShotsBottomTemplate, volumeName) 455 const snapShotsBottomTemplate string = ` </tbody> 456 </table> 457 <br /> 458 </div> 459 <script src="/jquery.min.js"></script> 460 <script src="/popper.min.js"></script> 461 <script src="/bootstrap.min.js"></script> 462 <script type="text/javascript"> 463 volumeName = "%[1]v"; 464 hideAlert = function() { 465 document.getElementById('alert-area').innerHTML = ''; 466 }; 467 showAlertWithMsg = function(msg) { 468 var html = '<div class="alert alert-danger alert-dismissible fade show" role="alert">\n'; 469 html += ' ' + msg + '\n'; 470 html += ' <button type="button" class="close" data-dismiss="alert" aria-label="Close">'; 471 html += ' <span aria-hidden="true">×</span>\n'; 472 html += ' </button>\n'; 473 html += '</div>\n'; 474 document.getElementById('alert-area').innerHTML = html; 475 }; 476 showDeleteError = function(id, jqXHR, textStatus, errorThrown) { 477 var msg = 'Error deleting snapshot with ID <em>' + id + '</em>: ' + jqXHR.status + ' ' + jqXHR.statusText; 478 showAlertWithMsg(msg); 479 }; 480 showCreateError = function(name, jqXHR, textStatus, errorThrown) { 481 var msg = 'Error creating snapshot with name <em>' + name + '</em>: ' + jqXHR.status + ' ' + jqXHR.statusText; 482 showAlertWithMsg(msg); 483 }; 484 deleteSnapShot = function(id) { 485 hideAlert(); 486 var url = '/volume/' + volumeName + '/snapshot/' + id; 487 $.ajax({ 488 url: url, 489 method: 'DELETE', 490 dataType: 'json', 491 success: function(data, textStatus, jqXHR) { 492 location.reload(); 493 }, 494 error: function(jqXHR, textStatus, errorThrown) { 495 showDeleteError(id, jqXHR, textStatus, errorThrown); 496 } 497 }); 498 }; 499 createSnapShot = function() { 500 hideAlert(); 501 document.getElementById('new-snapshot-name').select(); 502 var new_snapshot_name = $.trim($('#new-snapshot-name').val()); 503 if (new_snapshot_name == "") { 504 showAlertWithMsg("SnapShot name can't be blank."); 505 return false; 506 } 507 var url = '/volume/' + volumeName + '/snapshot/'; 508 $.ajax({ 509 url: url, 510 method: 'POST', 511 data: {'name': new_snapshot_name}, 512 success: function(data, textStatus, jqXHR) { 513 location.reload(); 514 }, 515 error: function(jqXHR, textStatus, errorThrown) { 516 showCreateError(new_snapshot_name, jqXHR, textStatus, errorThrown); 517 } 518 }); 519 return false; 520 }; 521 getQueryVariable = function(variable) { 522 var query = window.location.search.substring(1); 523 var vars = query.split('&'); 524 for (var i = 0; i < vars.length; i++) { 525 var pair = vars[i].split('='); 526 if (decodeURIComponent(pair[0]) == variable) { 527 return decodeURIComponent(pair[1]); 528 } 529 } 530 return null; 531 } 532 getQueryVariableOrDefault = function(variable, default_value, to_lower, allowed_values) { 533 var value = getQueryVariable(variable); 534 if (value === null) { 535 value = default_value; 536 } else if (to_lower) { 537 value = value.toLowerCase(); 538 } 539 // allowed_values is optional 540 if (typeof allowed_values !== 'undefined' && allowed_values.indexOf(value) == -1) { 541 value = default_value; 542 } 543 return value; 544 }; 545 getHeaderIdForField = function(field) { 546 return 'header-' + field; 547 }; 548 addCaretToElement = function(element, direction) { 549 if (direction == 'desc') { 550 element.insertAdjacentHTML('beforeend', ' <span class="oi oi-chevron-bottom"></span>'); 551 } else if (direction == 'asc') { 552 element.insertAdjacentHTML('beforeend', ' <span class="oi oi-chevron-top"></span>'); 553 } 554 }; 555 var orderby_default = 'timestamp'; 556 var orderby_allowed_values = ['id', 'timestamp', 'name']; 557 var direction_default = 'asc'; 558 var direction_allowed_values = ['asc', 'desc']; 559 displaySortingCaret = function() { 560 var orderby = getQueryVariableOrDefault('orderby', orderby_default, true, orderby_allowed_values); 561 var direction = getQueryVariableOrDefault('direction', direction_default, true, direction_allowed_values); 562 var header = document.getElementById(getHeaderIdForField(orderby)); 563 if (header === null) { 564 console.error("Could not get element by id: " + getHeaderIdForField(orderby)); 565 return {'orderby': null, 'direction': null}; 566 } 567 addCaretToElement(header, direction); 568 return {'orderby': orderby, 'direction': direction}; 569 }; 570 var current_sorting = displaySortingCaret(); 571 for (let i = orderby_allowed_values.length - 1; i >= 0; i--) { 572 var id = getHeaderIdForField(orderby_allowed_values[i]); 573 $("#" + id).on("click", function(){ 574 var new_order_by = orderby_allowed_values[i]; 575 var current_order_by = current_sorting['orderby']; 576 if (new_order_by == current_order_by) { 577 if (current_sorting['direction'] == 'asc') { 578 var new_direction = 'desc'; 579 } else { 580 var new_direction = 'asc'; 581 } 582 } else { 583 var new_direction = direction_default; 584 } 585 window.location = window.location.origin + window.location.pathname + "?orderby=" + new_order_by + "&direction=" + new_direction; 586 }); 587 } 588 </script> 589 </body> 590 </html> 591 ` 592 593 // To use: fmt.Sprintf(jobsTopTemplate, proxyfsVersion, globals.ipAddrTCPPort, volumeName, {"FSCK"|"SCRUB"}) 594 const jobsTopTemplate string = `<!doctype html> 595 <html lang="en"> 596 <head> 597 <meta charset="utf-8"> 598 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 599 <link rel="stylesheet" href="/bootstrap.min.css"> 600 <link rel="stylesheet" href="/styles.css"> 601 <title>%[4]v Jobs %[3]v - %[2]v</title> 602 </head> 603 <body> 604 <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> 605 <a class="navbar-brand" href="#">%[2]v</a> 606 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> 607 <span class="navbar-toggler-icon"></span> 608 </button> 609 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 610 <ul class="navbar-nav mr-auto"> 611 <li class="nav-item"> 612 <a class="nav-link" href="/">Home</a> 613 </li> 614 <li class="nav-item"> 615 <a class="nav-link" href="/config">Config</a> 616 </li> 617 <li class="nav-item"> 618 <a class="nav-link" href="/metrics">StatsD/Prometheus</a> 619 </li> 620 <li class="nav-item"> 621 <a class="nav-link" href="/trigger">Triggers</a> 622 </li> 623 <li class="nav-item active"> 624 <a class="nav-link" href="/volume">Volumes <span class="sr-only">(current)</span></a> 625 </li> 626 </ul> 627 <span class="navbar-text">Version %[1]v</span> 628 </div> 629 </nav> 630 <div class="container"> 631 <nav aria-label="breadcrumb"> 632 <ol class="breadcrumb"> 633 <li class="breadcrumb-item"><a href="/">Home</a></li> 634 <li class="breadcrumb-item"><a href="/volume">Volumes</a></li> 635 <li class="breadcrumb-item active" aria-current="page">%[4]v Jobs %[3]v</li> 636 </ol> 637 </nav> 638 <h1 class="display-4"> 639 %[4]v Jobs 640 <small class="text-muted">%[3]v</small> 641 </h1> 642 <table class="table table-sm table-striped table-hover"> 643 <thead> 644 <tr> 645 <th scope="col">Job ID</th> 646 <th>Start Time</th> 647 <th>End Time</th> 648 <th>Status</th> 649 <th class="fit"> </th> 650 </tr> 651 </thead> 652 <tbody> 653 ` 654 655 // To use: fmt.Sprintf(jobsPerRunningJobTemplate, jobID, job.startTime.Format(time.RFC3339), volumeName, {"fsck"|"scrub"}) 656 const jobsPerRunningJobTemplate string = ` <tr> 657 <td>%[1]v</td> 658 <td>%[2]v</td> 659 <td></td> 660 <td>Running</td> 661 <td class="fit"><a href="/volume/%[3]v/%[4]v-job/%[1]v" class="btn btn-sm btn-primary">View</a></td> 662 </tr> 663 ` 664 665 // To use: fmt.Sprintf(jobsPerHaltedJobTemplate, jobID, job.startTime.Format(time.RFC3339), job.endTime.Format(time.RFC3339), volumeName, {"fsck"|"scrub"}) 666 const jobsPerHaltedJobTemplate string = ` <tr class="table-info"> 667 <td>%[1]v</td> 668 <td>%[2]v</td> 669 <td>%[3]v</td> 670 <td>Halted</td> 671 <td class="fit"><a href="/volume/%[4]v/%[5]v-job/%[1]v" class="btn btn-sm btn-primary">View</a></td> 672 </tr> 673 ` 674 675 // To use: fmt.Sprintf(jobsPerSuccessfulJobTemplate, jobID, job.startTime.Format(time.RFC3339), job.endTime.Format(time.RFC3339), volumeName, {"fsck"|"scrub"}) 676 const jobsPerSuccessfulJobTemplate string = ` <tr class="table-success"> 677 <td>%[1]v</td> 678 <td>%[2]v</td> 679 <td>%[3]v</td> 680 <td>Successful</td> 681 <td class="fit"><a href="/volume/%[4]v/%[5]v-job/%[1]v" class="btn btn-sm btn-primary">View</a></td> 682 </tr> 683 ` 684 685 // To use: fmt.Sprintf(jobsPerFailedJobTemplate, jobID, job.startTime.Format(time.RFC3339), job.endTime.Format(time.RFC3339), volumeName, {"fsck"|"scrub"}) 686 const jobsPerFailedJobTemplate string = ` <tr class="table-danger"> 687 <td>%[1]v</td> 688 <td>%[2]v</td> 689 <td>%[3]v</td> 690 <td>Failed</td> 691 <td class="fit"><a href="/volume/%[4]v/%[5]v-job/%[1]v" class="btn btn-sm btn-primary">View</a></td> 692 </tr> 693 ` 694 695 const jobsListBottom string = ` </tbody> 696 </table> 697 <br /> 698 ` 699 700 // To use: fmt.Sprintf(jobsStartJobButtonTemplate, volumeName, {"fsck"|"scrub"}) 701 const jobsStartJobButtonTemplate string = ` <form method="post" action="/volume/%[1]v/%[2]v-job"> 702 <input type="submit" value="Start new job" class="btn btn-sm btn-primary"> 703 </form> 704 ` 705 706 const jobsBottom string = ` <script src="/jquery.min.js"></script> 707 <script src="/popper.min.js"></script> 708 <script src="/bootstrap.min.js"></script> 709 </body> 710 </html> 711 ` 712 713 // To use: fmt.Sprintf(jobTemplate, proxyfsVersion, globals.ipAddrTCPPort, volumeName, {"FSCK"|"SCRUB"}, {"fsck"|"scrub"}, jobID, jobStatusJSONString) 714 const jobTemplate string = `<!doctype html> 715 <html lang="en"> 716 <head> 717 <meta charset="utf-8"> 718 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 719 <link rel="stylesheet" href="/bootstrap.min.css"> 720 <link rel="stylesheet" href="/styles.css"> 721 <title>%[6]v %[4]v Job - %[2]v</title> 722 </head> 723 <body> 724 <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> 725 <a class="navbar-brand" href="#">%[2]v</a> 726 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> 727 <span class="navbar-toggler-icon"></span> 728 </button> 729 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 730 <ul class="navbar-nav mr-auto"> 731 <li class="nav-item"> 732 <a class="nav-link" href="/">Home</a> 733 </li> 734 <li class="nav-item"> 735 <a class="nav-link" href="/config">Config</a> 736 </li> 737 <li class="nav-item"> 738 <a class="nav-link" href="/metrics">StatsD/Prometheus</a> 739 </li> 740 <li class="nav-item"> 741 <a class="nav-link" href="/trigger">Triggers</a> 742 </li> 743 <li class="nav-item active"> 744 <a class="nav-link" href="/volume">Volumes <span class="sr-only">(current)</span></a> 745 </li> 746 </ul> 747 <span class="navbar-text">Version %[1]v</span> 748 </div> 749 </nav> 750 <div class="container"> 751 <nav aria-label="breadcrumb"> 752 <ol class="breadcrumb"> 753 <li class="breadcrumb-item"><a href="/">Home</a></li> 754 <li class="breadcrumb-item"><a href="/volume">Volumes</a></li> 755 <li class="breadcrumb-item"><a href="/volume/%[3]v/%[5]v-job">%[4]v Jobs %[3]v</a></li> 756 <li class="breadcrumb-item active" aria-current="page">%[6]v</li> 757 </ol> 758 </nav> 759 <h1 class="display-4"> 760 %[4]v Job 761 <small class="text-muted">%[6]v</small> 762 </h1> 763 <br> 764 <dl class="row" id="job-info"></dl> 765 </div> 766 <script src="/jquery.min.js"></script> 767 <script src="/popper.min.js"></script> 768 <script src="/bootstrap.min.js"></script> 769 <script type="text/javascript"> 770 var json_data = %[7]v; 771 var getDescriptionListEntryMarkup = function(dt, dd) { 772 var markup = ""; 773 markup += " <dt class=\"col-sm-2\">\n"; 774 markup += " " + dt + "\n"; 775 markup += " </dt>\n"; 776 markup += " <dd class=\"col-sm-10\">\n"; 777 markup += " " + dd + "\n"; 778 markup += " </dd>\n"; 779 return markup; 780 }; 781 var getLogEntryContentsMarkup = function(log_entries, entries_type) { 782 var log_entries_length = log_entries.length; 783 if (log_entries_length == 0) { 784 return "No " + entries_type; 785 } 786 var markup = ""; 787 var timestamp = ""; 788 var description = ""; 789 var timestamp_end_pos = 0; 790 var entry = ""; 791 markup += " <table class=\"table table-sm table-striped table-hover\">\n"; 792 for (var i = 0; i < log_entries_length; i++) { 793 entry = log_entries[i]; 794 timestamp_end_pos = entry.indexOf(" "); 795 timestamp = entry.slice(0, timestamp_end_pos); 796 description = entry.slice(timestamp_end_pos); 797 markup += " <tr>\n"; 798 markup += " <td class=\"fit align-text-top\">\n"; 799 markup += " <nobr>" + timestamp + " </nobr>\n"; 800 markup += " </td>\n"; 801 markup += " <td class=\"align-text-top\">\n"; 802 markup += " " + description + "\n"; 803 markup += " </td>\n"; 804 markup += " </tr>\n"; 805 } 806 markup += " </table>\n"; 807 return markup; 808 }; 809 var job_info = ""; 810 if (json_data["halt time"] !== "") { 811 var state = "Halted"; 812 } else if (json_data["done time"] !== "") { 813 var state = "Completed"; 814 } else { 815 var state = "Running"; 816 } 817 job_info += getDescriptionListEntryMarkup("State", state); 818 job_info += getDescriptionListEntryMarkup("Start time", json_data["start time"]); 819 if (json_data["halt time"] !== "") { 820 job_info += getDescriptionListEntryMarkup("Halt time", json_data["halt time"]); 821 } 822 if (json_data["done time"] !== "") { 823 job_info += getDescriptionListEntryMarkup("Done time", json_data["done time"]); 824 } 825 job_info += getDescriptionListEntryMarkup("Errors", getLogEntryContentsMarkup(json_data["error list"], "errors")); 826 job_info += getDescriptionListEntryMarkup("Info", getLogEntryContentsMarkup(json_data["info list"], "info")); 827 document.getElementById("job-info").innerHTML = job_info; 828 </script> 829 </body> 830 </html> 831 ` 832 833 // To use: fmt.Sprintf(layoutReportTopTemplate, proxyfsVersion, globals.ipAddrTCPPort, volumeName) 834 const layoutReportTopTemplate string = `<!doctype html> 835 <html lang="en"> 836 <head> 837 <meta charset="utf-8"> 838 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 839 <link rel="stylesheet" href="/bootstrap.min.css"> 840 <link rel="stylesheet" href="/styles.css"> 841 <title>Layout Report %[3]v - %[2]v</title> 842 </head> 843 <body> 844 <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> 845 <a class="navbar-brand" href="#">%[2]v</a> 846 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> 847 <span class="navbar-toggler-icon"></span> 848 </button> 849 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 850 <ul class="navbar-nav mr-auto"> 851 <li class="nav-item"> 852 <a class="nav-link" href="/">Home</a> 853 </li> 854 <li class="nav-item"> 855 <a class="nav-link" href="/config">Config</a> 856 </li> 857 <li class="nav-item"> 858 <a class="nav-link" href="/metrics">StatsD/Prometheus</a> 859 </li> 860 <li class="nav-item"> 861 <a class="nav-link" href="/trigger">Triggers</a> 862 </li> 863 <li class="nav-item active"> 864 <a class="nav-link" href="/volume">Volumes <span class="sr-only">(current)</span></a> 865 </li> 866 </ul> 867 <span class="navbar-text">Version %[1]v</span> 868 </div> 869 </nav> 870 <div class="container"> 871 <nav aria-label="breadcrumb"> 872 <ol class="breadcrumb"> 873 <li class="breadcrumb-item"><a href="/">Home</a></li> 874 <li class="breadcrumb-item"><a href="/volume">Volumes</a></li> 875 <li class="breadcrumb-item active" aria-current="page">Layout Report %[3]v</li> 876 </ol> 877 </nav> 878 <h1 class="display-4"> 879 Layout Report 880 <small class="text-muted">%[3]v</small> 881 </h1> 882 <a id="validate-button" class="btn btn-primary float-right" href="#">Show validated version</a><br><br> 883 ` 884 885 // To use: fmt.Sprintf(layoutReportTableTopTemplate, TreeName, NumDiscrepencies, {"success"|"danger"}) 886 const layoutReportTableTopTemplate string = ` <br> 887 <div class="d-flex justify-content-between"> 888 <h3>%[1]v</h3><h4><span class="badge badge-%[3]v d-none">%[2]v discrepancies</span></h4> 889 </div> 890 <table class="table table-sm table-striped table-hover"> 891 <thead> 892 <tr> 893 <th scope="col" class="w-50">ObjectName</th> 894 <th scope="col" class="w-50">ObjectBytes</th> 895 </tr> 896 </thead> 897 <tbody> 898 ` 899 900 // To use: fmt.Sprintf(layoutReportTableRowTemplate, ObjectName, ObjectBytes) 901 const layoutReportTableRowTemplate string = ` <tr> 902 <td><pre class="no-margin">%016[1]X</pre></td> 903 <td><pre class="no-margin">%[2]v</pre></td> 904 </tr> 905 ` 906 907 const layoutReportTableBottom string = ` </tbody> 908 </table> 909 ` 910 911 const layoutReportBottom string = ` <div> 912 <script src="/jquery.min.js"></script> 913 <script src="/popper.min.js"></script> 914 <script src="/bootstrap.min.js"></script> 915 <script type="text/javascript"> 916 var url_params = new URLSearchParams(window.location.search); 917 var validate = false; 918 var validate_button = document.getElementById('validate-button'); 919 if ( 920 url_params.has("validate") && 921 url_params.get("validate") != "0" && 922 url_params.get("validate").toLowerCase() != "false" 923 ) { 924 validate = true; 925 } 926 if (validate) { 927 validate_button.innerHTML = 'Show non-validated version'; 928 validate_button.href = window.location.origin + window.location.pathname + "?validate=0"; 929 $("h4 .badge").removeClass("d-none"); 930 } else { 931 validate_button.innerHTML = 'Show validated version'; 932 validate_button.href = window.location.origin + window.location.pathname + "?validate=1"; 933 } 934 </script> 935 </body> 936 </html> 937 ` 938 939 // To use: fmt.Sprintf(extentMapTemplate, proxyfsVersion, globals.ipAddrTCPPort, volumeName, extentMapJSONString, pathDoubleQuotedString, serverErrorBoolString) 940 const extentMapTemplate string = `<!doctype html> 941 <html lang="en"> 942 <head> 943 <meta charset="utf-8"> 944 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 945 <link rel="stylesheet" href="/bootstrap.min.css"> 946 <link rel="stylesheet" href="/styles.css"> 947 <title>Extent Map %[3]v - %[2]v</title> 948 </head> 949 <body> 950 <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> 951 <a class="navbar-brand" href="#">%[2]v</a> 952 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> 953 <span class="navbar-toggler-icon"></span> 954 </button> 955 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 956 <ul class="navbar-nav mr-auto"> 957 <li class="nav-item"> 958 <a class="nav-link" href="/">Home</a> 959 </li> 960 <li class="nav-item"> 961 <a class="nav-link" href="/config">Config</a> 962 </li> 963 <li class="nav-item"> 964 <a class="nav-link" href="/metrics">StatsD/Prometheus</a> 965 </li> 966 <li class="nav-item"> 967 <a class="nav-link" href="/trigger">Triggers</a> 968 </li> 969 <li class="nav-item active"> 970 <a class="nav-link" href="/volume">Volumes <span class="sr-only">(current)</span></a> 971 </li> 972 </ul> 973 <span class="navbar-text">Version %[1]v</span> 974 </div> 975 </nav> 976 <div class="container"> 977 <nav aria-label="breadcrumb"> 978 <ol class="breadcrumb"> 979 <li class="breadcrumb-item"><a href="/">Home</a></li> 980 <li class="breadcrumb-item"><a href="/volume">Volumes</a></li> 981 <li class="breadcrumb-item active" aria-current="page">Extent Map %[3]v</li> 982 </ol> 983 </nav> 984 985 <h1 class="display-4"> 986 Extent Map 987 <small class="text-muted">%[3]v</small> 988 </h1> 989 990 <div class="alert alert-danger" id="error-message" role="alert"></div> 991 992 <form id="new-path-form"> 993 <div class="input-group mb-3"> 994 <input type="text" id="path-text-box" class="form-control path-text-box" placeholder="path/to/check" aria-label="Path to check"> 995 <div class="input-group-append"> 996 <input type="submit" class="btn btn-primary" value="Search"> 997 </div> 998 </div> 999 </form> 1000 1001 <br> 1002 <table class="table table-sm table-striped table-hover" id="extent-map-table"> 1003 <thead> 1004 <tr> 1005 <th scope="col">File Offset</th> 1006 <th scope="col" class="w-50">Container/Object</th> 1007 <th scope="col">Object Offset</th> 1008 <th scope="col">Length</th> 1009 </tr> 1010 </thead> 1011 <tbody id="extent-map-data"></tbody> 1012 </table> 1013 </div> 1014 <script src="/jquery.min.js"></script> 1015 <script src="popper.min.js"></script> 1016 <script src="/bootstrap.min.js"></script> 1017 <script type="text/javascript"> 1018 var json_data = %[4]v 1019 var path = %[5]v; 1020 var volume = "%[3]v"; 1021 var server_error = %[6]v; 1022 1023 $("#new-path-form").submit(function(e){ 1024 e.preventDefault(); 1025 var new_path = $("#path-text-box").val().trim(); 1026 if (new_path != "" && !new_path.startsWith("/")) { 1027 new_path = "/" + new_path; 1028 } 1029 var new_url = "/volume/" + volume + "/extent-map" + new_path; 1030 window.location = new_url; 1031 }); 1032 1033 var hideError = function() { 1034 document.getElementById("error-message").style.display = "none"; 1035 }; 1036 1037 var showPathError = function(path) { 1038 var msg_to_print = ""; 1039 if (path !== null) { 1040 msg_to_print = "<p>There was an error getting extent map for path:</p><pre>" + path + "</pre>"; 1041 } else { 1042 msg_to_print = "<p>There was an error getting extent map for path:</p><pre>(error retrieving input path)</pre>"; 1043 } 1044 showCustomError(msg_to_print); 1045 }; 1046 1047 var showCustomError = function(text) { 1048 document.getElementById("error-message").innerHTML = text; 1049 }; 1050 1051 var getTableMarkupWithData = function(data) { 1052 var table_markup = ""; 1053 for (var key in data) { 1054 table_markup += " <tr>\n"; 1055 table_markup += " <td><pre class=\"no-margin\">" + data[key]["file_offset"] + "</pre></td>\n"; 1056 table_markup += " <td><pre class=\"no-margin\">" + data[key]["container_name"] + "/" + data[key]["object_name"] + "</pre></td>\n"; 1057 table_markup += " <td><pre class=\"no-margin\">" + data[key]["object_offset"] + "</pre></td>\n"; 1058 table_markup += " <td><pre class=\"no-margin\">" + data[key]["length"] + "</pre></td>\n"; 1059 table_markup += " </tr>\n"; 1060 } 1061 return table_markup; 1062 }; 1063 1064 var buildTable = function(data) { 1065 document.getElementById("extent-map-data").innerHTML = getTableMarkupWithData(data); 1066 }; 1067 1068 var hideTable = function() { 1069 document.getElementById("extent-map-table").style.display = "none"; 1070 }; 1071 1072 var fillInTextBox = function(path) { 1073 document.getElementById("path-text-box").value = path; 1074 }; 1075 1076 var selectSearchText = function() { 1077 var pat_text_box = document.getElementById("path-text-box"); 1078 pat_text_box.setSelectionRange(0, pat_text_box.value.length) 1079 }; 1080 1081 if (server_error) { 1082 // Error finding path 1083 hideTable(); 1084 fillInTextBox(path); 1085 showPathError(path); 1086 selectSearchText(); 1087 } else if (json_data === null && path === null) { 1088 // Empty form 1089 hideTable(); 1090 hideError(); 1091 selectSearchText(); 1092 } else if (json_data === null || path === null) { 1093 // This should never happen! 1094 hideTable(); 1095 var msg_to_print = "<p>Oops, that's embarrassing... Something went wrong server side: "; 1096 if (json_data === null) { 1097 msg_to_print += "'json_data' is null, but 'path' is not:</p><pre>" + path + "</pre>"; 1098 fillInTextBox(path); 1099 } else { 1100 msg_to_print += "'path' is null, but 'json_data' is not:</p><pre>" + JSON.stringify(json_data, null, 2) + "</pre>"; 1101 } 1102 showCustomError(msg_to_print); 1103 selectSearchText(); 1104 } else { 1105 // Path has been found 1106 hideError(); 1107 fillInTextBox(path); 1108 buildTable(json_data); 1109 selectSearchText(); 1110 } 1111 </script> 1112 </body> 1113 </html> 1114 ` 1115 1116 // To use: fmt.Sprintf(triggerTopTemplate, proxyfsVersion, globals.ipAddrTCPPort) 1117 const triggerTopTemplate string = `<!doctype html> 1118 <html lang="en"> 1119 <head> 1120 <meta charset="utf-8"> 1121 <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> 1122 <link rel="stylesheet" href="/bootstrap.min.css"> 1123 <link rel="stylesheet" href="/styles.css"> 1124 <title>Triggers - %[2]v</title> 1125 </head> 1126 <body> 1127 <nav class="navbar navbar-expand-lg navbar-dark bg-dark fixed-top"> 1128 <a class="navbar-brand" href="#">%[2]v</a> 1129 <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation"> 1130 <span class="navbar-toggler-icon"></span> 1131 </button> 1132 <div class="collapse navbar-collapse" id="navbarNavDropdown"> 1133 <ul class="navbar-nav mr-auto"> 1134 <li class="nav-item"> 1135 <a class="nav-link" href="/">Home</a> 1136 </li> 1137 <li class="nav-item"> 1138 <a class="nav-link" href="/config">Config</a> 1139 </li> 1140 <li class="nav-item"> 1141 <a class="nav-link" href="/metrics">StatsD/Prometheus</a> 1142 </li> 1143 <li class="nav-item active"> 1144 <a class="nav-link" href="/trigger">Triggers <span class="sr-only">(current)</span></a> 1145 </li> 1146 <li class="nav-item"> 1147 <a class="nav-link" href="/volume">Volumes</a> 1148 </li> 1149 </ul> 1150 <span class="navbar-text">Version %[1]v</span> 1151 </div> 1152 </nav> 1153 <div class="container"> 1154 <nav aria-label="breadcrumb"> 1155 <ol class="breadcrumb"> 1156 <li class="breadcrumb-item"><a href="/">Home</a></li> 1157 <li class="breadcrumb-item active" aria-current="page">Triggers</li> 1158 </ol> 1159 </nav> 1160 <h1 class="display-4">Triggers</h1> 1161 ` 1162 1163 const triggerAllActive string = ` <div class="text-center"> 1164 <div class="btn-group"> 1165 <a href="/trigger" class="btn btn-sm btn-primary active">All</a> 1166 <a href="/trigger?armed=true" class="btn btn-sm btn-primary">Armed</a> 1167 <a href="/trigger?armed=false" class="btn btn-sm btn-primary">Disarmed</a> 1168 </div> 1169 </div> 1170 ` 1171 1172 const triggerArmedActive string = ` <div class="text-center"> 1173 <div class="btn-group"> 1174 <a href="/trigger" class="btn btn-sm btn-primary">All</a> 1175 <a href="/trigger?armed=true" class="btn btn-sm btn-primary active">Armed</a> 1176 <a href="/trigger?armed=false" class="btn btn-sm btn-primary">Disarmed</a> 1177 </div> 1178 </div> 1179 ` 1180 1181 const triggerDisarmedActive string = ` <div class="text-center"> 1182 <div class="btn-group"> 1183 <a href="/trigger" class="btn btn-sm btn-primary">All</a> 1184 <a href="/trigger?armed=true" class="btn btn-sm btn-primary">Armed</a> 1185 <a href="/trigger?armed=false" class="btn btn-sm btn-primary active">Disarmed</a> 1186 </div> 1187 </div> 1188 ` 1189 1190 const triggerTableTop string = ` <br> 1191 <table class="table table-sm table-striped table-hover"> 1192 <thead> 1193 <tr> 1194 <th scope="col">Halt Label</th> 1195 <th scope="col" class="w-25">Halt After Count</th> 1196 </tr> 1197 </thead> 1198 <tbody> 1199 ` 1200 1201 // To use: fmt.Sprintf(triggerTableRowTemplate, haltTriggerString, haltTriggerCount) 1202 const triggerTableRowTemplate string = ` <tr> 1203 <td class="halt-label">%[1]v</td> 1204 <td> 1205 <div class="input-group"> 1206 <input type="number" class="form-control form-control-sm haltTriggerCount" min="0" max="4294967295" value="%[2]v"> 1207 <div class="valid-feedback"> 1208 New value successfully saved. 1209 </div> 1210 <div class="invalid-feedback"> 1211 There was an error saving the new value. 1212 </div> 1213 </div> 1214 </td> 1215 </tr> 1216 ` 1217 1218 const triggerBottom string = ` </tbody> 1219 </table> 1220 </div> 1221 <script src="/jquery.min.js"></script> 1222 <script src="/popper.min.js"></script> 1223 <script src="/bootstrap.min.js"></script> 1224 <script type="text/javascript"> 1225 markValid = function(elem) {elem.removeClass("is-valid is-invalid").addClass("is-valid");}; 1226 markInvalid = function(elem) {elem.removeClass("is-valid is-invalid").addClass("is-invalid");}; 1227 unmark = function(elem) {elem.removeClass("is-valid is-invalid");}; 1228 updateErrorMsg = function(elem, text) {elem.siblings(".invalid-feedback").html(text);}; 1229 getLabelForCount = function(elem) {return elem.parent().parent().siblings(".halt-label").html();}; 1230 var timeout_unmark = 2000; 1231 $(".haltTriggerCount").on("change", function(){ 1232 that = $( this ); 1233 $.ajax({ 1234 url: '/trigger/' + getLabelForCount(that), 1235 method: 'POST', 1236 data: {'count': that.val()}, 1237 dataType: 'json', 1238 success: function(data, textStatus, jqXHR) { 1239 markValid(that); 1240 window.setTimeout(function(){unmark(that);}, timeout_unmark); 1241 }, 1242 error: function(jqXHR, textStatus, errorThrown) { 1243 var msg = "Error: " + jqXHR.status + " " + jqXHR.statusText // Do we want to use jqXHR.responseText? 1244 updateErrorMsg(that, msg); 1245 markInvalid(that); 1246 } 1247 }); 1248 }); 1249 </script> 1250 </body> 1251 </html> 1252 `