add README.md and new app branding assets.

This commit is contained in:
2026-05-11 17:13:13 +03:30
parent 91f8eda94a
commit 327a146d21
12 changed files with 284 additions and 192 deletions

98
README.md Normal file
View File

@@ -0,0 +1,98 @@
# NGX Monitor
NGX Monitor is an Android client for `ngx_http_monitoring_module`. It is built with Kotlin and Jetpack Compose for monitoring a fleet of local or private Nginx servers from a phone.
## Features
- Fleet dashboard for multiple servers with live status, tags, favorites, search, and critical-first sorting.
- Server detail cockpit with system, Nginx, request, disk, history, alert, and route diagnostics views.
- JSON API polling via `/monitor/api` and health checks via `/monitor/health`.
- Foreground live streaming via `/monitor/live` Server-Sent Events.
- Encrypted per-server credentials for monitor tokens and optional Nginx Basic Auth.
- Local SQLite history with 1-minute summaries, 15-minute raw snapshots, and 30-day pruning.
- Background periodic checks with local Android notifications.
- VPN/DNS resilience with per-server fallback LAN IP aliases while preserving HTTPS hostnames for TLS/SNI.
## Monitor API
The app expects each server base URL to expose `/monitor`.
Common endpoints used by the app:
- `GET /monitor/api`
- `GET /monitor/health`
- `GET /monitor/live`
Authentication rules:
- Monitor tokens are sent as `X-Monitor-Token`.
- Basic Auth is sent on every monitor request when configured.
- If both are enabled on the server, configure both in the app.
## Local Network And VPN Notes
For private domains, keep the main server URL as the HTTPS domain, for example:
```text
https://edge-1.home.example
```
If VPN or DNS breaks local resolution, add fallback LAN IPs in the server editor:
```text
192.168.1.20, 192.168.1.21
```
The app keeps the URL hostname unchanged and only substitutes DNS results internally. This keeps HTTPS certificate verification and SNI aligned with the private domain.
Android or the VPN app may still block LAN access completely. In that case, the app shows the failure in the Route panel but cannot override the VPN policy.
## Security
- Prefer HTTPS for monitor endpoints.
- Prefer header tokens over query-string tokens.
- Avoid sharing screenshots that expose internal hostnames or route details.
- Keep monitor endpoints behind least-privilege ACLs.
- Do not reuse production admin passwords for Basic Auth.
## Build
From the project root:
```powershell
$env:GRADLE_USER_HOME='C:\Users\meghdad\AndroidStudioProjects\NGXhttpMonitoringClient\.gradle-user'
.\gradlew.bat :app:assembleDebug --console=plain
```
Debug APK output:
```text
app/build/outputs/apk/debug/app-debug.apk
```
Run unit tests:
```powershell
$env:GRADLE_USER_HOME='C:\Users\meghdad\AndroidStudioProjects\NGXhttpMonitoringClient\.gradle-user'
.\gradlew.bat :app:testDebugUnitTest --console=plain
```
## Project Structure
```text
app/src/main/java/net/rodakot/ngxhttpmonitoringclient/
background/ JobScheduler checks and notifications
data/ SQLite storage, encrypted credentials, HTTP/SSE client
domain/ URL rules, alert evaluation, route planning, sampling policy
model/ Server, metric, alert, and route data models
ui/ Compose command-deck UI
ui/theme/ Material theme and colors
```
## App Assets
- Launcher icon foreground/background: `app/src/main/res/drawable/ic_launcher_foreground.xml`, `ic_launcher_background.xml`
- Splash artwork: `app/src/main/res/drawable/splash_hero.xml`
- Splash background: `app/src/main/res/drawable/splash_screen.xml`
These assets are vector drawables, so they scale cleanly across screen densities.

View File

@@ -21,7 +21,7 @@
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.NGXHttpMonitoringClient"> android:theme="@style/Theme.NGXHttpMonitoringClient.Launcher">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@@ -16,6 +16,7 @@ import net.rodakot.ngxhttpmonitoringclient.ui.theme.NGXHttpMonitoringClientTheme
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_NGXHttpMonitoringClient)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
requestNotificationPermission() requestNotificationPermission()

View File

@@ -5,166 +5,17 @@
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> android:viewportHeight="108">
<path <path
android:fillColor="#3DDC84" android:fillColor="@color/command_black"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#FF151B24"
android:pathData="M0,0h108v108h-108z" /> android:pathData="M0,0h108v108h-108z" />
<path <path
android:fillColor="#00000000" android:fillColor="#00000000"
android:pathData="M9,0L9,108" android:pathData="M8,22h92M8,42h92M8,62h92M8,82h92M22,8v92M42,8v92M62,8v92M82,8v92"
android:strokeWidth="0.8" android:strokeColor="#2235D38B"
android:strokeColor="#33FFFFFF" /> android:strokeWidth="1" />
<path <path
android:fillColor="#00000000" android:fillColor="#1035D38B"
android:pathData="M19,0L19,108" android:pathData="M18,18h72a10,10 0,0 1,10 10v52a10,10 0,0 1,-10 10h-72a10,10 0,0 1,-10 -10v-52a10,10 0,0 1,10 -10z" />
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector> </vector>

View File

@@ -1,30 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp" android:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path <path
android:fillColor="#FFFFFF" android:fillColor="#FF202733"
android:fillType="nonZero" android:pathData="M25,23h58a9,9 0,0 1,9 9v44a9,9 0,0 1,-9 9h-58a9,9 0,0 1,-9 -9v-44a9,9 0,0 1,9 -9z" />
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" <path
android:strokeWidth="1" android:fillColor="#FF0E1116"
android:strokeColor="#00000000" /> android:pathData="M28,33h52a4,4 0,0 1,4 4v8a4,4 0,0 1,-4 4h-52a4,4 0,0 1,-4 -4v-8a4,4 0,0 1,4 -4z" />
</vector> <path
android:fillColor="#FF0E1116"
android:pathData="M28,59h52a4,4 0,0 1,4 4v8a4,4 0,0 1,-4 4h-52a4,4 0,0 1,-4 -4v-8a4,4 0,0 1,4 -4z" />
<path
android:fillColor="#FF35D38B"
android:pathData="M32,38a3,3 0,1 0,0.1 0zM32,64a3,3 0,1 0,0.1 0z" />
<path
android:fillColor="#FFFFB84D"
android:pathData="M43,38a3,3 0,1 0,0.1 0zM43,64a3,3 0,1 0,0.1 0z" />
<path
android:fillColor="#FF4CC9F0"
android:pathData="M58,39h17a2,2 0,0 1,0 4h-17a2,2 0,0 1,0 -4zM58,65h17a2,2 0,0 1,0 4h-17a2,2 0,0 1,0 -4z" />
<path
android:fillColor="#00000000"
android:pathData="M28,53h12l7,-9l9,20l7,-11h17"
android:strokeColor="#FF35D38B"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="4" />
<path
android:fillColor="#2235D38B"
android:pathData="M54,16a38,38 0,1 1,-0.1 0M54,22a32,32 0,1 0,0.1 0z" />
</vector>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="260dp"
android:height="260dp"
android:viewportWidth="260"
android:viewportHeight="260">
<path
android:fillColor="#FF0E1116"
android:pathData="M0,0h260v260h-260z" />
<path
android:fillColor="#FF171C23"
android:pathData="M36,38h188a24,24 0,0 1,24 24v136a24,24 0,0 1,-24 24h-188a24,24 0,0 1,-24 -24v-136a24,24 0,0 1,24 -24z" />
<path
android:fillColor="#00000000"
android:pathData="M38,86h184M38,130h184M38,174h184M86,54v152M130,54v152M174,54v152"
android:strokeColor="#244CC9F0"
android:strokeWidth="2" />
<path
android:fillColor="#FF202733"
android:pathData="M58,76h144a10,10 0,0 1,10 10v26a10,10 0,0 1,-10 10h-144a10,10 0,0 1,-10 -10v-26a10,10 0,0 1,10 -10z" />
<path
android:fillColor="#FF202733"
android:pathData="M58,138h144a10,10 0,0 1,10 10v26a10,10 0,0 1,-10 10h-144a10,10 0,0 1,-10 -10v-26a10,10 0,0 1,10 -10z" />
<path
android:fillColor="#FF35D38B"
android:pathData="M70,92a8,8 0,1 0,0.1 0zM70,154a8,8 0,1 0,0.1 0z" />
<path
android:fillColor="#FFFFB84D"
android:pathData="M96,92a8,8 0,1 0,0.1 0z" />
<path
android:fillColor="#FFFF5A52"
android:pathData="M96,154a8,8 0,1 0,0.1 0z" />
<path
android:fillColor="#FF4CC9F0"
android:pathData="M126,93h58a6,6 0,0 1,0 12h-58a6,6 0,0 1,0 -12zM126,155h58a6,6 0,0 1,0 12h-58a6,6 0,0 1,0 -12z" />
<path
android:fillColor="#00000000"
android:pathData="M62,128h32l18,-28l24,60l19,-32h44"
android:strokeColor="#FF35D38B"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="8" />
<path
android:fillColor="#5535D38B"
android:pathData="M130,24a106,106 0,1 1,-0.1 0M130,34a96,96 0,1 0,0.1 0z" />
<path
android:fillColor="#334CC9F0"
android:pathData="M130,48a82,82 0,1 1,-0.1 0M130,54a76,76 0,1 0,0.1 0z" />
</vector>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="@color/command_black" />
</shape>
</item>
<item
android:drawable="@drawable/splash_hero"
android:gravity="center" />
</layer-list>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="@color/command_black"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#FF202733"
android:pathData="M20,20h68a10,10 0,0 1,10 10v48a10,10 0,0 1,-10 10h-68a10,10 0,0 1,-10 -10v-48a10,10 0,0 1,10 -10z" />
<path
android:fillColor="#FF0E1116"
android:pathData="M27,34h54a4,4 0,0 1,4 4v9a4,4 0,0 1,-4 4h-54a4,4 0,0 1,-4 -4v-9a4,4 0,0 1,4 -4zM27,59h54a4,4 0,0 1,4 4v9a4,4 0,0 1,-4 4h-54a4,4 0,0 1,-4 -4v-9a4,4 0,0 1,4 -4z" />
<path
android:fillColor="#FF35D38B"
android:pathData="M33,39a3,3 0,1 0,0.1 0zM33,64a3,3 0,1 0,0.1 0z" />
<path
android:fillColor="#FFFFB84D"
android:pathData="M44,39a3,3 0,1 0,0.1 0zM44,64a3,3 0,1 0,0.1 0z" />
<path
android:fillColor="#00000000"
android:pathData="M29,54h13l7,-9l8,19l7,-10h17"
android:strokeColor="#FF35D38B"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="4" />
</vector>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="@color/command_black"
android:pathData="M54,0a54,54 0,1 1,-0.1 0z" />
<path
android:fillColor="#FF202733"
android:pathData="M25,24h58a9,9 0,0 1,9 9v42a9,9 0,0 1,-9 9h-58a9,9 0,0 1,-9 -9v-42a9,9 0,0 1,9 -9z" />
<path
android:fillColor="#FF0E1116"
android:pathData="M29,36h50a4,4 0,0 1,4 4v8a4,4 0,0 1,-4 4h-50a4,4 0,0 1,-4 -4v-8a4,4 0,0 1,4 -4zM29,60h50a4,4 0,0 1,4 4v8a4,4 0,0 1,-4 4h-50a4,4 0,0 1,-4 -4v-8a4,4 0,0 1,4 -4z" />
<path
android:fillColor="#FF35D38B"
android:pathData="M35,41a3,3 0,1 0,0.1 0zM35,65a3,3 0,1 0,0.1 0z" />
<path
android:fillColor="#00000000"
android:pathData="M31,55h13l7,-9l8,19l7,-10h15"
android:strokeColor="#FF35D38B"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="4" />
</vector>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.NGXHttpMonitoringClient.Launcher" parent="Theme.NGXHttpMonitoringClient">
<item name="android:windowSplashScreenBackground">@color/command_black</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_hero</item>
<item name="android:windowSplashScreenIconBackgroundColor">@color/command_black</item>
</style>
</resources>

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<color name="purple_200">#FFBB86FC</color> <color name="command_black">#FF0E1116</color>
<color name="purple_500">#FF6200EE</color> <color name="command_panel">#FF171C23</color>
<color name="purple_700">#FF3700B3</color> <color name="signal_green">#FF35D38B</color>
<color name="teal_200">#FF03DAC5</color> <color name="signal_cyan">#FF4CC9F0</color>
<color name="teal_700">#FF018786</color> <color name="signal_amber">#FFFFB84D</color>
<color name="black">#FF000000</color> <color name="signal_red">#FFFF5A52</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
</resources> </resources>

View File

@@ -1,5 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.NGXHttpMonitoringClient" parent="android:Theme.Material.Light.NoActionBar" /> <style name="Theme.NGXHttpMonitoringClient" parent="android:style/Theme.Material.NoActionBar">
</resources> <item name="android:windowNoTitle">true</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowLightNavigationBar">false</item>
<item name="android:windowBackground">@color/command_black</item>
<item name="android:fontFamily">sans</item>
<item name="android:colorAccent">@color/signal_green</item>
</style>
<style name="Theme.NGXHttpMonitoringClient.Launcher" parent="Theme.NGXHttpMonitoringClient">
<item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowDisablePreview">false</item>
</style>
</resources>