diff --git a/src/main/java/org/ngafid/SendEmail.java b/src/main/java/org/ngafid/SendEmail.java index abaf24c1a..5f36ff064 100644 --- a/src/main/java/org/ngafid/SendEmail.java +++ b/src/main/java/org/ngafid/SendEmail.java @@ -32,8 +32,9 @@ public class SendEmail { private static final Logger LOG = Logger.getLogger(SendEmail.class.getName()); - private static final String baseURL = "https://ngafid.org"; - //private static final String baseURL = "https://ngafidbeta.rit.edu"; + private static final String baseURLFallback = "https://ngafid.org"; + private static final String baseURLEnvironment = System.getenv("NGAFID_BASE_URL"); + private static final String baseURL = Objects.requireNonNullElse(baseURLEnvironment, baseURLFallback); private static final String unsubscribeURLTemplate = (baseURL + "/email_unsubscribe?id=__ID__&token=__TOKEN__"); private static final java.sql.Date lastTokenFree = new java.sql.Date(0); private static final int EMAIL_UNSUBSCRIBE_TOKEN_EXPIRATION_MONTHS = 3; @@ -45,6 +46,7 @@ public class SendEmail { static { String enabled = System.getenv("NGAFID_EMAIL_ENABLED"); + LOG.info("Email base URL: " + baseURL); if (enabled != null && enabled.toLowerCase().equals("false")) { LOG.info("Emailing has been disabled"); @@ -69,9 +71,9 @@ public class SendEmail { String NGAFID_ADMIN_EMAILS = System.getenv("NGAFID_ADMIN_EMAILS"); adminEmails = new ArrayList(Arrays.asList(NGAFID_ADMIN_EMAILS.split(";"))); - System.out.println("import emails will always also be sent to the following admin emails:"); + LOG.info("import emails will always also be sent to the following admin emails:"); for (String adminEmail : adminEmails) { - System.out.println("\t'" + adminEmail + "'"); + LOG.info("\t'" + adminEmail + "'"); } try { @@ -139,16 +141,16 @@ private static class SMTPAuthenticator extends javax.mail.Authenticator { public SMTPAuthenticator(String username, String password) { this.username = username; this.password = password; - System.out.println("Created authenticator with username: '" + this.username + "' and password: '" + this.password + "'"); + LOG.info("Created authenticator with username: '" + this.username + "' and password: '" + this.password + "'"); } public PasswordAuthentication getPasswordAuthentication() { - System.out.println("Attempting to authenticate with username: '" + this.username + "' and password: '" + this.password + "'"); + LOG.info("Attempting to authenticate with username: '" + this.username + "' and password: '" + this.password + "'"); return new PasswordAuthentication(this.username, this.password); } public boolean isValid() { - System.out.println("Checking if valid with username: '" + this.username + "' and password: '" + this.password + "'"); + LOG.info("Checking if valid with username: '" + this.username + "' and password: '" + this.password + "'"); return !(this.username == null || this.password == null); } @@ -259,7 +261,7 @@ public static void sendEmail(ArrayList toRecipients, ArrayList b SMTPAuthenticator auth = new SMTPAuthenticator(username, password); if (!emailEnabled) { - System.out.println("Emailing has been disabled, not sending email"); + LOG.info("Emailing has been disabled, not sending email"); return; } @@ -270,10 +272,10 @@ public static void sendEmail(ArrayList toRecipients, ArrayList b if (auth.isValid()) { - System.out.println("emailing to " + String.join(", ", toRecipients)); - System.out.println("BCCing to " + String.join(", ", bccRecipients)); - System.out.println("subject: '" + subject); - System.out.println("body: '" + body); + LOG.info("emailing to " + String.join(", ", toRecipients)); + LOG.info("BCCing to " + String.join(", ", bccRecipients)); + LOG.info("subject: '" + subject); + LOG.info("body: '" + body); // Sender's email ID needs to be mentioned @@ -318,7 +320,7 @@ public static void sendEmail(ArrayList toRecipients, ArrayList b boolean embedUnsubscribeURL = true; if (EmailType.isForced(emailType)) { - System.out.println("Delivering FORCED email type: " + emailType); + LOG.info("Delivering FORCED email type: " + emailType); embedUnsubscribeURL = false; } else if (!UserEmailPreferences.getEmailTypeUserState(toRecipient, emailType)) { //Check whether or not the emailType is enabled for the user @@ -354,7 +356,7 @@ public static void sendEmail(ArrayList toRecipients, ArrayList b message.setRecipient(Message.RecipientType.TO, new InternetAddress(toRecipient)); // Send the message to the current recipient - System.out.println("sending message!"); + LOG.info("Sending email message!"); Transport.send(message); } @@ -377,12 +379,12 @@ public static void sendEmail(ArrayList toRecipients, ArrayList b message.setContent(body, "text/html; charset=utf-8"); // Send the message to the current BCC recipients - System.out.println("sending message (BCC)!"); + LOG.info("Sending email message (BCC)!"); Transport.send(message); } - System.out.println("Sent messages successfully...."); + LOG.info("Sent email messages successfully..."); } catch (MessagingException mex) { mex.printStackTrace(); diff --git a/src/main/java/org/ngafid/WebServer.java b/src/main/java/org/ngafid/WebServer.java index 876effc66..7e3e603a1 100755 --- a/src/main/java/org/ngafid/WebServer.java +++ b/src/main/java/org/ngafid/WebServer.java @@ -250,6 +250,7 @@ public static void main(String[] args) { Spark.post("/protected/monthly_event_counts", new PostMonthlyEventCounts(gson)); Spark.get("/protected/severities", new GetSeverities(gson)); Spark.post("/protected/severities", new PostSeverities(gson)); + Spark.post("/protected/all_severities", new PostAllSeverities(gson)); Spark.get("/protected/event_statistics", new GetEventStatistics(gson)); Spark.get("/protected/waiting", new GetWaiting(gson)); diff --git a/src/main/java/org/ngafid/accounts/UserEmailPreferences.java b/src/main/java/org/ngafid/accounts/UserEmailPreferences.java index 5c6040fae..3e3901695 100644 --- a/src/main/java/org/ngafid/accounts/UserEmailPreferences.java +++ b/src/main/java/org/ngafid/accounts/UserEmailPreferences.java @@ -21,15 +21,17 @@ public class UserEmailPreferences { - private int userId; - private HashMap emailTypesUser; - private String[] emailTypesKeys; + + private static final Logger LOG = Logger.getLogger(UserEmailPreferences.class.getName()); private static HashMap users = new HashMap<>(); private static HashMap userIDs = new HashMap<>(); private static HashMap> emailTypesUsers = new HashMap<>(); - private static final Logger LOG = Logger.getLogger(UserEmailPreferences.class.getName()); + + private int userId; + private HashMap emailTypesUser; + private String[] emailTypesKeys; public UserEmailPreferences(int userId, HashMap emailTypesUser) { @@ -47,31 +49,30 @@ public UserEmailPreferences(int userId, HashMap emailTypesUser) this.emailTypesKeys = keysRecent; } - public static void addUser(Connection connection, User user) throws SQLException { + public static void addUser(Connection connection, User userTarget) throws SQLException { - int userID = user.getId(); - String userEmail = user.getEmail(); + int userTargetID = userTarget.getId(); + String userEmail = userTarget.getEmail(); //user email --> userId - userIDs.put(userEmail, userID); + userIDs.put(userEmail, userTargetID); //userId --> User - users.put(userID, user); - - emailTypesUsers.put( user.getEmail(), user.getUserEmailPreferences(connection).getEmailTypesUser() ); + users.put(userTargetID, userTarget); - } + emailTypesUsers.put( + userTarget.getEmail(), + userTarget.getUserEmailPreferences(connection).getEmailTypesUser() + ); - public static User getUser(int userId) { - return users.get(userId); } - public static int getUserIDFromEmail(String userEmail) { - return userIDs.get(userEmail); + public static User getUser(int userTargetID) { + return users.get(userTargetID); } - public HashMap getEmailTypesUser() { - return emailTypesUser; + public static int getUserIDFromEmail(String userTargetEmail) { + return userIDs.get(userTargetEmail); } public static boolean getEmailTypeUserState(String email, EmailType emailType) { @@ -96,4 +97,8 @@ public static boolean getEmailTypeUserState(String email, EmailType emailType) { } + public HashMap getEmailTypesUser() { + return emailTypesUser; + } + } \ No newline at end of file diff --git a/src/main/java/org/ngafid/events/Event.java b/src/main/java/org/ngafid/events/Event.java index 2847bca98..0f5f96f26 100644 --- a/src/main/java/org/ngafid/events/Event.java +++ b/src/main/java/org/ngafid/events/Event.java @@ -369,11 +369,24 @@ public static HashMap> getEvents(Connection connection, eventsByAirframe.put(airframe, new ArrayList()); } - String query = "SELECT id FROM event_definitions WHERE (fleet_id = 0 OR fleet_id = ?) AND name LIKE ? ORDER BY name"; + String query; + PreparedStatement preparedStatement; + + if (eventName.equals("ANY Event")) { + + LOG.info("[EX] Getting ALL events for fleet with ID: " + fleetId); + + query = "SELECT id FROM event_definitions WHERE fleet_id = 0 OR fleet_id = ? ORDER BY name"; + preparedStatement = connection.prepareStatement(query); + preparedStatement.setInt(1, fleetId); + + } else { + query = "SELECT id FROM event_definitions WHERE (fleet_id = 0 OR fleet_id = ?) AND name LIKE ? ORDER BY name"; + preparedStatement = connection.prepareStatement(query); + preparedStatement.setInt(1, fleetId); + preparedStatement.setString(2, eventName); + } - PreparedStatement preparedStatement = connection.prepareStatement(query); - preparedStatement.setInt(1, fleetId); - preparedStatement.setString(2, eventName); LOG.info(preparedStatement.toString()); ResultSet resultSet = preparedStatement.executeQuery(); diff --git a/src/main/java/org/ngafid/routes/GetUpload.java b/src/main/java/org/ngafid/routes/GetUpload.java index 50e5a3888..017688990 100644 --- a/src/main/java/org/ngafid/routes/GetUpload.java +++ b/src/main/java/org/ngafid/routes/GetUpload.java @@ -44,9 +44,10 @@ public Object handle(Request request, Response response) throws SQLException { return "Upload not found"; } - if (!user.hasUploadAccess(upload.getFleetId())) { - LOG.severe("INVALID ACCESS: user did not have upload or manager access this fleet."); - Spark.halt(401, "User did not have access to delete this upload."); + //Verify that a user has access to the download the uploaded file (requires View Access) + if (!user.hasViewAccess(upload.getFleetId())) { + LOG.severe("INVALID ACCESS: user did not have view, upload, or manager access this fleet."); + Spark.halt(401, "User did not have access to download the uploaded file."); return null; } diff --git a/src/main/java/org/ngafid/routes/GetUploads.java b/src/main/java/org/ngafid/routes/GetUploads.java index 2bb242a08..f88a3f056 100644 --- a/src/main/java/org/ngafid/routes/GetUploads.java +++ b/src/main/java/org/ngafid/routes/GetUploads.java @@ -125,6 +125,8 @@ public Object handle(Request request, Response response) { scopes.put("uploads_js", "var uploads = JSON.parse('" + gson.toJson(other_uploads) + "'); var pending_uploads = JSON.parse('" + gson.toJson(pending_uploads) + "');"); + scopes.put("user_js", "var user = JSON.parse('" + gson.toJson(user) + "');"); + StringWriter stringOut = new StringWriter(); mustache.execute(new PrintWriter(stringOut), scopes).flush(); resultString = stringOut.toString(); diff --git a/src/main/java/org/ngafid/routes/Navbar.java b/src/main/java/org/ngafid/routes/Navbar.java index e514a0850..e4e92022b 100644 --- a/src/main/java/org/ngafid/routes/Navbar.java +++ b/src/main/java/org/ngafid/routes/Navbar.java @@ -63,6 +63,7 @@ public static String getJavascript(Request request) { + "var modifyTailsAccess = " + modifyTailsAccess + ";" + "var unconfirmedTailsCount = " + unconfirmedTailsCount + ";" + "var airSyncEnabled = " + airSyncEnabled + ";" - + "var uploader = " + hasUploadAccess + ";"; + + "var isUploader = " + hasUploadAccess + ";"; + } } diff --git a/src/main/java/org/ngafid/routes/PostAllSeverities.java b/src/main/java/org/ngafid/routes/PostAllSeverities.java new file mode 100644 index 000000000..0622604d1 --- /dev/null +++ b/src/main/java/org/ngafid/routes/PostAllSeverities.java @@ -0,0 +1,89 @@ +package org.ngafid.routes; + +import java.time.LocalDate; + +import java.util.List; +import java.util.ArrayList; +import java.util.logging.Logger; +import java.util.HashMap; + + +import java.sql.Connection; +import java.sql.SQLException; + +import com.google.gson.Gson; + +import spark.Route; +import spark.Request; +import spark.Response; +import spark.Session; +import spark.Spark; + +import org.ngafid.Database; +import org.ngafid.WebServer; +import org.ngafid.accounts.User; +import org.ngafid.events.Event; +import org.ngafid.events.EventStatistics; +import org.ngafid.common.*; + +public class PostAllSeverities implements Route { + private static final Logger LOG = Logger.getLogger(PostAllSeverities.class.getName()); + private Gson gson; + + public PostAllSeverities(Gson gson) { + this.gson = gson; + LOG.info("post " + this.getClass().getName() + " initalized"); + } + + + @Override + public Object handle(Request request, Response response) { + LOG.info("handling " + this.getClass().getName() + " route"); + + String startDate = request.queryParams("startDate"); + String endDate = request.queryParams("endDate"); + String eventNames = request.queryParams("eventNames"); + String tagName = request.queryParams("tagName"); + final Session session = request.session(); + User user = session.attribute("user"); + int fleetId = user.getFleetId(); + + //check to see if the user has upload access for this fleet. + if (!user.hasViewAccess(fleetId)) { + LOG.severe("INVALID ACCESS: user did not have access view imports for this fleet."); + Spark.halt(401, "User did not have access to view imports for this fleet."); + return null; + } + + try { + + Connection connection = Database.getConnection(); + String[] eventNamesArray = eventNames.split(","); + HashMap> > eventMap = new HashMap<>(); + + for (String eventName : eventNamesArray) { + + //Remove leading and trailing quotes + eventName = eventName.replace("\"", ""); + + //Remove brackets + eventName = eventName.replace("[", ""); + eventName = eventName.replace("]", ""); + + //Remove trailing spaces + eventName = eventName.trim(); + + if (eventName.equals("ANY Event")) + continue; + + HashMap> events = Event.getEvents(connection, fleetId, eventName, LocalDate.parse(startDate), LocalDate.parse(endDate), tagName); + eventMap.put(eventName, events); + } + + return gson.toJson(eventMap); + + } catch (SQLException e) { + return gson.toJson(new ErrorResponse(e)); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/ngafid/routes/PostUploads.java b/src/main/java/org/ngafid/routes/PostUploads.java index 7cdaa5f64..4c9a99c56 100644 --- a/src/main/java/org/ngafid/routes/PostUploads.java +++ b/src/main/java/org/ngafid/routes/PostUploads.java @@ -73,4 +73,4 @@ public Object handle(Request request, Response response) { return gson.toJson(new ErrorResponse(e)); } } -} +} \ No newline at end of file diff --git a/src/main/javascript/cesium_buttons.js b/src/main/javascript/cesium_buttons.js index 79c7c9cfb..6db66c5bd 100644 --- a/src/main/javascript/cesium_buttons.js +++ b/src/main/javascript/cesium_buttons.js @@ -70,7 +70,8 @@ class CesiumButtons extends React.Component {
-
+
+
+
+
+ +
+
+
+
- + -
+ ); } } diff --git a/src/main/javascript/dark_mode_toggle.js b/src/main/javascript/dark_mode_toggle.js new file mode 100644 index 000000000..1a1cc9612 --- /dev/null +++ b/src/main/javascript/dark_mode_toggle.js @@ -0,0 +1,72 @@ +import React, { Component } from 'react'; + +export class DarkModeToggle extends Component { + + constructor(props) { + + super(props); + + //Initialize state based on localStorage value + const darkMode = localStorage.getItem('darkMode') === 'true'; + this.state = { + useDarkMode: darkMode, + }; + + this.updateDarkMode = this.updateDarkMode.bind(this); + + } + + componentDidMount() { + + const darkMode = localStorage.getItem('darkMode') === 'true'; + this.setState( + { useDarkMode: darkMode }, () => {this.applyTheme(this.state.useDarkMode);} + ); + + } + + + applyTheme(useDarkMode) { + + const root = document.documentElement; + if (useDarkMode) + root.classList.add('dark-theme'); + else + root.classList.remove('dark-theme'); + + } + + updateDarkMode() { + + const newDarkMode = !this.state.useDarkMode; + + //Update the localStorage value + localStorage.setItem('darkMode', newDarkMode ? 'true' : 'false'); + + //Apply the theme + this.applyTheme(newDarkMode); + + //Update the state + this.setState({ useDarkMode: newDarkMode }); + + if (this.props.onClickAlt) + this.props.onClickAlt(); + + } + + render() { + return ( + +
+ +
+
+ ); + } +} diff --git a/src/main/javascript/email_settings.js b/src/main/javascript/email_settings.js index db4be4430..41c5a0c6c 100644 --- a/src/main/javascript/email_settings.js +++ b/src/main/javascript/email_settings.js @@ -4,6 +4,7 @@ import ReactDOM from "react-dom"; import $ from 'jquery'; +import { redraw } from 'plotly.js'; window.jQuery = $; window.$ = $; @@ -167,6 +168,7 @@ class EmailSettingsTableUser extends React.Component { textAlign: "center", borderCollapse: "separate", borderSpacing: "16px 16px", + color: "var(--c_text)" }}> @@ -497,6 +499,7 @@ class EmailSettingsTableManager extends React.Component { @@ -524,11 +527,10 @@ class EmailSettingsTableManager extends React.Component { - - + { this.state.fleetUsers.map((userCurrent, settingIndex) => ( - + { this.state.emailTypes.map((type, typeIndex) => ( diff --git a/src/main/javascript/event_definitions_display.js b/src/main/javascript/event_definitions_display.js index 9036d7268..e286d6fd3 100644 --- a/src/main/javascript/event_definitions_display.js +++ b/src/main/javascript/event_definitions_display.js @@ -1,5 +1,7 @@ import React from "react"; import ReactDOM from "react-dom"; +import Table from "react-bootstrap/Table"; +import Col from "react-bootstrap/Col"; import SignedInNavbar from "./signed_in_navbar"; import GetAllDescriptions from "./get_all_descriptions"; @@ -19,33 +21,44 @@ class EventDefinitionsDisplayPage extends React.Component { } return ( -
- -
-
-
-
{userCurrent.email}
- - - - - - - - - {rows.map((row, index) => { - return ( - - - - - - ) - })} - -
Event NameAircraft TypeEvent Definition
{row[0]}{row[1]}{row[2]}
+
+ +
+ +
+ +
+
+
+
+ + +
+ + + + + + + + + {rows.map((row, index) => { + return ( + + + + + + ) + })} + +
Event NameAircraft TypeEvent Definition
{row[0]}{row[1]}{row[2]}
+ + +
diff --git a/src/main/javascript/event_statistics.js b/src/main/javascript/event_statistics.js index 93a228f57..70f0d0a47 100755 --- a/src/main/javascript/event_statistics.js +++ b/src/main/javascript/event_statistics.js @@ -190,7 +190,7 @@ class EventCard extends React.Component { render() { return (
-
+
@@ -199,7 +199,7 @@ class EventCard extends React.Component { -
+
  {eventInfo.processedFlights + " / " + eventInfo.totalFlights + " (" + processedPercentage + "%) flights processed"}
@@ -276,6 +276,7 @@ class AirframeCard extends React.Component { } render() { + let marginTop = 0; if (!this.props.first) { marginTop = 14; @@ -296,8 +297,8 @@ class AirframeCard extends React.Component { return ( -
-
+
+
{this.props.airframeName + " Event Statistics"} @@ -312,7 +313,7 @@ class AirframeCard extends React.Component { return (
-
+
@@ -321,7 +322,7 @@ class AirframeCard extends React.Component { -
+
  {eventInfo.processedFlights + " / " + eventInfo.totalFlights + " (" + processedPercentage + "%) flights processed"}
@@ -332,7 +333,7 @@ class AirframeCard extends React.Component { {eventInfo.humanReadable}

- +
@@ -429,22 +430,26 @@ class DashboardCard extends React.Component { let airframeIds = Object.keys(airframeMap); return ( -
- +
+
+ +
- { - airframeIds.map((airframeId, airframeIndex) => { - let first = true; - if (airframeIndex > 0) first = false - return ( - ); - }) - } +
+ { + airframeIds.map((airframeId, airframeIndex) => { + let first = true; + if (airframeIndex > 0) first = false + return ( + ); + }) + } +
); } @@ -462,7 +467,7 @@ class DashboardCard extends React.Component { } return (
-
+
{airframeStats.airframeName + " Events"}
@@ -477,7 +482,7 @@ class DashboardCard extends React.Component { return (
-
+
@@ -486,7 +491,7 @@ class DashboardCard extends React.Component { -
+
  {eventInfo.processedFlights + " / " + eventInfo.totalFlights + " (" + processedPercentage + "%) flights processed"}
diff --git a/src/main/javascript/events_component.js b/src/main/javascript/events_component.js index fbee338e7..15a8518f4 100644 --- a/src/main/javascript/events_component.js +++ b/src/main/javascript/events_component.js @@ -253,9 +253,19 @@ class Events extends React.Component { }) return ( -
+
Events: + { + (this.state.events.length == 0) + && +
+
+ No events were found for this flight. +
+
+ } +
{ eventTypeButtons.map( (button) => { diff --git a/src/main/javascript/filter.js b/src/main/javascript/filter.js index 95b349b29..eb228cb2d 100644 --- a/src/main/javascript/filter.js +++ b/src/main/javascript/filter.js @@ -19,6 +19,8 @@ import { Colors } from "./map.js"; import { timeZones } from "./time_zones.js"; import { confirmModal } from "./confirm_modal.js"; +const MESSAGE_BIND_PERIOD_MS = 3000; + //Used to check names for filter validation function isEmptyOrSpaces(str){ return str === null || str.match(/^ *$/) !== null; @@ -59,6 +61,7 @@ function filterValid(filter, rules) { } for (let i = 0; i < rule.conditions.length; i++) { + if (rule.conditions[i].type == "number") { if (typeof inputs[i+1] == 'undefined' || inputs[i+1] == "") { return "Please enter a number."; @@ -78,7 +81,10 @@ function filterValid(filter, rules) { if (typeof inputs[i+1] == 'undefined' || inputs[i+1] == "") { return "Please enter a date and time."; } + } else if (rule.conditions[i].options.length === 0) { + return "No options available for this condition."; } + } return ""; } @@ -91,7 +97,6 @@ function recursiveValid(filters, rules) { if (filterValid(filters, rules) != "") return false; //console.log("recursiveValid on:"); - //console.log(filters); let subFilters = filters.filters; for (let i = 0; i < subFilters.length; i++) { @@ -280,7 +285,9 @@ class Rule extends React.Component { } - +
id fleet_idactions
{eventDefinition.id} {eventDefinition.fleetId} {eventDefinition.airframeNameId}{arrayToString(eventDefinition.columnNames)} {arrayToString(eventDefinition.severityColumnNames)} {eventDefinition.severityType} - - + +
+
{fleetUser.email} {fleetUser.firstName} {this.state.fleetUser.lastName} @@ -243,72 +246,88 @@ class ManageFleetPage extends React.Component { } return ( -
- - -
{Number(info.count).toLocaleString('en')} {info.message} + +  {info.message}
{formatNumberAsync(this.state.statistics.uploads, integerOptions)} Uploads + +  Upload(s)
{formatNumberAsync(this.state.statistics.uploadsNotImported, integerOptions)} Pending Upload(s) + +  Upload(s) awaiting Import
{formatNumberAsync(this.state.statistics.uploadsWithError, integerOptions)} Uploads with Error(s)
+ +  Upload(s) with Errors
{formatNumberAsync(this.state.statistics.numberFlights, integerOptions)} Flights + +  Flight(s) Valid
{formatNumberAsync(this.state.statistics.flightsWithWarning, integerOptions)} Flights with Warning(s) + +  Flight(s) with Warnings
{formatNumberAsync(this.state.statistics.flightsWithError, integerOptions)} Flights with Error(s)
+ +  Flight(s) with Errors
+ +  Flight(s) Imported