Added report manager for seizure logging
This commit is contained in:
@@ -97,7 +97,7 @@ public class LogManager {
|
||||
private boolean mLogRemote;
|
||||
private boolean mLogRemoteMobile;
|
||||
private String mAuthToken;
|
||||
static private SQLiteDatabase mOsdDb = null; // SQLite Database for data and log entries.
|
||||
static public SQLiteDatabase mOsdDb = null; // SQLite Database for data and log entries.
|
||||
private RemoteLogTimer mRemoteLogTimer;
|
||||
private boolean mLogNDA;
|
||||
public NDATimer mNDATimer;
|
||||
@@ -568,7 +568,7 @@ public class LogManager {
|
||||
//long endDateMillis = currentDateMillis - 3600*1000* mDataRetentionPeriod; // Using hours rather than days for testing
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String endDateStr = dateFormat.format(new Date(endDateMillis));
|
||||
String[] tableNames = new String[]{mDpTableName, mEventsTableName};
|
||||
String[] tableNames = new String[]{mDpTableName};
|
||||
for (String tableName : tableNames) {
|
||||
Log.i(TAG, "pruneLocalDb - pruning table " + tableName);
|
||||
try {
|
||||
|
||||
779
app/src/main/java/uk/org/openseizuredetector/ReportManager.java
Normal file
779
app/src/main/java/uk/org/openseizuredetector/ReportManager.java
Normal file
@@ -0,0 +1,779 @@
|
||||
package uk.org.openseizuredetector;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class ReportManager {
|
||||
private static final String TAG = "ReportManager";
|
||||
private static final int GROUP_THRESHOLD_SECS = 30;
|
||||
private static final int MONTHS_TO_SHOW = 12;
|
||||
|
||||
public static String generateHtmlReport(SQLiteDatabase db, int days) {
|
||||
if (db == null) {
|
||||
return "<html><body><h1>Error: Database not available</h1></body></html>";
|
||||
}
|
||||
|
||||
// Fetch full calendar months for the report selector. This avoids the month dropdown
|
||||
// losing the previous month when the app moves into a new month.
|
||||
String query = "SELECT dataTime, status, type, notes, dataJSON " +
|
||||
"FROM events " +
|
||||
"WHERE dataTime >= date('now', 'start of month', '-" + (MONTHS_TO_SHOW - 1) + " months') " +
|
||||
"AND status IN (1, 2, 3, 5) " +
|
||||
"ORDER BY dataTime ASC";
|
||||
|
||||
Cursor cursor = null;
|
||||
ArrayList<SeizureEvent> rawEvents = new ArrayList<>();
|
||||
|
||||
try {
|
||||
cursor = db.rawQuery(query, null);
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.UK);
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
SeizureEvent event = new SeizureEvent();
|
||||
event.dataTime = cursor.getString(0);
|
||||
event.status = cursor.getInt(1);
|
||||
event.type = cursor.getString(2);
|
||||
event.notes = cursor.getString(3);
|
||||
|
||||
try {
|
||||
String dataJson = cursor.getString(4);
|
||||
if (dataJson != null) {
|
||||
JSONObject jo = new JSONObject(dataJson);
|
||||
event.hr = jo.optDouble("hr", 0.0);
|
||||
event.alarmPhrase = jo.optString("alarmPhrase", "");
|
||||
event.alarmCause = jo.optString("alarmCause", "").trim();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Error parsing dataJSON: " + e.getMessage());
|
||||
}
|
||||
|
||||
try {
|
||||
event.date = sdf.parse(event.dataTime);
|
||||
} catch (ParseException e) {
|
||||
Log.w(TAG, "Error parsing date: " + event.dataTime);
|
||||
}
|
||||
|
||||
rawEvents.add(event);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error querying database: " + e.getMessage());
|
||||
return "<html><body><h1>Error querying database: " + e.getMessage() + "</h1></body></html>";
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
|
||||
ArrayList<SeizureGroup> groups = groupEvents(rawEvents);
|
||||
return buildCalendarHtml(groups, days);
|
||||
}
|
||||
|
||||
private static ArrayList<SeizureGroup> groupEvents(ArrayList<SeizureEvent> events) {
|
||||
ArrayList<SeizureGroup> groups = new ArrayList<>();
|
||||
if (events.isEmpty()) return groups;
|
||||
|
||||
SeizureGroup currentGroup = null;
|
||||
|
||||
for (SeizureEvent event : events) {
|
||||
if (currentGroup == null) {
|
||||
currentGroup = new SeizureGroup(event);
|
||||
} else {
|
||||
long diffSecs = 0;
|
||||
if (event.date != null && currentGroup.lastDate != null) {
|
||||
diffSecs = (event.date.getTime() - currentGroup.lastDate.getTime()) / 1000;
|
||||
}
|
||||
|
||||
if (diffSecs <= GROUP_THRESHOLD_SECS) {
|
||||
currentGroup.lastDate = event.date;
|
||||
currentGroup.lastEvent = event;
|
||||
if (event.notes != null && event.notes.contains("Duration:")) {
|
||||
currentGroup.durationStr = extractDuration(event.notes);
|
||||
}
|
||||
if (event.hr > 0) {
|
||||
currentGroup.hr = event.hr;
|
||||
}
|
||||
} else {
|
||||
groups.add(currentGroup);
|
||||
currentGroup = new SeizureGroup(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentGroup != null) {
|
||||
groups.add(currentGroup);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
private static String extractDuration(String notes) {
|
||||
try {
|
||||
int start = notes.indexOf("Duration:") + 9;
|
||||
int end = notes.indexOf("HR:");
|
||||
if (end == -1) end = notes.length();
|
||||
return notes.substring(start, end).trim();
|
||||
} catch (Exception e) {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private static String getTimeOfDayColor(Date date) {
|
||||
if (date == null) return "#888888";
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
int hour = cal.get(Calendar.HOUR_OF_DAY);
|
||||
if (hour >= 0 && hour < 6) return "#3b4a6b"; // Night - dark blue
|
||||
if (hour >= 6 && hour < 12) return "#e6a817"; // Morning - yellow
|
||||
if (hour >= 12 && hour < 18) return "#e07b2a"; // Afternoon - orange
|
||||
return "#7b4fa6"; // Evening - purple
|
||||
}
|
||||
|
||||
private static String getTimeOfDayLabel(Date date) {
|
||||
if (date == null) return "";
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
int hour = cal.get(Calendar.HOUR_OF_DAY);
|
||||
if (hour >= 0 && hour < 6) return "Night";
|
||||
if (hour >= 6 && hour < 12) return "Morning";
|
||||
if (hour >= 12 && hour < 18) return "Afternoon";
|
||||
return "Evening";
|
||||
}
|
||||
|
||||
private static int getMinutesOfDay(Date date) {
|
||||
if (date == null) return 0;
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.setTime(date);
|
||||
return cal.get(Calendar.HOUR_OF_DAY) * 60 + cal.get(Calendar.MINUTE);
|
||||
}
|
||||
|
||||
private static int parseDurationMinutes(String durationStr) {
|
||||
if (durationStr == null) return 5;
|
||||
|
||||
String s = durationStr.trim().toLowerCase(Locale.UK);
|
||||
if (s.isEmpty() || s.equals("n/a") || s.equals("unknown")) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
try {
|
||||
// Supports HH:MM:SS
|
||||
if (s.matches("\\d{1,2}:\\d{1,2}:\\d{1,2}")) {
|
||||
String[] parts = s.split(":");
|
||||
int hours = Integer.parseInt(parts[0]);
|
||||
int mins = Integer.parseInt(parts[1]);
|
||||
int secs = Integer.parseInt(parts[2]);
|
||||
int totalSeconds = hours * 3600 + mins * 60 + secs;
|
||||
return Math.max(1, (int) Math.ceil(totalSeconds / 60.0));
|
||||
}
|
||||
|
||||
// Supports MM:SS
|
||||
if (s.matches("\\d{1,2}:\\d{1,2}")) {
|
||||
String[] parts = s.split(":");
|
||||
int mins = Integer.parseInt(parts[0]);
|
||||
int secs = Integer.parseInt(parts[1]);
|
||||
int totalSeconds = mins * 60 + secs;
|
||||
return Math.max(1, (int) Math.ceil(totalSeconds / 60.0));
|
||||
}
|
||||
|
||||
// Supports text like "2 min 30 sec" or "1 hour 5 min"
|
||||
int totalMinutes = 0;
|
||||
String[] tokens = s.replace(",", " ").split("\\s+");
|
||||
for (int i = 0; i < tokens.length - 1; i++) {
|
||||
try {
|
||||
int value = Integer.parseInt(tokens[i]);
|
||||
String unit = tokens[i + 1];
|
||||
if (unit.startsWith("hour") || unit.startsWith("hr")) {
|
||||
totalMinutes += value * 60;
|
||||
} else if (unit.startsWith("min")) {
|
||||
totalMinutes += value;
|
||||
} else if (unit.startsWith("sec")) {
|
||||
if (value > 0) totalMinutes += 1;
|
||||
}
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Keep checking the rest of the duration text.
|
||||
}
|
||||
}
|
||||
|
||||
if (totalMinutes > 0) return totalMinutes;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
|
||||
return 5;
|
||||
}
|
||||
|
||||
private static String getStatusColor(int status) {
|
||||
switch (status) {
|
||||
case 1: return "#f59e0b"; // WARNING
|
||||
case 2: return "#ef4444"; // ALARM
|
||||
case 3: return "#8b5cf6"; // FALL
|
||||
case 5: return "#06b6d4"; // MANUAL
|
||||
default: return "#6b7280";
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatPct(double value) {
|
||||
return String.format(Locale.UK, "%.2f", value);
|
||||
}
|
||||
|
||||
private static String statusToString(int status) {
|
||||
switch (status) {
|
||||
case 1: return "WARNING";
|
||||
case 2: return "ALARM";
|
||||
case 3: return "FALL";
|
||||
case 5: return "MANUAL";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
private static String statusToCssClass(int status) {
|
||||
switch (status) {
|
||||
case 1: return "warning";
|
||||
case 2: return "alarm";
|
||||
case 3: return "fall";
|
||||
case 5: return "manual";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
private static String escapeHtml(String value) {
|
||||
if (value == null) return "";
|
||||
return value.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace("\"", """)
|
||||
.replace("'", "'");
|
||||
}
|
||||
|
||||
private static void appendSummaryBar(StringBuilder sb, String label, String range, String cssClass,
|
||||
int count, int maxCount) {
|
||||
double pct = maxCount > 0 ? (count / (double) maxCount) * 100.0 : 0.0;
|
||||
sb.append("<div class='time-summary-row'>");
|
||||
sb.append("<div>").append(label).append("<br><span style='color:#6b7280;font-size:11px'>")
|
||||
.append(range).append("</span></div>");
|
||||
sb.append("<div class='summary-bar-track'><div class='summary-bar ").append(cssClass)
|
||||
.append("' style='width:").append(formatPct(pct)).append("%'></div></div>");
|
||||
sb.append("<div class='summary-count'>").append(count).append("</div>");
|
||||
sb.append("</div>");
|
||||
}
|
||||
|
||||
private static String buildCalendarHtml(ArrayList<SeizureGroup> groups, int days) {
|
||||
SimpleDateFormat dayKeyFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.UK);
|
||||
SimpleDateFormat timeFormat = new SimpleDateFormat("HH:mm:ss", Locale.UK);
|
||||
SimpleDateFormat monthFormat = new SimpleDateFormat("MMMM yyyy", Locale.UK);
|
||||
SimpleDateFormat monthIdFormat = new SimpleDateFormat("yyyy-MM", Locale.UK);
|
||||
|
||||
// Group seizures by day and by month.
|
||||
Map<String, ArrayList<SeizureGroup>> byDay = new HashMap<>();
|
||||
Map<String, Integer> eventsByMonth = new HashMap<>();
|
||||
for (SeizureGroup g : groups) {
|
||||
if (g.startDate != null) {
|
||||
String dayKey = dayKeyFormat.format(g.startDate);
|
||||
if (!byDay.containsKey(dayKey)) {
|
||||
byDay.put(dayKey, new ArrayList<SeizureGroup>());
|
||||
}
|
||||
byDay.get(dayKey).add(g);
|
||||
|
||||
String monthKey = monthIdFormat.format(g.startDate);
|
||||
Integer count = eventsByMonth.get(monthKey);
|
||||
eventsByMonth.put(monthKey, count == null ? 1 : count + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Calendar todayCal = Calendar.getInstance();
|
||||
Date today = todayCal.getTime();
|
||||
|
||||
// Build a fixed list of recent full months so the dropdown remains useful across
|
||||
// month boundaries, for example allowing April to be selected after May begins.
|
||||
Calendar firstMonth = Calendar.getInstance();
|
||||
firstMonth.add(Calendar.MONTH, -(MONTHS_TO_SHOW - 1));
|
||||
firstMonth.set(Calendar.DAY_OF_MONTH, 1);
|
||||
firstMonth.set(Calendar.HOUR_OF_DAY, 0);
|
||||
firstMonth.set(Calendar.MINUTE, 0);
|
||||
firstMonth.set(Calendar.SECOND, 0);
|
||||
firstMonth.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
Calendar lastMonth = Calendar.getInstance();
|
||||
lastMonth.set(Calendar.DAY_OF_MONTH, 1);
|
||||
lastMonth.set(Calendar.HOUR_OF_DAY, 0);
|
||||
lastMonth.set(Calendar.MINUTE, 0);
|
||||
lastMonth.set(Calendar.SECOND, 0);
|
||||
lastMonth.set(Calendar.MILLISECOND, 0);
|
||||
|
||||
ArrayList<Calendar> reportMonths = new ArrayList<>();
|
||||
Calendar monthCursor = (Calendar) firstMonth.clone();
|
||||
while (!monthCursor.after(lastMonth)) {
|
||||
reportMonths.add((Calendar) monthCursor.clone());
|
||||
monthCursor.add(Calendar.MONTH, 1);
|
||||
}
|
||||
|
||||
String defaultMonthId = monthIdFormat.format(today);
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<!DOCTYPE html><html><head>");
|
||||
sb.append("<meta charset='UTF-8'>");
|
||||
sb.append("<meta name='viewport' content='width=device-width, initial-scale=1'>");
|
||||
sb.append("<title>ClinX02 Seizure Report</title>");
|
||||
sb.append("<style>");
|
||||
sb.append("* { box-sizing: border-box; }");
|
||||
sb.append("body { font-family: Arial, sans-serif; margin: 0; padding: 16px; background: #eef1f5; color: #222; }");
|
||||
sb.append(".container { max-width: 1100px; margin: 0 auto; }");
|
||||
sb.append("h1 { color: #1f2937; margin: 0 0 6px 0; font-size: 28px; }");
|
||||
sb.append("h2 { color: #374151; margin-top: 24px; }");
|
||||
sb.append(".subtitle { color: #6b7280; margin-top: 0; margin-bottom: 18px; }");
|
||||
sb.append(".summary { background: white; border-radius: 12px; padding: 18px; margin-bottom: 18px; border: 1px solid #ddd; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }");
|
||||
sb.append(".stats { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 12px; }");
|
||||
sb.append(".stat-card { flex: 1 1 160px; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 10px; padding: 12px; }");
|
||||
sb.append(".stat-label { font-size: 12px; color: #6b7280; }");
|
||||
sb.append(".stat-value { font-size: 22px; font-weight: bold; color: #111827; margin-top: 4px; }");
|
||||
sb.append(".download-btn { display: inline-block; margin-top: 14px; background: #2563eb; color: white; text-decoration: none; padding: 10px 14px; border-radius: 8px; font-weight: bold; }");
|
||||
sb.append(".download-btn:hover { background: #1d4ed8; }");
|
||||
sb.append(".legend { display: flex; gap: 16px; margin: 12px 0 18px 0; flex-wrap: wrap; background: white; border: 1px solid #ddd; border-radius: 12px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.04); }");
|
||||
sb.append(".legend-item { display: flex; align-items: center; gap: 6px; font-size: 13px; color: #374151; }");
|
||||
sb.append(".legend-dot { width: 12px; height: 12px; border-radius: 50%; flex: 0 0 auto; }");
|
||||
sb.append(".month-controls { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 16px 0; background: white; border: 1px solid #ddd; border-radius: 12px; padding: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.04); }");
|
||||
sb.append(".month-controls label { font-weight: bold; color: #374151; }");
|
||||
sb.append(".month-controls select { padding: 8px 10px; border: 1px solid #d1d5db; border-radius: 8px; background: white; font-size: 14px; min-width: 190px; }");
|
||||
sb.append(".month-count { color: #6b7280; font-size: 13px; }");
|
||||
sb.append(".month-panel { display: none; }");
|
||||
sb.append(".month-panel.active { display: block; }");
|
||||
sb.append(".month-summary-panel { display: none; background: white; border: 1px solid #ddd; border-radius: 12px; padding: 14px; margin: 14px 0 18px 0; box-shadow: 0 2px 8px rgba(0,0,0,0.04); }");
|
||||
sb.append(".month-summary-panel.active { display: block; }");
|
||||
sb.append(".summary-chart-title { font-weight: bold; color: #374151; margin-bottom: 10px; }");
|
||||
sb.append(".time-summary-row { display: grid; grid-template-columns: 90px 1fr 44px; gap: 10px; align-items: center; margin: 8px 0; font-size: 13px; }");
|
||||
sb.append(".summary-bar-track { height: 18px; background: #f3f4f6; border-radius: 999px; overflow: hidden; border: 1px solid #e5e7eb; }");
|
||||
sb.append(".summary-bar { height: 100%; min-width: 0; border-radius: 999px; }");
|
||||
sb.append(".summary-bar.night { background: #9ca3af; }");
|
||||
sb.append(".summary-bar.morning { background: #f59e0b; }");
|
||||
sb.append(".summary-bar.afternoon { background: #f97316; }");
|
||||
sb.append(".summary-bar.evening { background: #8b5cf6; }");
|
||||
sb.append(".summary-count { text-align: right; color: #374151; font-weight: bold; }");
|
||||
sb.append(".table-controls { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; background: white; border: 1px solid #ddd; border-radius: 12px; padding: 12px; margin-bottom: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.04); }");
|
||||
sb.append(".table-controls label { font-weight: bold; color: #374151; font-size: 13px; }");
|
||||
sb.append(".table-controls select { padding: 7px 9px; border: 1px solid #d1d5db; border-radius: 8px; background: white; font-size: 13px; }");
|
||||
sb.append(".pager { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }");
|
||||
sb.append(".pager button { padding: 7px 10px; border: 1px solid #d1d5db; border-radius: 8px; background: #f9fafb; cursor: pointer; }");
|
||||
sb.append(".pager button:disabled { opacity: 0.45; cursor: not-allowed; }");
|
||||
sb.append(".table-info { color: #6b7280; font-size: 13px; }");
|
||||
sb.append(".calendar { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; margin-top: 16px; }");
|
||||
sb.append(".day-header { text-align: center; font-weight: bold; padding: 6px; color: #555; font-size: 13px; }");
|
||||
sb.append(".day-box { background: white; border-radius: 8px; padding: 6px; min-height: 86px; border: 1px solid #ddd; box-shadow: 0 1px 4px rgba(0,0,0,0.04); }");
|
||||
sb.append(".day-box.today { border: 2px solid #2563eb; }");
|
||||
sb.append(".day-box.has-seizures { background: #fff8f8; border-color: #fecaca; }");
|
||||
sb.append(".day-box.empty { background: transparent; border: none; box-shadow: none; }");
|
||||
sb.append(".day-num { font-size: 13px; font-weight: bold; color: #333; margin-bottom: 4px; }");
|
||||
sb.append(".timeline { position: relative; height: 28px; margin-top: 6px; border-radius: 6px; overflow: hidden; border: 1px solid #d1d5db; background: white; isolation: isolate; }");
|
||||
sb.append(".time-bg { position: absolute; inset: 0; display: flex; z-index: 0; }");
|
||||
sb.append(".time-segment { height: 100%; flex: 1 1 25%; }");
|
||||
sb.append(".time-night { background: #e5e7eb; }");
|
||||
sb.append(".time-morning { background: #fde68a; }");
|
||||
sb.append(".time-afternoon { background: #fdba74; }");
|
||||
sb.append(".time-evening { background: #c4b5fd; }");
|
||||
sb.append(".timeline-grid { position: absolute; inset: 0; pointer-events: none; z-index: 1; }");
|
||||
sb.append(".time-marker { position: absolute; top: 0; bottom: 0; width: 1px; background: rgba(0,0,0,0.16); }");
|
||||
sb.append(".event-bar { position: absolute; top: 5px; height: 18px; border-radius: 4px; opacity: 0.96; border: 1px solid rgba(0,0,0,0.18); z-index: 2; }");
|
||||
sb.append(".time-scale { display: flex; justify-content: space-between; font-size: 9px; color: #6b7280; margin-top: 3px; }");
|
||||
sb.append(".seizure-count { font-size: 11px; color: #6b7280; margin-top: 4px; }");
|
||||
sb.append(".table-wrap { width: 100%; overflow-x: auto; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.06); }");
|
||||
sb.append(".detail-table { width: 100%; border-collapse: collapse; background: white; overflow: hidden; min-width: 680px; }");
|
||||
sb.append(".detail-table th { background: #2563eb; color: white; padding: 12px; text-align: left; font-size: 13px; }");
|
||||
sb.append(".detail-table td { padding: 10px 12px; border-bottom: 1px solid #eee; font-size: 13px; vertical-align: top; }");
|
||||
sb.append(".detail-table tr:hover { background: #f9fafb; }");
|
||||
sb.append(".detail-table tr:last-child td { border-bottom: none; }");
|
||||
sb.append(".status-pill { display: inline-block; padding: 4px 8px; border-radius: 999px; font-size: 12px; font-weight: bold; }");
|
||||
sb.append(".alarm { background: #fee2e2; color: #b91c1c; }");
|
||||
sb.append(".warning { background: #ffedd5; color: #c2410c; }");
|
||||
sb.append(".fall { background: #ede9fe; color: #6d28d9; }");
|
||||
sb.append(".manual { background: #dcfce7; color: #15803d; }");
|
||||
sb.append(".unknown { background: #e5e7eb; color: #374151; }");
|
||||
sb.append(".no-month-events { display: none; background: white; border: 1px solid #ddd; border-radius: 12px; padding: 14px; color: #6b7280; }");
|
||||
sb.append(".footer { color: #6b7280; margin-top: 18px; }");
|
||||
sb.append("@media (max-width: 700px) {");
|
||||
sb.append("body { padding: 10px; }");
|
||||
sb.append("h1 { font-size: 23px; }");
|
||||
sb.append(".summary { padding: 14px; }");
|
||||
sb.append(".time-summary-row { grid-template-columns: 78px 1fr 34px; gap: 6px; font-size: 11px; }");
|
||||
sb.append(".table-controls { align-items: stretch; }");
|
||||
sb.append(".pager { width: 100%; }");
|
||||
sb.append(".calendar { gap: 2px; }");
|
||||
sb.append(".day-box { min-height: 64px; padding: 4px; border-radius: 6px; }");
|
||||
sb.append(".day-header { font-size: 11px; padding: 4px; }");
|
||||
sb.append(".day-num { font-size: 11px; }");
|
||||
sb.append(".timeline { height: 22px; }");
|
||||
sb.append(".event-bar { top: 4px; height: 14px; }");
|
||||
sb.append(".time-scale { font-size: 8px; }");
|
||||
sb.append(".seizure-count { font-size: 10px; }");
|
||||
sb.append(".detail-table th, .detail-table td { font-size: 11px; padding: 6px; }");
|
||||
sb.append("}");
|
||||
sb.append("</style></head><body>");
|
||||
sb.append("<div class='container'>");
|
||||
|
||||
sb.append("<h1>ClinX02 Seizure Report</h1>");
|
||||
sb.append("<p class='subtitle'>Calendar timeline overview and detailed event log</p>");
|
||||
|
||||
// Summary box
|
||||
sb.append("<div class='summary'>");
|
||||
sb.append("<div class='stats'>");
|
||||
|
||||
sb.append("<div class='stat-card'>");
|
||||
sb.append("<div class='stat-label'>Calendar range</div>");
|
||||
sb.append("<div class='stat-value'>").append(MONTHS_TO_SHOW).append(" months</div>");
|
||||
sb.append("</div>");
|
||||
|
||||
sb.append("<div class='stat-card'>");
|
||||
sb.append("<div class='stat-label'>Total seizure events</div>");
|
||||
sb.append("<div class='stat-value'>").append(groups.size()).append("</div>");
|
||||
sb.append("</div>");
|
||||
|
||||
sb.append("<div class='stat-card'>");
|
||||
sb.append("<div class='stat-label'>Days with seizures</div>");
|
||||
sb.append("<div class='stat-value'>").append(byDay.size()).append("</div>");
|
||||
sb.append("</div>");
|
||||
|
||||
sb.append("</div>");
|
||||
sb.append("<a class='download-btn' href='/report/download?days=").append(days)
|
||||
.append("&token=clinx02secure' download='seizure_report.html'>Download Report</a>");
|
||||
sb.append("</div>");
|
||||
|
||||
// Legend
|
||||
sb.append("<div class='legend'>");
|
||||
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#e5e7eb'></div>Night background (00-06)</div>");
|
||||
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#fde68a'></div>Morning background (06-12)</div>");
|
||||
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#fdba74'></div>Afternoon background (12-18)</div>");
|
||||
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#c4b5fd'></div>Evening background (18-24)</div>");
|
||||
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#f59e0b'></div>Warning bar</div>");
|
||||
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#ef4444'></div>Alarm bar</div>");
|
||||
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#8b5cf6'></div>Fall bar</div>");
|
||||
sb.append("<div class='legend-item'><div class='legend-dot' style='background:#06b6d4'></div>Manual bar</div>");
|
||||
sb.append("</div>");
|
||||
|
||||
// Month selector
|
||||
sb.append("<div class='month-controls'>");
|
||||
sb.append("<label for='monthSelect'>Month:</label>");
|
||||
sb.append("<select id='monthSelect' onchange='showMonth(this.value)'>");
|
||||
for (Calendar reportMonth : reportMonths) {
|
||||
String monthId = monthIdFormat.format(reportMonth.getTime());
|
||||
String selected = monthId.equals(defaultMonthId) ? " selected" : "";
|
||||
Integer eventCount = eventsByMonth.get(monthId);
|
||||
int count = eventCount == null ? 0 : eventCount;
|
||||
sb.append("<option value='").append(monthId).append("'").append(selected).append(">");
|
||||
sb.append(monthFormat.format(reportMonth.getTime())).append(" (").append(count)
|
||||
.append(count == 1 ? " event" : " events").append(")");
|
||||
sb.append("</option>");
|
||||
}
|
||||
sb.append("</select>");
|
||||
sb.append("<span id='selectedMonthCount' class='month-count'></span>");
|
||||
sb.append("</div>");
|
||||
|
||||
// Monthly time-of-day summary chart
|
||||
for (Calendar reportMonth : reportMonths) {
|
||||
String monthId = monthIdFormat.format(reportMonth.getTime());
|
||||
boolean activeMonth = monthId.equals(defaultMonthId);
|
||||
int nightCount = 0;
|
||||
int morningCount = 0;
|
||||
int afternoonCount = 0;
|
||||
int eveningCount = 0;
|
||||
|
||||
for (SeizureGroup g : groups) {
|
||||
if (g.startDate != null && monthId.equals(monthIdFormat.format(g.startDate))) {
|
||||
Calendar eventCal = Calendar.getInstance();
|
||||
eventCal.setTime(g.startDate);
|
||||
int hour = eventCal.get(Calendar.HOUR_OF_DAY);
|
||||
if (hour < 6) {
|
||||
nightCount++;
|
||||
} else if (hour < 12) {
|
||||
morningCount++;
|
||||
} else if (hour < 18) {
|
||||
afternoonCount++;
|
||||
} else {
|
||||
eveningCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int maxTimeCount = Math.max(Math.max(nightCount, morningCount), Math.max(afternoonCount, eveningCount));
|
||||
if (maxTimeCount < 1) maxTimeCount = 1;
|
||||
|
||||
sb.append("<div class='month-summary-panel");
|
||||
if (activeMonth) sb.append(" active");
|
||||
sb.append("' id='summary-").append(monthId).append("' data-month='").append(monthId).append("'>");
|
||||
sb.append("<div class='summary-chart-title'>Seizures by time of day</div>");
|
||||
appendSummaryBar(sb, "Night", "00-06", "night", nightCount, maxTimeCount);
|
||||
appendSummaryBar(sb, "Morning", "06-12", "morning", morningCount, maxTimeCount);
|
||||
appendSummaryBar(sb, "Afternoon", "12-18", "afternoon", afternoonCount, maxTimeCount);
|
||||
appendSummaryBar(sb, "Evening", "18-24", "evening", eveningCount, maxTimeCount);
|
||||
sb.append("</div>");
|
||||
}
|
||||
|
||||
// Calendar months
|
||||
for (Calendar reportMonth : reportMonths) {
|
||||
String monthId = monthIdFormat.format(reportMonth.getTime());
|
||||
boolean activeMonth = monthId.equals(defaultMonthId);
|
||||
|
||||
Calendar cal = (Calendar) reportMonth.clone();
|
||||
int currentMonth = cal.get(Calendar.MONTH);
|
||||
int currentYear = cal.get(Calendar.YEAR);
|
||||
int daysInMonth = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||
int firstDayOfWeek = cal.get(Calendar.DAY_OF_WEEK); // 1=Sun, 2=Mon...
|
||||
|
||||
sb.append("<div class='month-panel");
|
||||
if (activeMonth) sb.append(" active");
|
||||
sb.append("' id='month-").append(monthId).append("' data-month='").append(monthId).append("'>");
|
||||
sb.append("<h2>").append(monthFormat.format(reportMonth.getTime())).append("</h2>");
|
||||
sb.append("<div class='calendar'>");
|
||||
|
||||
String[] dayNames = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
|
||||
for (String d : dayNames) {
|
||||
sb.append("<div class='day-header'>").append(d).append("</div>");
|
||||
}
|
||||
|
||||
for (int i = 1; i < firstDayOfWeek; i++) {
|
||||
sb.append("<div class='day-box empty'></div>");
|
||||
}
|
||||
|
||||
Calendar today2 = Calendar.getInstance();
|
||||
for (int day = 1; day <= daysInMonth; day++) {
|
||||
String dayKey = String.format(Locale.UK, "%04d-%02d-%02d", currentYear, currentMonth + 1, day);
|
||||
ArrayList<SeizureGroup> dayGroups = byDay.get(dayKey);
|
||||
boolean isToday = (day == today2.get(Calendar.DAY_OF_MONTH)
|
||||
&& currentMonth == today2.get(Calendar.MONTH)
|
||||
&& currentYear == today2.get(Calendar.YEAR));
|
||||
|
||||
String boxClass = "day-box";
|
||||
if (isToday) boxClass += " today";
|
||||
if (dayGroups != null && !dayGroups.isEmpty()) boxClass += " has-seizures";
|
||||
|
||||
sb.append("<div class='").append(boxClass).append("'>");
|
||||
sb.append("<div class='day-num'>").append(day).append("</div>");
|
||||
|
||||
if (dayGroups != null && !dayGroups.isEmpty()) {
|
||||
sb.append("<div class='timeline'>");
|
||||
sb.append("<div class='time-bg'>");
|
||||
sb.append("<div class='time-segment time-night'></div>");
|
||||
sb.append("<div class='time-segment time-morning'></div>");
|
||||
sb.append("<div class='time-segment time-afternoon'></div>");
|
||||
sb.append("<div class='time-segment time-evening'></div>");
|
||||
sb.append("</div>");
|
||||
sb.append("<div class='timeline-grid'>");
|
||||
sb.append("<span class='time-marker' style='left:25%'></span>");
|
||||
sb.append("<span class='time-marker' style='left:50%'></span>");
|
||||
sb.append("<span class='time-marker' style='left:75%'></span>");
|
||||
sb.append("</div>");
|
||||
|
||||
for (SeizureGroup g : dayGroups) {
|
||||
int startMinutes = getMinutesOfDay(g.startDate);
|
||||
int durationMinutes = parseDurationMinutes(g.durationStr);
|
||||
|
||||
double leftPct = (startMinutes / 1440.0) * 100.0;
|
||||
double widthPct = Math.max((durationMinutes / 1440.0) * 100.0, 1.4);
|
||||
|
||||
if (leftPct + widthPct > 100.0) {
|
||||
widthPct = Math.max(0.8, 100.0 - leftPct);
|
||||
}
|
||||
|
||||
String timeStr = g.startDate != null ? timeFormat.format(g.startDate) : "";
|
||||
String label = getTimeOfDayLabel(g.startDate);
|
||||
String tip = label + " " + timeStr + " - " + statusToString(g.firstEvent.status)
|
||||
+ " (" + g.durationStr + ")";
|
||||
|
||||
sb.append("<div class='event-bar' style='left:")
|
||||
.append(formatPct(leftPct))
|
||||
.append("%;width:")
|
||||
.append(formatPct(widthPct))
|
||||
.append("%;background:")
|
||||
.append(getStatusColor(g.firstEvent.status))
|
||||
.append(";' title='")
|
||||
.append(escapeHtml(tip))
|
||||
.append("'></div>");
|
||||
}
|
||||
|
||||
sb.append("</div>");
|
||||
sb.append("<div class='time-scale'><span>00</span><span>06</span><span>12</span><span>18</span><span>24</span></div>");
|
||||
sb.append("<div class='seizure-count'>").append(dayGroups.size())
|
||||
.append(dayGroups.size() == 1 ? " event" : " events").append("</div>");
|
||||
}
|
||||
|
||||
sb.append("</div>");
|
||||
}
|
||||
|
||||
sb.append("</div>"); // calendar
|
||||
sb.append("</div>"); // month-panel
|
||||
}
|
||||
|
||||
// Detailed table below. JavaScript filters this table to the selected month.
|
||||
sb.append("<h2>Event Details</h2>");
|
||||
if (groups.isEmpty()) {
|
||||
sb.append("<p>No seizure events recorded in this period.</p>");
|
||||
} else {
|
||||
sb.append("<p id='emptyMonthMessage' class='no-month-events'>No seizure events recorded for the selected month.</p>");
|
||||
sb.append("<div class='table-controls' id='eventTableControls'>");
|
||||
sb.append("<div><label for='rowsPerPage'>Entries per page: </label>");
|
||||
sb.append("<select id='rowsPerPage' onchange='changePageSize()'>");
|
||||
sb.append("<option value='5'>5</option>");
|
||||
sb.append("<option value='10' selected>10</option>");
|
||||
sb.append("<option value='25'>25</option>");
|
||||
sb.append("<option value='50'>50</option>");
|
||||
sb.append("<option value='all'>All</option>");
|
||||
sb.append("</select></div>");
|
||||
sb.append("<div class='pager'>");
|
||||
sb.append("<button type='button' id='prevPageBtn' onclick='previousPage()'>Previous</button>");
|
||||
sb.append("<label for='pageSelect'>Page: </label>");
|
||||
sb.append("<select id='pageSelect' onchange='changePage()'></select>");
|
||||
sb.append("<button type='button' id='nextPageBtn' onclick='nextPage()'>Next</button>");
|
||||
sb.append("</div>");
|
||||
sb.append("<div id='tableInfo' class='table-info'></div>");
|
||||
sb.append("</div>");
|
||||
sb.append("<div class='table-wrap' id='eventTableWrap'><table class='detail-table'>");
|
||||
sb.append("<tr><th>#</th><th>Date & Time</th><th>Status</th><th>Duration</th><th>Heart Rate</th><th>Cause</th></tr>");
|
||||
SimpleDateFormat displaySdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss", Locale.UK);
|
||||
int i = 1;
|
||||
for (SeizureGroup g : groups) {
|
||||
String timeStr = g.startDate != null ? displaySdf.format(g.startDate) : g.firstEvent.dataTime;
|
||||
String statusStr = statusToString(g.firstEvent.status);
|
||||
String cssClass = statusToCssClass(g.firstEvent.status);
|
||||
String hrStr = g.hr > 0 ? String.format(Locale.UK, "%.1f bpm", g.hr) : "N/A";
|
||||
String cause = g.firstEvent.alarmCause != null && !g.firstEvent.alarmCause.isEmpty()
|
||||
? g.firstEvent.alarmCause : "Unknown";
|
||||
String dotColor = getTimeOfDayColor(g.startDate);
|
||||
String eventMonth = g.startDate != null ? monthIdFormat.format(g.startDate) : "";
|
||||
|
||||
sb.append("<tr class='event-row' data-month='").append(eventMonth).append("'>");
|
||||
sb.append("<td>").append(i++).append("</td>");
|
||||
sb.append("<td><span style='display:inline-block;width:10px;height:10px;border-radius:50%;background:")
|
||||
.append(dotColor).append(";margin-right:6px'></span>").append(timeStr).append("</td>");
|
||||
sb.append("<td><span class='status-pill ").append(cssClass).append("'>").append(statusStr).append("</span></td>");
|
||||
sb.append("<td>").append(escapeHtml(g.durationStr)).append("</td>");
|
||||
sb.append("<td>").append(hrStr).append("</td>");
|
||||
sb.append("<td>").append(escapeHtml(cause)).append("</td>");
|
||||
sb.append("</tr>");
|
||||
}
|
||||
sb.append("</table></div>");
|
||||
}
|
||||
|
||||
sb.append("<p class='footer'><small>Generated by ClinX02 OpenSeizureDetector</small></p>");
|
||||
sb.append("</div>");
|
||||
|
||||
sb.append("<script>");
|
||||
sb.append("var currentPage = 1;");
|
||||
sb.append("function getSelectedMonth() {");
|
||||
sb.append("var select = document.getElementById('monthSelect');");
|
||||
sb.append("return select ? select.value : ''; }");
|
||||
sb.append("function showMonth(monthId) {");
|
||||
sb.append("var panels = document.querySelectorAll('.month-panel');");
|
||||
sb.append("for (var i = 0; i < panels.length; i++) { panels[i].classList.remove('active'); }");
|
||||
sb.append("var selectedPanel = document.getElementById('month-' + monthId);");
|
||||
sb.append("if (selectedPanel) { selectedPanel.classList.add('active'); }");
|
||||
sb.append("var summaries = document.querySelectorAll('.month-summary-panel');");
|
||||
sb.append("for (var s = 0; s < summaries.length; s++) { summaries[s].classList.remove('active'); }");
|
||||
sb.append("var selectedSummary = document.getElementById('summary-' + monthId);");
|
||||
sb.append("if (selectedSummary) { selectedSummary.classList.add('active'); }");
|
||||
sb.append("currentPage = 1;");
|
||||
sb.append("updateTable();");
|
||||
sb.append("}");
|
||||
sb.append("function changePageSize() { currentPage = 1; updateTable(); }");
|
||||
sb.append("function changePage() {");
|
||||
sb.append("var pageSelect = document.getElementById('pageSelect');");
|
||||
sb.append("if (pageSelect) { currentPage = parseInt(pageSelect.value, 10) || 1; }");
|
||||
sb.append("updateTable();");
|
||||
sb.append("}");
|
||||
sb.append("function previousPage() { if (currentPage > 1) { currentPage--; updateTable(); } }");
|
||||
sb.append("function nextPage() {");
|
||||
sb.append("var pageSelect = document.getElementById('pageSelect');");
|
||||
sb.append("var totalPages = pageSelect ? pageSelect.options.length : 1;");
|
||||
sb.append("if (currentPage < totalPages) { currentPage++; updateTable(); }");
|
||||
sb.append("}");
|
||||
sb.append("function updateTable() {");
|
||||
sb.append("var monthId = getSelectedMonth();");
|
||||
sb.append("var rows = document.querySelectorAll('.event-row');");
|
||||
sb.append("var matchingRows = [];");
|
||||
sb.append("for (var r = 0; r < rows.length; r++) {");
|
||||
sb.append("var matches = rows[r].getAttribute('data-month') === monthId;");
|
||||
sb.append("rows[r].style.display = 'none';");
|
||||
sb.append("if (matches) { matchingRows.push(rows[r]); }");
|
||||
sb.append("}");
|
||||
sb.append("var rowsPerPageSelect = document.getElementById('rowsPerPage');");
|
||||
sb.append("var rowsPerPageValue = rowsPerPageSelect ? rowsPerPageSelect.value : '10';");
|
||||
sb.append("var rowsPerPage = rowsPerPageValue === 'all' ? Math.max(matchingRows.length, 1) : parseInt(rowsPerPageValue, 10);");
|
||||
sb.append("if (!rowsPerPage || rowsPerPage < 1) { rowsPerPage = 10; }");
|
||||
sb.append("var totalPages = Math.max(1, Math.ceil(matchingRows.length / rowsPerPage));");
|
||||
sb.append("if (currentPage > totalPages) { currentPage = totalPages; }");
|
||||
sb.append("if (currentPage < 1) { currentPage = 1; }");
|
||||
sb.append("var startIndex = (currentPage - 1) * rowsPerPage;");
|
||||
sb.append("var endIndex = Math.min(startIndex + rowsPerPage, matchingRows.length);");
|
||||
sb.append("for (var i = startIndex; i < endIndex; i++) { matchingRows[i].style.display = ''; }");
|
||||
sb.append("var pageSelect = document.getElementById('pageSelect');");
|
||||
sb.append("if (pageSelect) {");
|
||||
sb.append("pageSelect.innerHTML = '';");
|
||||
sb.append("for (var p = 1; p <= totalPages; p++) {");
|
||||
sb.append("var option = document.createElement('option'); option.value = p; option.textContent = p + ' of ' + totalPages;");
|
||||
sb.append("if (p === currentPage) { option.selected = true; }");
|
||||
sb.append("pageSelect.appendChild(option); }");
|
||||
sb.append("}");
|
||||
sb.append("var wrap = document.getElementById('eventTableWrap');");
|
||||
sb.append("var empty = document.getElementById('emptyMonthMessage');");
|
||||
sb.append("var controls = document.getElementById('eventTableControls');");
|
||||
sb.append("var hasRows = matchingRows.length > 0;");
|
||||
sb.append("if (wrap) { wrap.style.display = hasRows ? '' : 'none'; }");
|
||||
sb.append("if (controls) { controls.style.display = hasRows ? '' : 'none'; }");
|
||||
sb.append("if (empty) { empty.style.display = hasRows ? 'none' : 'block'; }");
|
||||
sb.append("var countText = document.getElementById('selectedMonthCount');");
|
||||
sb.append("if (countText) { countText.textContent = matchingRows.length + (matchingRows.length === 1 ? ' event in selected month' : ' events in selected month'); }");
|
||||
sb.append("var info = document.getElementById('tableInfo');");
|
||||
sb.append("if (info) {");
|
||||
sb.append("if (hasRows) { info.textContent = 'Showing ' + (startIndex + 1) + '-' + endIndex + ' of ' + matchingRows.length; }");
|
||||
sb.append("else { info.textContent = ''; }");
|
||||
sb.append("}");
|
||||
sb.append("var prev = document.getElementById('prevPageBtn');");
|
||||
sb.append("var next = document.getElementById('nextPageBtn');");
|
||||
sb.append("if (prev) { prev.disabled = currentPage <= 1; }");
|
||||
sb.append("if (next) { next.disabled = currentPage >= totalPages; }");
|
||||
sb.append("}");
|
||||
sb.append("document.addEventListener('DOMContentLoaded', function() {");
|
||||
sb.append("var select = document.getElementById('monthSelect');");
|
||||
sb.append("if (select) { showMonth(select.value); }");
|
||||
sb.append("});");
|
||||
sb.append("</script>");
|
||||
sb.append("</body></html>");
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
static class SeizureEvent {
|
||||
String dataTime;
|
||||
int status;
|
||||
String type;
|
||||
String notes;
|
||||
String alarmPhrase;
|
||||
String alarmCause;
|
||||
double hr = 0.0;
|
||||
Date date;
|
||||
}
|
||||
|
||||
static class SeizureGroup {
|
||||
SeizureEvent firstEvent;
|
||||
SeizureEvent lastEvent;
|
||||
Date startDate;
|
||||
Date lastDate;
|
||||
String durationStr = "N/A";
|
||||
double hr = 0.0;
|
||||
|
||||
SeizureGroup(SeizureEvent first) {
|
||||
this.firstEvent = first;
|
||||
this.lastEvent = first;
|
||||
this.startDate = first.date;
|
||||
this.lastDate = first.date;
|
||||
if (first.notes != null && first.notes.contains("Duration:")) {
|
||||
this.durationStr = extractDuration(first.notes);
|
||||
}
|
||||
this.hr = first.hr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,12 +26,18 @@ import fi.iki.elonen.NanoHTTPD;
|
||||
*/
|
||||
public class SdWebServer extends NanoHTTPD {
|
||||
private String TAG = "WebServer";
|
||||
private static final String REPORT_TOKEN = "clinx02secure";
|
||||
private SdData mSdData;
|
||||
private SdServer mSdServer;
|
||||
private Context mContext;
|
||||
private Handler mHandler;
|
||||
private OsdUtil mUtil;
|
||||
|
||||
private boolean isValidToken(Map<String, String> parameters) {
|
||||
String token = parameters.get("token");
|
||||
return REPORT_TOKEN.equals(token);
|
||||
}
|
||||
|
||||
public SdWebServer(Context context, SdData sdData, SdServer sdServer) {
|
||||
// Set the port to listen on (8080)
|
||||
super(8080);
|
||||
@@ -192,6 +198,33 @@ public class SdWebServer extends NanoHTTPD {
|
||||
answer = "{'msg' : 'Alarm Accepted'}";
|
||||
break;
|
||||
|
||||
case "/report":
|
||||
if (!isValidToken(parameters)) {
|
||||
return new NanoHTTPD.Response(NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/html", "<h1>403 Forbidden - Invalid or missing token</h1>");
|
||||
}
|
||||
String days = parameters.get("days");
|
||||
if (days == null) days = "1";
|
||||
answer = ReportManager.generateHtmlReport(
|
||||
LogManager.mOsdDb, Integer.parseInt(days));
|
||||
responseMimeType = "text/html";
|
||||
break;
|
||||
|
||||
case "/report/download":
|
||||
if (!isValidToken(parameters)) {
|
||||
return new NanoHTTPD.Response(NanoHTTPD.Response.Status.FORBIDDEN,
|
||||
"text/html", "<h1>403 Forbidden - Invalid or missing token</h1>");
|
||||
}
|
||||
String dlDays = parameters.get("days");
|
||||
if (dlDays == null) dlDays = "1";
|
||||
String reportHtml = ReportManager.generateHtmlReport(
|
||||
LogManager.mOsdDb, Integer.parseInt(dlDays));
|
||||
NanoHTTPD.Response dlRes = new NanoHTTPD.Response(
|
||||
NanoHTTPD.Response.Status.OK, "text/html", reportHtml);
|
||||
dlRes.addHeader("Content-Disposition",
|
||||
"attachment; filename=\"seizure_report.html\"");
|
||||
return dlRes;
|
||||
|
||||
default:
|
||||
if (uri.startsWith("/index.html") ||
|
||||
uri.startsWith("/logfiles.html") ||
|
||||
|
||||
Reference in New Issue
Block a user