Router and Repeater Status Monitor applet
Almquist Shell Script written for POSIX systems like OpenWRT, Linux and WSL2/WSLg

---------------------------------------
Dependencies
---------------------------------------
NONE!  Just OpenWRT or any POSIX system like OpenWRT, Linux and WSL2/WSLg.  If you can run ash prompt scripts or bash (Bourne Again Shell) scripts, you can run this!

---------------------------------------
Installation
---------------------------------------
The files are to be mounted as follows:

├── readme.txt (this very file)
├── <the code goes into your html file if you want an embedded version>
├── routerstatus.html
└── cgi-bin
    └── routerstatus.sh

Just remeber to chmod +x upsstatus.sh before running it!  You could even set this up as a cron task, if you wish...

---------------------------------------
What is it?
---------------------------------------
I wrote this program to help me monitor the repeater routers that my router is connected to.  I wrote a custom landing page in place of the usual OpenWRT LuCI page at index.html (with the LuCI page being moved to admin.html) and needed a way to see how the three wireless repeater bridges (in Wireless Distributed System / WDS configuration) were doing, as they occasionally fall out of sync and render themselves and their downstream devices inaccessible.

SO!  I wrote up routerstatus.sh and then embedded it with this code:

```
  <div style="float: right; width: 300px; color: #222222; padding: 8px;">
<style>
#router-status-container {
  border: 2px solid #ccc;
  border-radius: 8px;
  padding: 12px;
  margin: 20px auto;
  width: fit-content;
  background: #F9F9F9;
  box-shadow: 0 0 10px rgba(0,0,0,0.1);
}

#router-status-title {
  font-family: sans-serif;
  font-size: 18px;
  font-weight: bold;
  text-align: center;
  margin-bottom: 12px;
  padding-bottom: 6px;
  border-bottom: 1px solid #CCCCCC;
}

#router-status-panel {
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: flex-start;
  flex-wrap: wrap;
}

.router-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 100px;
  padding: 10px;
  margin: 10px;
  text-align: center;
  font-family: sans-serif;
  font-size: 14px;
  border: 1px solid #DDDDDD;
  border-radius: 6px;
  background: #FFFFFF;
}

.led-status-green {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  margin-bottom: 6px;
  background-color: limegreen;
  box-shadow: 0 0 8px 2px limegreen;
  animation: softGlow 2s ease-in-out infinite alternate;
}

.led-status-red {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  margin-bottom: 6px;
  background-color: red;
  box-shadow: 0 0 8px 2px red;
  animation: softGlowRed 2s ease-in-out infinite alternate;
}

.ping-time {
  font-size: 11px;
  color: #666666;
  margin-top: 4px;
}

.twilight-zone {
  filter: hue-rotate(90deg) contrast(200%);
}

@keyframes softGlow {
  from {
    box-shadow: 0 0 4px 1px limegreen;
  }
  to {
    box-shadow: 0 0 12px 4px limegreen;
  }
}

@keyframes softGlowRed {
  from {
    box-shadow: 0 0 4px 1px red;
  }
  to {
    box-shadow: 0 0 12px 4px red;
  }
}

</style>

<div id="router-status-container">

  <div id="router-status-title">Repeater Status</div>

  <div id="router-status-panel">

    <div class="router-card">
      <div id="led-Nighthawk" class="led-status-green"></div>
      <div>Nighthawk</div>
      <div>(192.168.1.1)</div>
      <div id="time-Nighthawk" class="ping-time"></div>
    </div>

    <div class="router-card">
      <div id="led-DIR615" class="led-status-green"></div>
      <div>DIR-615</div>
      <div>(192.168.1.2)</div>
      <div id="time-DIR615" class="ping-time"></div>
    </div>

    <div class="router-card">
      <div id="led-DIR601" class="led-status-green"></div>
      <div>DIR-601</div>
      <div>(192.168.1.3)</div>
      <div id="time-DIR601" class="ping-time"></div>
    </div>

    <div class="router-card">
      <div id="led-WRT54Gv8" class="led-status-green"></div>
      <div>WRT54Gv8</div>
      <div>(192.168.1.6)</div>
      <div id="time-WRT54Gv8" class="ping-time"></div>
    </div>

  </div>
</div>

  <script>
const routerMap = {
  "Nighthawk": "192.168.1.1",
  "DIR615": "192.168.1.2",
  "DIR601": "192.168.1.3",
  "WRT54Gv8": "192.168.1.6"
};

async function updateRouterLEDs() {
  const res = await fetch("/cgi-bin/routerstatus.sh?cachebust=" + Date.now());
  const data = await res.json();

  for (const name in routerMap) {
    const ip = routerMap[name];
    const status = data[ip];
    const led = document.getElementById("led-" + name);

    if (!led) continue;

    if (status === "online") {
      led.classList.remove("led-status-red");
      led.classList.add("led-status-green");
    } else {
      led.classList.remove("led-status-green");
      led.classList.add("led-status-red");
    }

  // Offline timestamp logic (freeze when router stops responding)
  const timeSpan = document.getElementById("time-" + name);

  if (status === "offline") {
    // Only set the timestamp the *first* time it goes offline
    if (timeSpan && timeSpan.textContent.trim() === "") {
      const now = new Date();
      timeSpan.textContent = "Last responded: " + now.toLocaleString();
    }
  } else {
    // Router is online → clear the timestamp
    if (timeSpan) {
      timeSpan.textContent = "";
    }
  }

    // Twilight Zone logic
    if (ip === "192.168.1.1" && status === "offline") {
      document.body.classList.add("twilight-zone");
      alert("You have entered... the Twilight Zone.");
    } else if (ip === "192.168.1.1" && status === "online") {
      document.body.classList.remove("twilight-zone");
    }
  }
}

setInterval(updateRouterLEDs, 5000);
updateRouterLEDs();
</script>
  </div>

```
This prorgam is horizontal by default, but upon being resized to a smaller width, becomes stacked vertically, so it works in either orientation, for whatever suits you!

Any further comments, questions or suggestions can be forwarded to the developer at slycooper1986@yahoo.ca
