Implemented the Play Store readiness pass.
This commit is contained in:
@@ -118,3 +118,12 @@ app/src/main/java/net/rodakot/ngxhttpmonitoringclient/
|
|||||||
- Widget layouts: `app/src/main/res/layout/widget_fleet.xml`, `widget_server.xml`, `widget_metric.xml`, `widget_graph.xml`, `widget_incidents.xml`
|
- Widget layouts: `app/src/main/res/layout/widget_fleet.xml`, `widget_server.xml`, `widget_metric.xml`, `widget_graph.xml`, `widget_incidents.xml`
|
||||||
|
|
||||||
These assets are vector drawables, so they scale cleanly across screen densities.
|
These assets are vector drawables, so they scale cleanly across screen densities.
|
||||||
|
|
||||||
|
## Google Play Release
|
||||||
|
|
||||||
|
Release configuration, signing instructions, Play Console metadata, and privacy/data-safety drafts live in:
|
||||||
|
|
||||||
|
- `docs/PLAY_STORE_RELEASE.md`
|
||||||
|
- `docs/PLAY_STORE_DATA_SAFETY.md`
|
||||||
|
- `docs/PRIVACY_POLICY.md`
|
||||||
|
- `fastlane/metadata/android/en-US/`
|
||||||
|
|||||||
@@ -1,8 +1,32 @@
|
|||||||
|
import java.util.Properties
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val localProperties = Properties().apply {
|
||||||
|
val file = rootProject.file("local.properties")
|
||||||
|
if (file.isFile) {
|
||||||
|
file.inputStream().use { load(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun releaseSecret(name: String): String? {
|
||||||
|
return providers.environmentVariable(name).orNull ?: localProperties.getProperty(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
val releaseStoreFile = releaseSecret("NGX_RELEASE_STORE_FILE")
|
||||||
|
val releaseStorePassword = releaseSecret("NGX_RELEASE_STORE_PASSWORD")
|
||||||
|
val releaseKeyAlias = releaseSecret("NGX_RELEASE_KEY_ALIAS")
|
||||||
|
val releaseKeyPassword = releaseSecret("NGX_RELEASE_KEY_PASSWORD")
|
||||||
|
val hasReleaseSigning = listOf(
|
||||||
|
releaseStoreFile,
|
||||||
|
releaseStorePassword,
|
||||||
|
releaseKeyAlias,
|
||||||
|
releaseKeyPassword,
|
||||||
|
).all { !it.isNullOrBlank() }
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "net.rodakot.ngxhttpmonitoringclient"
|
namespace = "net.rodakot.ngxhttpmonitoringclient"
|
||||||
compileSdk {
|
compileSdk {
|
||||||
@@ -15,15 +39,30 @@ android {
|
|||||||
applicationId = "net.rodakot.ngxhttpmonitoringclient"
|
applicationId = "net.rodakot.ngxhttpmonitoringclient"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 1
|
versionCode = (providers.gradleProperty("VERSION_CODE").orNull ?: "1").toInt()
|
||||||
versionName = "1.0"
|
versionName = providers.gradleProperty("VERSION_NAME").orNull ?: "1.0.0"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
if (hasReleaseSigning) {
|
||||||
|
create("release") {
|
||||||
|
storeFile = file(releaseStoreFile!!)
|
||||||
|
storePassword = releaseStorePassword
|
||||||
|
keyAlias = releaseKeyAlias
|
||||||
|
keyPassword = releaseKeyPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = true
|
||||||
|
isShrinkResources = true
|
||||||
|
if (hasReleaseSigning) {
|
||||||
|
signingConfig = signingConfigs.getByName("release")
|
||||||
|
}
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".NgxMonitorApplication"
|
android:name=".NgxMonitorApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="false"
|
||||||
|
android:appCategory="productivity"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
|||||||
@@ -587,17 +587,27 @@ private fun HistoryCockpit(history: List<MetricSummary>) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SettingsCockpit(server: ServerProfile, controller: MonitorController) {
|
private fun SettingsCockpit(server: ServerProfile, controller: MonitorController) {
|
||||||
DataPanel("Alert Rules") {
|
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
DataPanel("Alert Rules") {
|
||||||
RuleRow("CPU limit", "${server.alertOverrides.cpuPercent ?: DefaultCpuThreshold}%")
|
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
RuleRow("Memory limit", "${server.alertOverrides.memoryPercent ?: DefaultMemoryThreshold}%")
|
RuleRow("CPU limit", "${server.alertOverrides.cpuPercent ?: DefaultCpuThreshold}%")
|
||||||
RuleRow("Disk limit", "${server.alertOverrides.diskPercent ?: DefaultDiskThreshold}%")
|
RuleRow("Memory limit", "${server.alertOverrides.memoryPercent ?: DefaultMemoryThreshold}%")
|
||||||
RuleRow("p95 latency", "${(server.alertOverrides.latencyP95Millis ?: DefaultLatencyP95ThresholdMs).toInt()} ms")
|
RuleRow("Disk limit", "${server.alertOverrides.diskPercent ?: DefaultDiskThreshold}%")
|
||||||
RuleRow("5xx limit", "${server.alertOverrides.errors5xx ?: DefaultErrors5xxThreshold}")
|
RuleRow("p95 latency", "${(server.alertOverrides.latencyP95Millis ?: DefaultLatencyP95ThresholdMs).toInt()} ms")
|
||||||
RuleRow("Fallback IPs", server.fallbackIpAddresses.ifEmpty { listOf("None") }.joinToString(", "))
|
RuleRow("5xx limit", "${server.alertOverrides.errors5xx ?: DefaultErrors5xxThreshold}")
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
RuleRow("Fallback IPs", server.fallbackIpAddresses.ifEmpty { listOf("None") }.joinToString(", "))
|
||||||
Button(onClick = controller::showEditSelectedServer, shape = RoundedCornerShape(8.dp)) { Text("Edit") }
|
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
OutlinedButton(onClick = controller::deleteSelectedServer, shape = RoundedCornerShape(8.dp)) { Text("Delete") }
|
Button(onClick = controller::showEditSelectedServer, shape = RoundedCornerShape(8.dp)) { Text("Edit") }
|
||||||
|
OutlinedButton(onClick = controller::deleteSelectedServer, shape = RoundedCornerShape(8.dp)) { Text("Delete") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DataPanel("Privacy") {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||||
|
RuleRow("Storage", "Server profiles, credentials, samples, alerts, and widget choices stay on this device.")
|
||||||
|
RuleRow("Credentials", "Monitor tokens and Basic Auth passwords are encrypted with Android Keystore.")
|
||||||
|
RuleRow("Network", "The app connects only to monitor endpoints configured by the user.")
|
||||||
|
RuleRow("Backups", "Android cloud backup and device transfer are disabled for app data.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.content.Intent
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
@@ -66,14 +67,14 @@ abstract class BaseWidgetConfigureActivity : Activity() {
|
|||||||
orientation = RadioGroup.VERTICAL
|
orientation = RadioGroup.VERTICAL
|
||||||
servers.forEachIndexed { index, server ->
|
servers.forEachIndexed { index, server ->
|
||||||
addView(RadioButton(this@BaseWidgetConfigureActivity).apply {
|
addView(RadioButton(this@BaseWidgetConfigureActivity).apply {
|
||||||
id = index + 1
|
id = View.generateViewId()
|
||||||
text = server.name
|
text = server.name
|
||||||
setTextColor(Color.rgb(234, 240, 247))
|
setTextColor(Color.rgb(234, 240, 247))
|
||||||
textSize = 16f
|
textSize = 16f
|
||||||
setPadding(0, 10, 0, 10)
|
setPadding(0, 10, 0, 10)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
check(1)
|
check(getChildAt(0).id)
|
||||||
}
|
}
|
||||||
val scroll = ScrollView(this).apply {
|
val scroll = ScrollView(this).apply {
|
||||||
addView(serverGroup)
|
addView(serverGroup)
|
||||||
@@ -98,7 +99,8 @@ abstract class BaseWidgetConfigureActivity : Activity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
root.addView(actionButton("Add ${mode.buttonLabel}") {
|
root.addView(actionButton("Add ${mode.buttonLabel}") {
|
||||||
val serverIndex = (serverGroup.checkedRadioButtonId - 1).coerceAtLeast(0)
|
val checkedView = serverGroup.findViewById<RadioButton>(serverGroup.checkedRadioButtonId)
|
||||||
|
val serverIndex = serverGroup.indexOfChild(checkedView).coerceAtLeast(0)
|
||||||
val server = servers[serverIndex]
|
val server = servers[serverIndex]
|
||||||
when (mode) {
|
when (mode) {
|
||||||
WidgetConfigureMode.Server -> {
|
WidgetConfigureMode.Server -> {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:windowActionBar">false</item>
|
<item name="android:windowActionBar">false</item>
|
||||||
<item name="android:windowLightStatusBar">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:windowBackground">@color/command_black</item>
|
||||||
<item name="android:fontFamily">sans</item>
|
<item name="android:fontFamily">sans</item>
|
||||||
<item name="android:colorAccent">@color/signal_green</item>
|
<item name="android:colorAccent">@color/signal_green</item>
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
Sample backup rules file; uncomment and customize as necessary.
|
|
||||||
See https://developer.android.com/guide/topics/data/autobackup
|
|
||||||
for details.
|
|
||||||
Note: This file is ignored for devices older than API 31
|
|
||||||
See https://developer.android.com/about/versions/12/backup-restore
|
|
||||||
-->
|
|
||||||
<full-backup-content>
|
<full-backup-content>
|
||||||
<!--
|
<exclude
|
||||||
<include domain="sharedpref" path="."/>
|
domain="database"
|
||||||
<exclude domain="sharedpref" path="device.xml"/>
|
path="." />
|
||||||
-->
|
<exclude
|
||||||
</full-backup-content>
|
domain="sharedpref"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="file"
|
||||||
|
path="." />
|
||||||
|
</full-backup-content>
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
Sample data extraction rules file; uncomment and customize as necessary.
|
|
||||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
|
||||||
for details.
|
|
||||||
-->
|
|
||||||
<data-extraction-rules>
|
<data-extraction-rules>
|
||||||
<cloud-backup>
|
<cloud-backup>
|
||||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
<exclude
|
||||||
<include .../>
|
domain="database"
|
||||||
<exclude .../>
|
path="." />
|
||||||
-->
|
<exclude
|
||||||
|
domain="sharedpref"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="file"
|
||||||
|
path="." />
|
||||||
</cloud-backup>
|
</cloud-backup>
|
||||||
<!--
|
|
||||||
<device-transfer>
|
<device-transfer>
|
||||||
<include .../>
|
<exclude
|
||||||
<exclude .../>
|
domain="database"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="sharedpref"
|
||||||
|
path="." />
|
||||||
|
<exclude
|
||||||
|
domain="file"
|
||||||
|
path="." />
|
||||||
</device-transfer>
|
</device-transfer>
|
||||||
-->
|
</data-extraction-rules>
|
||||||
</data-extraction-rules>
|
|
||||||
|
|||||||
37
docs/PLAY_STORE_DATA_SAFETY.md
Normal file
37
docs/PLAY_STORE_DATA_SAFETY.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Play Console Data Safety Notes
|
||||||
|
|
||||||
|
Use this as a starting point for the Play Console Data safety form. Review it before submission because the final answers are your legal responsibility.
|
||||||
|
|
||||||
|
## App Data Flow
|
||||||
|
|
||||||
|
NGX Monitor is a client for monitor endpoints configured by the user. The developer does not operate a backend for this app.
|
||||||
|
|
||||||
|
Stored locally on the device:
|
||||||
|
|
||||||
|
- Server names, base URLs, tags, fallback LAN IPs, and alert thresholds
|
||||||
|
- Monitor API tokens and optional Basic Auth credentials, encrypted with Android Keystore
|
||||||
|
- Metric samples, alert history, route diagnostics, and widget preferences
|
||||||
|
|
||||||
|
Transmitted by the app:
|
||||||
|
|
||||||
|
- HTTP(S) requests to user-configured monitor endpoints
|
||||||
|
- Authentication headers only for the specific endpoint configured by the user
|
||||||
|
- Notification content generated locally on the device
|
||||||
|
|
||||||
|
Not used by the app:
|
||||||
|
|
||||||
|
- Advertising ID
|
||||||
|
- Location APIs
|
||||||
|
- Contacts, photos, camera, microphone, calendar, or SMS
|
||||||
|
- Developer-operated analytics or crash reporting SDKs
|
||||||
|
|
||||||
|
## Suggested Data Safety Answers
|
||||||
|
|
||||||
|
- Data collected by developer: No
|
||||||
|
- Data shared with third parties by developer: No
|
||||||
|
- Data is encrypted in transit: Yes, when users configure HTTPS monitor endpoints
|
||||||
|
- Users can request data deletion: Data is local; users can delete server profiles in-app or clear app data
|
||||||
|
- App type: utility/productivity
|
||||||
|
- Account creation: Not required
|
||||||
|
|
||||||
|
Important note for the form: the app allows user-configured HTTP endpoints for private LAN monitoring. If you publish with HTTP support enabled, disclose that encryption in transit depends on the endpoint configured by the user.
|
||||||
92
docs/PLAY_STORE_RELEASE.md
Normal file
92
docs/PLAY_STORE_RELEASE.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# Google Play Release Checklist
|
||||||
|
|
||||||
|
This project is configured to build Google Play compatible Android App Bundles.
|
||||||
|
|
||||||
|
## Current Project Readiness
|
||||||
|
|
||||||
|
- Package name: `net.rodakot.ngxhttpmonitoringclient`
|
||||||
|
- Minimum SDK: 24
|
||||||
|
- Target SDK: 36
|
||||||
|
- Version: `VERSION_NAME` / `VERSION_CODE` in `gradle.properties`
|
||||||
|
- Release build: R8 minification enabled, resource shrinking enabled
|
||||||
|
- App data backup: disabled for app data, databases, shared preferences, and files
|
||||||
|
- Release signing: read from environment variables or `local.properties`
|
||||||
|
|
||||||
|
## Create An Upload Key
|
||||||
|
|
||||||
|
Create an upload keystore outside the repository:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
keytool -genkeypair `
|
||||||
|
-v `
|
||||||
|
-keystore "C:\secure\keys\ngx-monitor-upload.jks" `
|
||||||
|
-storetype JKS `
|
||||||
|
-keyalg RSA `
|
||||||
|
-keysize 4096 `
|
||||||
|
-validity 10000 `
|
||||||
|
-alias ngx-monitor-upload
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy `local.properties.example` to `local.properties` and set:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
NGX_RELEASE_STORE_FILE=C:\Users\meghdad\keystores\ngx-monitor-upload.jks
|
||||||
|
NGX_RELEASE_STORE_PASSWORD=<redacted>
|
||||||
|
NGX_RELEASE_KEY_ALIAS=ngx-monitor-upload
|
||||||
|
NGX_RELEASE_KEY_PASSWORD=<redacted>
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not commit `local.properties` or the keystore.
|
||||||
|
|
||||||
|
## Build Release Artifacts
|
||||||
|
|
||||||
|
Unsigned release sanity build:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:GRADLE_USER_HOME='C:\Users\meghdad\AndroidStudioProjects\NGXhttpMonitoringClient\.gradle-user'
|
||||||
|
.\gradlew.bat :app:bundleRelease --console=plain
|
||||||
|
```
|
||||||
|
|
||||||
|
Signed upload bundle, after signing values are configured:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$env:GRADLE_USER_HOME='C:\Users\meghdad\AndroidStudioProjects\NGXhttpMonitoringClient\.gradle-user'
|
||||||
|
.\gradlew.bat :app:bundleRelease --console=plain
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/build/outputs/bundle/release/app-release.aab
|
||||||
|
```
|
||||||
|
|
||||||
|
## Before Uploading To Play Console
|
||||||
|
|
||||||
|
1. Create the app in Play Console.
|
||||||
|
2. Enable Play App Signing.
|
||||||
|
3. Upload `app-release.aab`.
|
||||||
|
4. Fill the store listing using `fastlane/metadata/android/en-US`.
|
||||||
|
5. Upload phone screenshots and a 1024x500 feature graphic.
|
||||||
|
6. Host `docs/PRIVACY_POLICY.md` as a public web page and paste that URL into Play Console.
|
||||||
|
7. Complete Data Safety using `docs/PLAY_STORE_DATA_SAFETY.md`.
|
||||||
|
8. Complete Content Rating. Suggested category: utility/productivity/server monitoring.
|
||||||
|
9. Complete Target Audience. This app is for server administrators, not children.
|
||||||
|
10. Run an internal test release before production.
|
||||||
|
|
||||||
|
## Release Version Bump
|
||||||
|
|
||||||
|
For every Play upload, increment `VERSION_CODE` in `gradle.properties`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
VERSION_CODE=2
|
||||||
|
VERSION_NAME=1.0.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Official References
|
||||||
|
|
||||||
|
- Google Play target API requirements: https://support.google.com/googleplay/android-developer/answer/11926878
|
||||||
|
- Android app signing: https://developer.android.com/studio/publish/app-signing
|
||||||
|
- Build and upload an Android App Bundle: https://developer.android.com/guide/app-bundle
|
||||||
|
- Google Play Data safety: https://support.google.com/googleplay/android-developer/answer/10787469
|
||||||
37
docs/PRIVACY_POLICY.md
Normal file
37
docs/PRIVACY_POLICY.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# NGX Monitor Privacy Policy
|
||||||
|
|
||||||
|
Effective date: 2026-05-11
|
||||||
|
|
||||||
|
NGX Monitor is a mobile client for monitoring Nginx servers that you configure in the app.
|
||||||
|
|
||||||
|
## Data Stored On Your Device
|
||||||
|
|
||||||
|
The app stores server profiles, monitor endpoint URLs, optional fallback LAN IP addresses, tags, alert thresholds, metric history, route diagnostics, alerts, and widget preferences on your device.
|
||||||
|
|
||||||
|
If you save monitor tokens or Basic Auth credentials, the app encrypts them with Android Keystore before storing them locally.
|
||||||
|
|
||||||
|
Android cloud backup and device transfer are disabled for app data.
|
||||||
|
|
||||||
|
## Data Sent By The App
|
||||||
|
|
||||||
|
The app sends monitor API requests only to endpoints that you configure. If you configure authentication, the app sends the configured token or Basic Auth credentials to that monitor endpoint.
|
||||||
|
|
||||||
|
The app does not send your monitor data, credentials, server list, or usage data to the app developer.
|
||||||
|
|
||||||
|
## Network Security
|
||||||
|
|
||||||
|
HTTPS is recommended for monitor endpoints. The app also supports HTTP endpoints for private LAN monitoring when the user explicitly configures them.
|
||||||
|
|
||||||
|
## Third Parties
|
||||||
|
|
||||||
|
The app does not include advertising SDKs, analytics SDKs, or developer-operated crash reporting SDKs.
|
||||||
|
|
||||||
|
Google Play may process app install, billing, review, crash, and device information according to Google's own policies.
|
||||||
|
|
||||||
|
## Data Deletion
|
||||||
|
|
||||||
|
You can delete server profiles inside the app. You can also remove all app data from Android system settings by clearing the app's storage or uninstalling the app.
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
Replace this section with the publisher support email before submitting the app to Google Play.
|
||||||
7
fastlane/metadata/android/en-US/changelogs/1.txt
Normal file
7
fastlane/metadata/android/en-US/changelogs/1.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
Initial release.
|
||||||
|
|
||||||
|
- Fleet monitor for Nginx monitor endpoints
|
||||||
|
- Server detail cockpit with system, request, disk, alert, history, and route diagnostics
|
||||||
|
- VPN and DNS resilience with fallback LAN IPs
|
||||||
|
- Home-screen widgets for fleet status, server bars, metric sparklines, telemetry graphs, and incidents
|
||||||
|
- Local encrypted credential storage
|
||||||
15
fastlane/metadata/android/en-US/full_description.txt
Normal file
15
fastlane/metadata/android/en-US/full_description.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
NGX Monitor is a focused Android client for Nginx monitor endpoints.
|
||||||
|
|
||||||
|
Track CPU, memory, storage, request rate, latency, connection state, HTTP errors, and route health from your phone. The app is designed for server administrators who monitor multiple Nginx hosts and need a fast, readable mobile command deck.
|
||||||
|
|
||||||
|
Key features:
|
||||||
|
- Monitor 20+ servers from one fleet view
|
||||||
|
- View live CPU, memory, storage, request, latency, and connection signals
|
||||||
|
- Follow Nginx worker, request, upstream, disk, history, alert, and route diagnostics
|
||||||
|
- Configure monitor API tokens and optional Basic Auth credentials
|
||||||
|
- Store credentials locally with Android Keystore encryption
|
||||||
|
- Use fallback LAN IPs for VPN, DNS, and local network routing problems
|
||||||
|
- Get Android notifications for degraded or unreachable servers
|
||||||
|
- Add home-screen widgets for fleet status, server bars, metric sparklines, telemetry graphs, and incident watch
|
||||||
|
|
||||||
|
NGX Monitor connects only to the monitor endpoints you configure. HTTPS endpoints are recommended, and HTTP endpoints are supported for private LAN monitoring.
|
||||||
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Monitor Nginx servers, alerts, routes, and home-screen widgets.
|
||||||
1
fastlane/metadata/android/en-US/title.txt
Normal file
1
fastlane/metadata/android/en-US/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
NGX Monitor
|
||||||
@@ -12,4 +12,6 @@ org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|||||||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
# Kotlin code style for this project: "official" or "obsolete":
|
# Kotlin code style for this project: "official" or "obsolete":
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
|
VERSION_CODE=1
|
||||||
|
VERSION_NAME=1.0.0
|
||||||
|
|||||||
9
local.properties.example
Normal file
9
local.properties.example
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
## Copy these values into local.properties or export them as environment variables
|
||||||
|
## before building the signed Play upload bundle.
|
||||||
|
##
|
||||||
|
## Do not commit local.properties or the keystore file.
|
||||||
|
|
||||||
|
NGX_RELEASE_STORE_FILE=C:\\path\\to\\ngx-monitor-upload.jks
|
||||||
|
NGX_RELEASE_STORE_PASSWORD=replace-with-keystore-password
|
||||||
|
NGX_RELEASE_KEY_ALIAS=ngx-monitor-upload
|
||||||
|
NGX_RELEASE_KEY_PASSWORD=replace-with-key-password
|
||||||
Reference in New Issue
Block a user