Browse Source

Add Bootstrap Styling and Refactor Webpanel

First adjustments which include renaming and re-organizing files, adding
Twitter Bootstrap for better mobile-first styling, and finishing first
iteration for the passes interface.
pull/123/head
Justin Karimi 3 years ago
parent
commit
81fc46eb27
37 changed files with 248 additions and 388 deletions
  1. +0
    -7
      templates/webpanel/Config.php
  2. +0
    -6
      templates/webpanel/Controller/C_predict.php
  3. +0
    -8
      templates/webpanel/Controller/C_showDetail.php
  4. +0
    -6
      templates/webpanel/Controller/C_showLastImage.php
  5. +0
    -11
      templates/webpanel/Controller/C_showLastImages.php
  6. +0
    -82
      templates/webpanel/Model/Conn.php
  7. +0
    -0
      templates/webpanel/Model/index.html
  8. +4
    -0
      templates/webpanel/TODO
  9. +0
    -23
      templates/webpanel/Views/V_viewDetail.php
  10. +0
    -24
      templates/webpanel/Views/V_viewLastImage.php
  11. +0
    -48
      templates/webpanel/Views/V_viewLastImages.php
  12. +0
    -24
      templates/webpanel/Views/V_viewPasses.php
  13. +0
    -0
      templates/webpanel/Views/index.html
  14. +0
    -0
      templates/webpanel/assets/web_img/favicon.ico
  15. +0
    -0
      templates/webpanel/assets/web_img/logo-small.png
  16. +7
    -0
      templates/webpanel/config.php
  17. +6
    -0
      templates/webpanel/controllers/Pass_Controller.php
  18. +6
    -0
      templates/webpanel/controllers/pass_controller.php
  19. +17
    -0
      templates/webpanel/css/header.css
  20. +9
    -0
      templates/webpanel/css/pass_list.css
  21. +0
    -7
      templates/webpanel/detail.php
  22. +0
    -6
      templates/webpanel/footer.php
  23. +0
    -27
      templates/webpanel/header.php
  24. +0
    -0
      templates/webpanel/i18n/ar.php
  25. +0
    -0
      templates/webpanel/i18n/de.php
  26. +0
    -0
      templates/webpanel/i18n/en.php
  27. +0
    -0
      templates/webpanel/i18n/es.php
  28. +0
    -0
      templates/webpanel/i18n/sr.php
  29. +3
    -4
      templates/webpanel/index.php
  30. +30
    -0
      templates/webpanel/models/DB_Conn.php
  31. +30
    -0
      templates/webpanel/models/db_conn.php
  32. +0
    -5
      templates/webpanel/passes.php
  33. +0
    -100
      templates/webpanel/style.css
  34. +41
    -0
      templates/webpanel/views/PassList.php
  35. +14
    -0
      templates/webpanel/views/footer.php
  36. +40
    -0
      templates/webpanel/views/header.php
  37. +41
    -0
      templates/webpanel/views/pass_list.php

+ 0
- 7
templates/webpanel/Config.php View File

@@ -1,7 +0,0 @@
<?php
return (object) array(
'base_url' => '/images/',
'img_per_page' => 12,
'lang' => 'es'
);
?>

+ 0
- 6
templates/webpanel/Controller/C_predict.php View File

@@ -1,6 +0,0 @@
<?php
require('Model/Conn.php');
$con = new Conn();
$passes = $con->getPasses();
require('Views/V_viewPasses.php');
?>

+ 0
- 8
templates/webpanel/Controller/C_showDetail.php View File

@@ -1,8 +0,0 @@
<?php
require('Model/Conn.php');
$con = new Conn();
if ($pass_id < 1) $pass_id = 1;
$enhacements = $con->getEnhacements($pass_id);
$path = $con->getPath($pass_id);
require('Views/V_viewDetail.php');
?>

+ 0
- 6
templates/webpanel/Controller/C_showLastImage.php View File

@@ -1,6 +0,0 @@
<?php
require('Model/Conn.php');
$con = new Conn();
$images = $con->getLastImage();
require('Views/V_viewLastImage.php');
?>

+ 0
- 11
templates/webpanel/Controller/C_showLastImages.php View File

@@ -1,11 +0,0 @@
<?php
require('Model/Conn.php');
$con = new Conn();
if ($page < 1) $page = 1;
$img_per_page = $configs->img_per_page;
$page_count = $con->totalPages($img_per_page);
if ($page < 1) $page = 1;
if ($page > $page_count) $page = $page_count;
$images = $con->getImages($page, $img_per_page);
require('Views/V_viewLastImages.php');
?>

+ 0
- 82
templates/webpanel/Model/Conn.php View File

@@ -1,82 +0,0 @@
<?php
class Conn {
private $con;
public function __construct() {
$this->con = new SQLite3("/home/pi/raspberry-noaa/panel.db");
}
public function getPasses() {
$today = strtotime(date('Y-m-d', time()));
$query = $this->con->query("SELECT sat_name, is_active,
pass_start, pass_end,
max_elev FROM predict_passes
WHERE (pass_start > $today) ORDER BY
pass_start ASC;");
$passes = [];
$i = 0;
while($row = $query->fetchArray()){
$passes[$i] = $row;
$i++;
}
return $passes;
}
public function totalPages($img_per_page) {
$total_pages = $this->con->querySingle("SELECT count() from decoded_passes;");
return ceil($total_pages/$img_per_page);
}
public function getImages($page, $img_per_page) {
$query = $this->con->prepare("SELECT decoded_passes.id, predict_passes.pass_start,
file_path, sat_type, predict_passes.sat_name, predict_passes.max_elev
FROM decoded_passes INNER JOIN predict_passes
ON predict_passes.pass_start = decoded_passes.pass_start
ORDER BY decoded_passes.pass_start DESC LIMIT ? OFFSET ?;");
$query->bindValue(1, $img_per_page);
$query->bindValue(2, $img_per_page * ($page-1));
$result = $query->execute();
$images = [];
$i = 0;
while($row = $result->fetchArray()){
$images[$i] = $row;
$i++;
}
return $images;
}
public function getEnhacements($id) {
$query = $this->con->prepare('SELECT daylight_pass, sat_type, img_count
FROM decoded_passes WHERE id = ?;');
$query->bindValue(1, $id);
$result = $query->execute();
$pass = $result->fetchArray();
switch($pass['sat_type']) {
case 0: // Meteor-M2
$enhacements = ['-122-rectified.jpg'];
break;
case 1: // NOAA
if ($pass['daylight_pass'] == 1) {
$enhacements = ['-ZA.jpg','-MCIR.jpg','-MCIR-precip.jpg','-MSA.jpg','-MSA-precip.jpg','-HVC.jpg','-HVC-precip.jpg','-HVCT.jpg','-HVCT-precip.jpg'];
} else {
$enhacements = ['-ZA.jpg','-MCIR.jpg','-MCIR-precip.jpg'];
}
break;
case 2: // ISS
for ($x = 0; $x <= $pass['img_count']-1; $x++) {
$enhacements[] = "-$x.png";
}
break;
}
return $enhacements;
}
public function getPath($id) {
$query = $this->con->prepare('SELECT file_path FROM decoded_passes
WHERE id = ?;');
$query->bindValue(1, $id);
$result = $query->execute();
$image = $result->fetchArray();
return $image['file_path'];
}
}
?>

+ 0
- 0
templates/webpanel/Model/index.html View File


+ 4
- 0
templates/webpanel/TODO View File

@@ -0,0 +1,4 @@
* Change location of database file (sub-directory and ignore in git)
** Update config.php
* Include more details in pass table like "was captured" or not


+ 0
- 23
templates/webpanel/Views/V_viewDetail.php View File

@@ -1,23 +0,0 @@
<div style="overflow-x:auto;">
<table id="passes">
<?php
$row_count=0;
$col_count=0;
$baseurl = $configs->base_url;
foreach ($enhacements as $enhacement) {
if($row_count%3==0) {
echo "<tr>";
$col_count=1;
}
echo "<td><div id =\"satimgdiv\"><a href=". $baseurl . $path . $enhacement ."><img id=\"satimg\" src=". $baseurl . "thumb/" . $path . $enhacement ."></img></a></div>";
if($col_count==3) {
echo "</tr>";
}
$row_count++;
$col_count++;
}
?>
</table>
</div>
</body>
</html>

+ 0
- 24
templates/webpanel/Views/V_viewLastImage.php View File

@@ -1,24 +0,0 @@
<table id="passes" class="img-grid">
<?php
$row_count=0;
$col_count=0;
$baseurl = $configs->base_url;
foreach ($images as $image) {
if($row_count%3==0) {
echo "<tr>";
$col_count=1;
}
echo "<td><div id =\"satimgdiv\"><a href=". $baseurl . $image['file_path'] ."><img id=\"satimg\" src=". $baseurl . "thumb/" . $image['file_path'] ."></img></a></div>";
echo "<ul><li>". $image['sat_name'] ."</li>";
echo "<li>". date('d/m/Y H:i:s', $image['pass_start']) ."</li></ul></td>";
if($col_count==3) {
echo "</tr>";
}
$row_count++;
$col_count++;
}
?>
</table>
</body>
</html>

+ 0
- 48
templates/webpanel/Views/V_viewLastImages.php View File

@@ -1,48 +0,0 @@
<div style="overflow-x:auto;">
<table id="passes" class="img-grid">
<?php
if ($page > 1) {
echo "<tr><th><a href=\"?page=" . ($page-1) . "\">" . $lang['prev'] . "</a></th>";
} else {
echo "<tr><th></th>";
}
echo "<th>" . $lang['page'] . " $page " . $lang['of'] . " $page_count</th>";
if ($page < $page_count) {
echo "<th><a href=\"?page=" . ($page+1) . "\">" . $lang['next'] . "</a></th></tr>";
} else {
echo "<th></th></tr>";
}
$row_count=0;
$col_count=0;
$baseurl = $configs->base_url;
foreach ($images as $image) {
if($row_count%3==0) {
echo "<tr>";
$col_count=1;
}
switch($image['sat_type']) {
case 0: // Meteor-M2
$ending = "-122-rectified.jpg";
break;
case 1: // NOAA
$ending = "-MCIR.jpg";
break;
case 2: // ISS
$ending = "-0.png";
break;
}
echo "<td><div id =\"satimgdiv\"><a href=". "detail.php?id=" . $image['id'] ."><img id=\"satimg\" src=". $baseurl . "thumb/" . $image['file_path'] . $ending ."></img></a></div>";
echo "<ul><li>". $image['sat_name'] ."</li>";
echo "<li> " . $lang['elev'] . ": ". $image['max_elev'] ."°</li>";
echo "<li>". date('d/m/Y H:i:s', $image['pass_start']) ."</li></ul></td>";
if($col_count==3) {
echo "</tr>";
}
$row_count++;
$col_count++;
}
?>
</table>
</div>
</body>
</html>

+ 0
- 24
templates/webpanel/Views/V_viewPasses.php View File

@@ -1,24 +0,0 @@
<div style="overflow-x:auto;">
<table id="passes">
<tr>
<th><?php echo $lang['satellite']; ?></th>
<th><?php echo $lang['pass_start']; ?></th>
<th><?php echo $lang['pass_end']; ?></th>
<th><?php echo $lang['max_elev']; ?></th>
</tr>
<?php
foreach ($passes as $pass) {
if ($pass['is_active'] == false) {
echo "<tr class='inactive'>";
} else {
echo "<tr>";
}
echo "<td>". $pass['sat_name'] ."</td>";
echo "<td>". date('H:i:s', $pass['pass_start']) ."</td>";
echo "<td>". date('H:i:s', $pass['pass_end']) ."</td>";
echo "<td>". $pass['max_elev'] ."</td>";
echo "</tr>";
}
?>
</table>
</div>

+ 0
- 0
templates/webpanel/Views/index.html View File


templates/webpanel/Controller/index.html → templates/webpanel/assets/web_img/favicon.ico View File


templates/webpanel/logo-small.png → templates/webpanel/assets/web_img/logo-small.png View File


+ 7
- 0
templates/webpanel/config.php View File

@@ -0,0 +1,7 @@
<?php
return (object) array(
'db_dir' => '/home/pi/raspberry-noaa/', # base directory for sqlite database
'lang' => 'en', # see files in i18n/ directory for available translations
'timezone' => 'America/New_York' # use https://www.php.net/manual/en/timezones.php
);
?>

+ 6
- 0
templates/webpanel/controllers/Pass_Controller.php View File

@@ -0,0 +1,6 @@
<?php
require('models/DB_Conn.php');
$db_con = new Conn($configs->db_dir);
$passes = $db_con->getPasses();
require('views/PassList.php');
?>

+ 6
- 0
templates/webpanel/controllers/pass_controller.php View File

@@ -0,0 +1,6 @@
<?php
require('models/db_conn.php');
$db_con = new Conn($configs->db_dir);
$passes = $db_con->getPasses();
require('views/pass_list.php');
?>

+ 17
- 0
templates/webpanel/css/header.css View File

@@ -0,0 +1,17 @@
header ul.navbar-nav li {
display: block;
font-weight: 700;
}

header ul.navbar-nav li a {
display: block;
padding-left: 0 !important;
padding-right: 0 !important;
padding-bottom: 0.2rem;
margin-bottom: 0.3rem;
margin-right: 1.25rem;
}

header ul.navbar-nav li.active a {
border-bottom: 0.25rem solid white;
}

+ 9
- 0
templates/webpanel/css/pass_list.css View File

@@ -0,0 +1,9 @@
table#passes tr.inactive {
color: lightgray;
}

table#passes td.no-passes {
text-align: center;
font-weight: bold;
font-style: italic;
}

+ 0
- 7
templates/webpanel/detail.php View File

@@ -1,7 +0,0 @@
<?php
$configs = include('Config.php');
include_once('header.php');
$pass_id = isset($_GET['id']) ? intval($_GET['id']) : 1;
require('Controller/C_showDetail.php');
include_once("footer.php")
?>

+ 0
- 6
templates/webpanel/footer.php View File

@@ -1,6 +0,0 @@
</div>
<footer>
<a href="https://github.com/reynico/raspberry-noaa"><img class="img-footer" src="logo-small.png"></a>
</footer>
</body>
</html>

+ 0
- 27
templates/webpanel/header.php View File

@@ -1,27 +0,0 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
date_default_timezone_set('America/Argentina/Buenos_Aires');
$page = basename($_SERVER['PHP_SELF']);
$configs = include('Config.php');
$lang = $configs->lang;
include_once('language/' . $lang . '.php');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" type="text/css" href="style.css">
<title><?= isset($PageTitle) ? $PageTitle : "Raspberry NOAA"?></title>
</head>
<body>
<div class="topnav">
<a class="<?php if($page == 'passes.php'){ echo ' active"';}?>" href="passes.php"><?php echo $lang['passes']; ?></a>
<a class="<?php if($page == 'index.php' || $page == 'detail.php'){ echo ' active"';}?>" href="index.php"><?php echo $lang['images']; ?></a>
</div>
<div class="container">



templates/webpanel/language/ar.php → templates/webpanel/i18n/ar.php View File


templates/webpanel/language/de.php → templates/webpanel/i18n/de.php View File


templates/webpanel/language/en.php → templates/webpanel/i18n/en.php View File


templates/webpanel/language/es.php → templates/webpanel/i18n/es.php View File


templates/webpanel/language/sr.php → templates/webpanel/i18n/sr.php View File


+ 3
- 4
templates/webpanel/index.php View File

@@ -1,7 +1,6 @@
<?php
include_once('header.php');
include_once('views/header.php');
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
require('Controller/C_showLastImages.php');
include_once("footer.php")
require('controllers/pass_controller.php');
include_once('views/footer.php')
?>

+ 30
- 0
templates/webpanel/models/DB_Conn.php View File

@@ -0,0 +1,30 @@
<?php
class Conn {
private $con;

public function __construct(string $db_dir) {
$this->con = new SQLite3($db_dir . 'panel.db');
}

public function getPasses() {
$today = strtotime(date('Y-m-d', time()));
$query = $this->con->query("SELECT sat_name,
is_active,
pass_start,
pass_end,
max_elev
FROM predict_passes
WHERE (pass_start > $today)
ORDER BY pass_start ASC;");
$passes = [];
$i = 0;

while($row = $query->fetchArray()) {
$passes[$i] = $row;
$i++;
}

return $passes;
}
}
?>

+ 30
- 0
templates/webpanel/models/db_conn.php View File

@@ -0,0 +1,30 @@
<?php
class Conn {
private $con;

public function __construct(string $db_dir) {
$this->con = new SQLite3($db_dir . 'panel.db');
}

public function getPasses() {
$today = strtotime(date('Y-m-d', time()));
$query = $this->con->query("SELECT sat_name,
is_active,
pass_start,
pass_end,
max_elev
FROM predict_passes
WHERE (pass_start > $today)
ORDER BY pass_start ASC;");
$passes = [];
$i = 0;

while($row = $query->fetchArray()) {
$passes[$i] = $row;
$i++;
}

return $passes;
}
}
?>

+ 0
- 5
templates/webpanel/passes.php View File

@@ -1,5 +0,0 @@
<?php
include_once('header.php');
require('Controller/C_predict.php');
include_once("footer.php")
?>

+ 0
- 100
templates/webpanel/style.css View File

@@ -1,100 +0,0 @@
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
}

.container {
margin: 20px auto;
max-width: 800px;
}

.topnav {
overflow: hidden;
background-color: #333;
}

.topnav a {
float: left;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 17px;
}

.topnav a:hover {
background-color: #ddd;
color: black;
}

.topnav a.active {
background-color: #4CAF50;
color: white;
}

#passes {
font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
border-collapse: collapse;
max-width: 500px;
width: 100%;
margin: auto;
}

#passes.img-grid {
max-width: 700px;
}

#passes td,
#passes th {
border: 1px solid #ddd;
padding: 8px;
}

#passes tr:nth-child(even) {
background-color: #f2f2f2;
}


/* #passes tr:hover {background-color: #ddd;} */

#passes th {
padding-top: 12px;
padding-bottom: 12px;
background-color: #4CAF50;
text-align: center;
color: white;
}

#passes tr.inactive {
color: lightgray;
}

#satimg {
max-width: 200px;
max-height: 200px;
width: 100%;
object-fit: cover;
}

#satimgdiv {
width: 100%;
height: 100%;
max-width: 200px;
background-color: antiquewhite;
/* margin: 10px; */
display: inline-block;
vertical-align: middle;
text-align: center;
}

footer {
/* position: absolute; */
margin: 20px;
bottom: 0;
max-width: 100%;
text-align: center;
}

img.img-footer {
max-width: 100%;
}

+ 41
- 0
templates/webpanel/views/PassList.php View File

@@ -0,0 +1,41 @@
<link rel="stylesheet" type="text/css" href="css/pass_list.css">

<table class="table table-bordered table-sm table-striped" id="passes">
<thead class="thead-dark">
<tr class="text-center">
<th scope="col"><?php echo $lang['satellite']; ?></th>
<th scope="col"><?php echo $lang['pass_start']; ?></th>
<th scope="col"><?php echo $lang['pass_end']; ?></th>
<th scope="col"><?php echo $lang['max_elev']; ?></th>
</tr>
</thead>
<tbody>
<?php
$now = date('H:i:s', time());

# account for no passes currently scheduled
if (count($passes) <= 0) {
echo "<tr><td colspan=\"4\" class=\"no-passes\">0 " . $lang['passes'] . "</td></tr>";
} else {
foreach ($passes as $pass) {
$pass_start = date('H:i:s', $pass['pass_start']);
$pass_end = date('H:i:s', $pass['pass_end']);

# gray out anything that has already run or did not run because there
# was another overlapping capture but is now in the past
if ($pass['is_active'] == false or $pass_end < $now) {
echo "<tr class='inactive'>";
} else {
echo "<tr>";
}

echo "<td scope=\"row\">". $pass['sat_name'] ."</td>";
echo "<td scope=\"row\" class=\"text-center\">" . $pass_start ."</td>";
echo "<td scope=\"row\" class=\"text-center\">" . $pass_end . "</td>";
echo "<td scope=\"row\" class=\"text-center\">" . $pass['max_elev'] ."</td>";
echo "</tr>";
}
}
?>
</tbody>
</table>

+ 14
- 0
templates/webpanel/views/footer.php View File

@@ -0,0 +1,14 @@
</div>

<footer class="footer text-center">
<div class="contaienr">
<a href="https://github.com/jekhokie/raspberry-noaa-v2"><img class="img-footer" src="assets/web_img/logo-small.png"></a>
</div>
</footer>

<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-Piv4xVNRyMGpqkS2by6br4gNJ7DXjqk09RmUpJ8jgGtD7zP9yug3goQfGII0yAns" crossorigin="anonymous"></script>
</body>
</html>

+ 40
- 0
templates/webpanel/views/header.php View File

@@ -0,0 +1,40 @@
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$page = basename($_SERVER['PHP_SELF']);
$configs = include('config.php');
date_default_timezone_set($configs->timezone);
$lang = $configs->lang;
include_once('i18n/' . $lang . '.php');
?>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">

<link rel="stylesheet" type="text/css" href="css/header.css">

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css"
integrity="sha384-B0vP5xmATw1+K9KRQjQERJvTumQW0nPEzvF6L/Z6nronJ3oUOFUFpCjEUQouq2+l" crossorigin="anonymous">
<title>Raspberry NOAA V2</title>

<link rel="shortcut icon" href="assets/web_img/favicon.ico" type="image/x-icon"/>
</head>
<body>
<header class="mb-3">
<div class="navbar navbar-expand navbar-dark bg-dark">
<ul class="navbar-nav">
<li class="nav-item <?php if($page == 'index.php' || $page == 'passes.php'){ echo 'active'; }?>">
<a class="nav-link" href="index.php"><?php echo $lang['passes']; ?></a>
</li>
<li class="nav-item <?php if($page == 'images.php'){ echo 'active'; }?>">
<a class="nav-link" href="images.php"><?php echo $lang['images']; ?></a>
</li>
</ul>
</div>
</header>
<div class="container">

+ 41
- 0
templates/webpanel/views/pass_list.php View File

@@ -0,0 +1,41 @@
<link rel="stylesheet" type="text/css" href="css/pass_list.css">

<table class="table table-bordered table-sm table-striped" id="passes">
<thead class="thead-dark">
<tr class="text-center">
<th scope="col"><?php echo $lang['satellite']; ?></th>
<th scope="col"><?php echo $lang['pass_start']; ?></th>
<th scope="col"><?php echo $lang['pass_end']; ?></th>
<th scope="col"><?php echo $lang['max_elev']; ?></th>
</tr>
</thead>
<tbody>
<?php
$now = date('H:i:s', time());

# account for no passes currently scheduled
if (count($passes) <= 0) {
echo "<tr><td colspan=\"4\" class=\"no-passes\">0 " . $lang['passes'] . "</td></tr>";
} else {
foreach ($passes as $pass) {
$pass_start = date('H:i:s', $pass['pass_start']);
$pass_end = date('H:i:s', $pass['pass_end']);

# gray out anything that has already run or did not run because there
# was another overlapping capture but is now in the past
if ($pass['is_active'] == false or $pass_end < $now) {
echo "<tr class='inactive'>";
} else {
echo "<tr>";
}

echo "<td scope=\"row\">". $pass['sat_name'] ."</td>";
echo "<td scope=\"row\" class=\"text-center\">" . $pass_start ."</td>";
echo "<td scope=\"row\" class=\"text-center\">" . $pass_end . "</td>";
echo "<td scope=\"row\" class=\"text-center\">" . $pass['max_elev'] ."</td>";
echo "</tr>";
}
}
?>
</tbody>
</table>

Loading…
Cancel
Save