First Commit
This commit is contained in:
62
src/ConfigStructure.java
Normal file
62
src/ConfigStructure.java
Normal file
@@ -0,0 +1,62 @@
|
||||
import Gson.GsonFormatter;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class ConfigStructure {
|
||||
String MySQLHost = "localhost";
|
||||
int MySQLPort = 3306;
|
||||
String MySQLAdminUser = "adminfis";
|
||||
String MySQLAdminPassword = "adminfis";
|
||||
String MySQLDatabase = "minifis";
|
||||
Users[] users = new Users[]{new Users("teknisi","bandara","admin"), new Users("user","user123456","user")};
|
||||
String WebHost = "0.0.0.0";
|
||||
int WebPort = 7000;
|
||||
int SocketIOPort = 7001;
|
||||
|
||||
public static ConfigStructure LoadFromFile(Path path) {
|
||||
if (path != null ) {
|
||||
try {
|
||||
String content = Files.readString(path);
|
||||
return GsonFormatter.fromJson(content, ConfigStructure.class);
|
||||
} catch (Exception e) {
|
||||
{
|
||||
System.out.println("Error loading Config from file " + path+", Exception: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
// default values
|
||||
return new ConfigStructure();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save this config to file
|
||||
* @param path path to save the file
|
||||
* @return true if success
|
||||
*/
|
||||
public boolean SaveToFile(Path path){
|
||||
if (path!=null ){
|
||||
try{
|
||||
String content = GsonFormatter.toJson(this);
|
||||
Files.writeString(path, content);
|
||||
return true;
|
||||
} catch (Exception e){
|
||||
System.out.println("Error saving Config to file " + path+", Exception: "+e.getMessage());
|
||||
}
|
||||
} else System.out.println("SaveToFile failed, Path is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
class Users{
|
||||
String username;
|
||||
String password;
|
||||
String role;
|
||||
public Users(String username, String password, String role){
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.role = role;
|
||||
}
|
||||
}
|
||||
50
src/Database/CityDetail.java
Normal file
50
src/Database/CityDetail.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package Database;
|
||||
|
||||
import lombok.Data;
|
||||
import Gson.GsonFormatter;
|
||||
|
||||
/**
|
||||
* CityDetail class is a class that contains the details of a city.
|
||||
* <br/>Contains :
|
||||
* <br/>1. <b>CityName</b> , example : Tangerang
|
||||
* <br/>2. <b>AirportName</b>, example : Soekarno-Hatta International Airport
|
||||
* <br/>3. <b>AirportCode</b>, example : CGK
|
||||
* <br/>4. <b>StateCode</b>, example : Banten
|
||||
* <br/>5. <b>Country</b>, example : Indonesia
|
||||
*/
|
||||
@Data
|
||||
@SuppressWarnings("unused")
|
||||
public class CityDetail {
|
||||
// Example Tangerang
|
||||
private String CityName;
|
||||
// Example Soekarno-Hatta International Airport
|
||||
private String AirportName;
|
||||
// Example CGK
|
||||
private String AirportCode;
|
||||
// Example Banten
|
||||
private String StateCode;
|
||||
// Example Indonesia
|
||||
private String Country;
|
||||
|
||||
public CityDetail(String CityName, String AirportName, String AirportCode, String StateCode, String Country){
|
||||
this.CityName = CityName;
|
||||
this.AirportName = AirportName;
|
||||
this.AirportCode = AirportCode;
|
||||
this.StateCode = StateCode;
|
||||
this.Country = Country;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return GsonFormatter.toJson(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static CityDetail fromJson(String json){
|
||||
return GsonFormatter.fromJson(json, CityDetail.class);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static CityDetail NewCityDetail(String CityName, String AirportName, String AirportCode, String StateCode, String Country){
|
||||
return new CityDetail(CityName, AirportName, AirportCode, StateCode, Country);
|
||||
}
|
||||
}
|
||||
431
src/Database/FisData.java
Normal file
431
src/Database/FisData.java
Normal file
@@ -0,0 +1,431 @@
|
||||
package Database;
|
||||
|
||||
import Gson.GsonFormatter;
|
||||
import Gson.JsonHelper;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
// Source : https://developer.flightstats.com/api-docs/fids/v1/fidsresponse
|
||||
|
||||
public class FisData {
|
||||
public int id;
|
||||
// example GA for Garuda Indonesia
|
||||
public String AirlineCode;
|
||||
// example 123
|
||||
public String FlightNumber;
|
||||
// Departure or Arrival
|
||||
public char DA;
|
||||
// Domestic or International
|
||||
public char DI;
|
||||
// CityDetail of the origin of the flight
|
||||
//public CityDetail Origin;
|
||||
public String Origin;
|
||||
// CityDetail of the destination of the flight
|
||||
//public CityDetail[] Destination;
|
||||
public String Destination;
|
||||
// example D1
|
||||
public String Gate;
|
||||
// example Terminal 2
|
||||
public String Terminal;
|
||||
// Conveyor Belt 1
|
||||
public String BaggageClaim;
|
||||
// Check-in Counter 1
|
||||
public String CheckinCounter;
|
||||
// when this data last updated
|
||||
public LocalDateTime lastUpdated;
|
||||
// scheduled time of arrival or departure
|
||||
public LocalDateTime scheduledTime;
|
||||
// estimated time of arrival or departure
|
||||
public LocalDateTime estimatedTime;
|
||||
// actual time of arrival or departure
|
||||
public LocalDateTime actualTime;
|
||||
// scheduled time of gate departure or arrival
|
||||
public LocalDateTime gateScheduledTime;
|
||||
// estimated time of gate departure or arrival
|
||||
public LocalDateTime gateEstimatedTime;
|
||||
// actual time of gate departure or arrival
|
||||
public LocalDateTime gateActualTime;
|
||||
// optinal data, example : Cloudy
|
||||
public String weather;
|
||||
// optional data, example : 25.6
|
||||
public double temperatureC;
|
||||
public String Remark;
|
||||
// for AAS Purpose
|
||||
public String IsRead;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static FisData CreateFromJsonObject(JsonObject vv){
|
||||
if (vv!=null){
|
||||
FisData data = new FisData();
|
||||
data.id = JsonHelper.GetIntValue(vv, "id", 0);
|
||||
data.setAirlineCode(JsonHelper.GetStringValue(vv, "AirlineCode", null));
|
||||
data.setFlightNumber(JsonHelper.GetStringValue(vv, "FlightNumber", null));
|
||||
data.setDI(JsonHelper.GetCharacterValue(vv, "DI", null));
|
||||
data.setDA(JsonHelper.GetCharacterValue(vv, "DA", null));
|
||||
//data.setOrigin(JsonHelper.GetCityDetailValue(vv, "Origin", null));
|
||||
data.Origin = JsonHelper.GetStringValue(vv, "Origin", null);
|
||||
//data.setDestination(JsonHelper.GetCityDetailValues(vv, "Destination", null));
|
||||
data.Destination = JsonHelper.GetStringValue(vv, "Destination", null);
|
||||
data.Gate = JsonHelper.GetStringValue(vv, "Gate", null);
|
||||
data.Terminal =JsonHelper.GetStringValue(vv, "Terminal", null);
|
||||
data.BaggageClaim = JsonHelper.GetStringValue(vv, "BaggageClaim", null);
|
||||
data.CheckinCounter = JsonHelper.GetStringValue(vv, "CheckinCounter", null);
|
||||
data.setLastUpdated(JsonHelper.GetLocalDateTimeValue(vv, "LastUpdated", null));
|
||||
data.setScheduledTime(JsonHelper.GetLocalDateTimeValue(vv, "ScheduledTime", null));
|
||||
data.setEstimatedTime(JsonHelper.GetLocalDateTimeValue(vv, "EstimatedTime", null));
|
||||
data.setActualTime(JsonHelper.GetLocalDateTimeValue(vv, "ActualTime", null) );
|
||||
data.setGateScheduledTime(JsonHelper.GetLocalDateTimeValue(vv, "GateScheduledTime", null));
|
||||
data.setGateEstimatedTime(JsonHelper.GetLocalDateTimeValue(vv, "GateEstimatedTime", null));
|
||||
data.setGateActualTime(JsonHelper.GetLocalDateTimeValue(vv, "GateActualTime", null));
|
||||
data.weather = JsonHelper.GetStringValue(vv, "Weather", null);
|
||||
data.temperatureC = JsonHelper.GetDoubleValue(vv, "TemperatureC", 0.0);
|
||||
data.Remark = JsonHelper.GetStringValue(vv, "Remark", null);
|
||||
data.IsRead = JsonHelper.GetStringValue(vv, "IsRead", null);
|
||||
return data;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return GsonFormatter.toJson(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public JsonObject toJsonObject(){
|
||||
return GsonFormatter.toJsonObject(this, FisData.class);
|
||||
}
|
||||
|
||||
public static String LocalDateTimeToString(LocalDateTime value){
|
||||
if (value!=null){
|
||||
try {
|
||||
return dtf.format(value);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Airline Code
|
||||
* <br/>Airline code must be 2 characters, if longer than 2 characters, it will be truncated
|
||||
* <br/>Airline code will be converted to uppercase
|
||||
* @param airlineCode Airline Code
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setAirlineCode(String airlineCode) {
|
||||
if (airlineCode!=null && !airlineCode.isEmpty()){
|
||||
// airline code must be 2 characters
|
||||
if (airlineCode.length()>2) airlineCode = airlineCode.substring(0,2);
|
||||
this.AirlineCode = airlineCode.toUpperCase();
|
||||
}
|
||||
}
|
||||
|
||||
// flight number pattern, must consist of 1-4 digits
|
||||
public static final Pattern flightNumberPattern = Pattern.compile("^[0-9]{1,4}$");
|
||||
/**
|
||||
* Set Flight Number
|
||||
* <br/>Flight number must be 1-4 digits (0-9)
|
||||
* @param flightNumber Flight Number
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setFlightNumber(String flightNumber) {
|
||||
if (flightNumber!=null && !flightNumber.isEmpty()) {
|
||||
// flight number must be 1-4 digits
|
||||
if (flightNumberPattern.matcher(flightNumber).matches()) {
|
||||
this.FlightNumber = flightNumber;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DI (Domestic or International)
|
||||
* Accepted value is 'D' for Domestic and 'I' for International
|
||||
* @param DI DI value
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setDI(Character DI) {
|
||||
if (DI!=null){
|
||||
if (DI == 'D' || DI == 'I') {
|
||||
this.DI = DI;
|
||||
} else if (DI=='d' || DI=='i'){
|
||||
this.DI = Character.toUpperCase(DI);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the DA (Departure or Arrival)
|
||||
* Accepted value is 'D' for Departure and 'A' for Arrival
|
||||
* @param DA DA value
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setDA(Character DA) {
|
||||
if (DA!=null){
|
||||
if (DA == 'D' || DA == 'A') {
|
||||
this.DA = DA;
|
||||
} else if (DA == 'd' || DA == 'a') {
|
||||
this.DA = Character.toUpperCase(DA);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the time of the flight
|
||||
* If actual time is available, return actual time
|
||||
* If actual time is not available, return estimated time
|
||||
* If estimated time is not available, return scheduled time
|
||||
* if not available, return null
|
||||
* @return departure or arrival time, depend of the data
|
||||
*/
|
||||
public LocalDateTime getCurrentTime(){
|
||||
if (actualTime != null) {
|
||||
return actualTime;
|
||||
} else if (estimatedTime != null) {
|
||||
return estimatedTime;
|
||||
} else {
|
||||
return scheduledTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the time of the gate
|
||||
* if actual time is available, return actual time
|
||||
* if actual time is not available, return estimated time
|
||||
* if estimated time is not available, return scheduled time
|
||||
* if not available, return null
|
||||
* @return gate departure or arrival time, depend of the data
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public LocalDateTime getGateCurrentTime(){
|
||||
if (gateActualTime != null) {
|
||||
return gateActualTime;
|
||||
} else if (gateEstimatedTime != null) {
|
||||
return gateEstimatedTime;
|
||||
} else {
|
||||
return gateScheduledTime;
|
||||
}
|
||||
}
|
||||
|
||||
public static final DecimalFormat df = new DecimalFormat("#.#");
|
||||
|
||||
/**
|
||||
* Get the flight code
|
||||
* <br/>is a combination of <b>AirlineCode</b> and <b>FlightNumber</b>
|
||||
* <br/>example : GA123
|
||||
* @return flight code
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public String getFlight(){
|
||||
return AirlineCode + FlightNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the flight is delayed
|
||||
* <br/>if the current time is after the scheduled time, then the flight is delayed
|
||||
* @return true if the flight is delayed
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public boolean Delayed(){
|
||||
return getCurrentTime().isAfter(scheduledTime);
|
||||
}
|
||||
public static final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
public static boolean CorrectTimeFormat(String time){
|
||||
if (time!=null) {
|
||||
if (!time.isEmpty()){
|
||||
try {
|
||||
LocalDateTime.parse(time, dtf);
|
||||
return true;
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Set the scheduled time of the flight
|
||||
* <br/>the time must be in the format of "yyyy-MM-dd HH:mm:ss"
|
||||
* <br/>if DA is 'D', the time is the departure time
|
||||
* <br/>if DA is 'A', the time is the arrival time
|
||||
* @param scheduledTime scheduled time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setScheduledTime(String scheduledTime) {
|
||||
if (CorrectTimeFormat(scheduledTime)) setScheduledTime(LocalDateTime.parse(scheduledTime, dtf));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the scheduled time of the flight
|
||||
* <br/>if DA is 'D', the time is the departure time
|
||||
* <br/>if DA is 'A', the time is the arrival time
|
||||
* @param scheduledTime scheduled time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setScheduledTime(LocalDateTime scheduledTime) {
|
||||
this.scheduledTime = scheduledTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the estimated time
|
||||
* <br/>the time must be in the format of "yyyy-MM-dd HH:mm:ss"
|
||||
* <br/>if DA is 'D', the time is the departure time
|
||||
* <br/>if DA is 'A', the time is the arrival time
|
||||
* @param estimatedTime estimated time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setEstimatedTime(String estimatedTime) {
|
||||
if (CorrectTimeFormat(estimatedTime)) setEstimatedTime(LocalDateTime.parse(estimatedTime, dtf));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the estimated time
|
||||
* <br/>if DA is 'D', the time is the departure time
|
||||
* <br/>if DA is 'A', the time is the arrival time
|
||||
* @param estimatedTime estimated time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setEstimatedTime(LocalDateTime estimatedTime) {
|
||||
this.estimatedTime = estimatedTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the actual time
|
||||
* <br/>the time must be in the format of "yyyy-MM-dd HH:mm:ss"
|
||||
* <br/>if DA is 'D', the time is the departure time
|
||||
* <br/>if DA is 'A', the time is the arrival time
|
||||
* @param actualTime actual time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setActualTime(String actualTime) {
|
||||
if (CorrectTimeFormat(actualTime)) setActualTime(LocalDateTime.parse(actualTime, dtf));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the actual time
|
||||
* <br/>if DA is 'D', the time is the departure time
|
||||
* <br/>if DA is 'A', the time is the arrival time
|
||||
* @param actualTime actual time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setActualTime(LocalDateTime actualTime) {
|
||||
this.actualTime = actualTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gate scheduled time
|
||||
* <br/>the time must be in the format of "yyyy-MM-dd HH:mm:ss"
|
||||
*
|
||||
* @param gateScheduledTime gate scheduled time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setGateScheduledTime(String gateScheduledTime) {
|
||||
if (CorrectTimeFormat(gateScheduledTime)) setGateScheduledTime(LocalDateTime.parse(gateScheduledTime, dtf));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gate scheduled time
|
||||
*
|
||||
* @param gateScheduledTime gate scheduled time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setGateScheduledTime(LocalDateTime gateScheduledTime) {
|
||||
this.gateScheduledTime = gateScheduledTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gate estimated time
|
||||
* <br/>the time must be in the format of "yyyy-MM-dd HH:mm:ss"
|
||||
* @param gateEstimatedTime gate estimated time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setGateEstimatedTime(String gateEstimatedTime) {
|
||||
if (CorrectTimeFormat(gateEstimatedTime)) setGateEstimatedTime(LocalDateTime.parse(gateEstimatedTime, dtf));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gate estimated time
|
||||
* @param gateEstimatedTime gate estimated time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setGateEstimatedTime(LocalDateTime gateEstimatedTime) {
|
||||
this.gateEstimatedTime = gateEstimatedTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gate actual time
|
||||
* <br/>the time must be in the format of "yyyy-MM-dd HH:mm:ss"
|
||||
* @param gateActualTime gate actual time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setGateActualTime(String gateActualTime) {
|
||||
if (CorrectTimeFormat(gateActualTime)) setGateActualTime(LocalDateTime.parse(gateActualTime, dtf));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the gate actual time
|
||||
* @param gateActualTime gate actual time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setGateActualTime(LocalDateTime gateActualTime) {
|
||||
this.gateActualTime = gateActualTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last updated time
|
||||
* <br/>the time must be in the format of "yyyy-MM-dd HH:mm:ss"
|
||||
* @param lastUpdated last updated time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setLastUpdated(String lastUpdated) {
|
||||
if (CorrectTimeFormat(lastUpdated)) this.lastUpdated = LocalDateTime.parse(lastUpdated, dtf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last updated time
|
||||
* @param lastUpdated last updated time
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void setLastUpdated(LocalDateTime lastUpdated) {
|
||||
this.lastUpdated = lastUpdated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the destination of the flight
|
||||
* @param CityName City Name, example : Tangerang
|
||||
* @param AirportName Airport Name, example : Soekarno-Hatta International Airport
|
||||
* @param AirportCode Airport Code, example : CGK
|
||||
* @param StateCode State Code, example : Banten
|
||||
* @param Country Country, example : Indonesia
|
||||
*/
|
||||
// @SuppressWarnings("unused")
|
||||
// public void setOrigin(String CityName, String AirportName, String AirportCode, String StateCode, String Country){
|
||||
// setOrigin(new CityDetail(CityName, AirportName, AirportCode, StateCode, Country));
|
||||
// }
|
||||
|
||||
/**
|
||||
* Set the origin of the flight
|
||||
* @param origin CityDetail of the origin
|
||||
*/
|
||||
// @SuppressWarnings("unused")
|
||||
// public void setOrigin(CityDetail origin) {
|
||||
// this.Origin = origin;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Set the destination of the flight
|
||||
* @param destination CityDetail of the destination
|
||||
*/
|
||||
// @SuppressWarnings("unused")
|
||||
// public void setDestination(CityDetail... destination) {
|
||||
// this.Destination = destination;
|
||||
// }
|
||||
}
|
||||
382
src/Database/MySQLDatabase.java
Normal file
382
src/Database/MySQLDatabase.java
Normal file
@@ -0,0 +1,382 @@
|
||||
package Database;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
||||
import java.sql.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@SuppressWarnings("SqlSourceToSinkFlow")
|
||||
@Getter
|
||||
public class MySQLDatabase {
|
||||
private final String url;
|
||||
private final String user;
|
||||
private final String password;
|
||||
private final String database;
|
||||
|
||||
/**
|
||||
* Constructor for MySQLDatabase
|
||||
* @param host Server address
|
||||
* @param port Server port
|
||||
* @param user Username
|
||||
* @param password Password
|
||||
* @param database Database name
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public MySQLDatabase(String host, int port, String user, String password, String database){
|
||||
url = String.format("jdbc:mysql://%s:%d/", host, port);
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
this.database = database;
|
||||
raise_log(String.format("Connecting to %s:%d with user %s and password %s to database %s", host, port, user, password, database));
|
||||
CreateDatabase();
|
||||
|
||||
}
|
||||
|
||||
private void CreateDatabase(){
|
||||
try(Connection conn = DriverManager.getConnection(url, user, password)){
|
||||
int result = conn.createStatement().executeUpdate(String.format("CREATE DATABASE IF NOT EXISTS %s", database));
|
||||
if (result>0){
|
||||
raise_log(String.format("Database %s created successfully", database));
|
||||
} else {
|
||||
raise_log(String.format("Database %s already exists", database));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
raise_log(String.format("Error creating database %s, error: %s", database, e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private String correcttablename(String tablename){
|
||||
return database+"."+tablename;
|
||||
}
|
||||
|
||||
public String[] GetTableNames(){
|
||||
try(Connection conn = DriverManager.getConnection(url+database, user, password)){
|
||||
Statement stmt = conn.createStatement();
|
||||
String sql = String.format("SHOW TABLES FROM %s", database);
|
||||
ResultSet rs = stmt.executeQuery(sql);
|
||||
List<String> result = new ArrayList<>();
|
||||
while(rs.next()){
|
||||
result.add(rs.getString(1));
|
||||
}
|
||||
return result.toArray(new String[0]);
|
||||
} catch (Exception e) {
|
||||
raise_log(String.format("Error getting table names, error: %s", e.getMessage()));
|
||||
}
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Table in database
|
||||
* Table schema is defined as FisData object
|
||||
* @param tablename Table name, usually in format yyyyMMdd
|
||||
* @return String value, "success" | "error"
|
||||
*/
|
||||
@SuppressWarnings({"unused", "SqlSourceToSinkFlow"})
|
||||
public String CreateTable(@NonNull String tablename){
|
||||
try(Connection conn = DriverManager.getConnection(url+database, user, password)){
|
||||
Statement stmt = conn.createStatement();
|
||||
String sql = String.format("CREATE TABLE IF NOT EXISTS %s ("
|
||||
+"id INT PRIMARY KEY AUTO_INCREMENT,"
|
||||
+"AirlineCode CHAR(2)," // 2-letter IATA code
|
||||
+"FlightNumber CHAR(4)," // 4-digit flight number
|
||||
+"DA CHAR(1)," // Departure or Arrival
|
||||
+"DI CHAR(1)," // Domestic or International
|
||||
+"Origin VARCHAR(255)," // JSON String of CityDetail object
|
||||
+"Destination VARCHAR(255)," // JSON String of Array of CityDetail object
|
||||
+"Gate VARCHAR(20)," // Gate number
|
||||
+"Terminal VARCHAR(20)," // Terminal number
|
||||
+"Baggage VARCHAR(20)," // Baggage claim number
|
||||
+"CheckinCounter VARCHAR(20)," // Check-in counter number
|
||||
+"LastUpdated VARCHAR(20)," // Last updated time, in format yyyy-MM-dd HH:mm:ss
|
||||
+"ScheduledTime VARCHAR(20)," // Scheduled time, in format yyyy-MM-dd HH:mm:ss
|
||||
+"EstimatedTime VARCHAR(20)," // Estimated time, in format yyyy-MM-dd HH:mm:ss
|
||||
+"ActualTime VARCHAR(20)," // Actual time, in format yyyy-MM-dd HH:mm:ss
|
||||
+"GateScheduledTime VARCHAR(20)," // Gate scheduled time, in format yyyy-MM-dd HH:mm:ss
|
||||
+"GateEstimatedTime VARCHAR(20)," // Gate estimated time, in format yyyy-MM-dd HH:mm:ss
|
||||
+"GateActualTime VARCHAR(20)," // Gate actual time, in format yyyy-MM-dd HH:mm:ss
|
||||
+"Weather VARCHAR(20)," // Optional value
|
||||
+"TemperatureCelsius VARCHAR(20)," // Optional value
|
||||
+"Remarks VARCHAR(255)," // Optional value
|
||||
+"IsRead CHAR(1)" // for AAS to mark if the data is read
|
||||
+")", correcttablename(tablename));
|
||||
|
||||
// result for create table is always 0
|
||||
stmt.executeUpdate(sql);
|
||||
return "success";
|
||||
} catch (Exception e) {
|
||||
raise_log(String.format("Error creating table %s, error: %s\n", tablename,e.getMessage()));
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete table from database
|
||||
* @param tablename Table name, usually in format yyyyMMdd
|
||||
* @return String value, "success" | "error"
|
||||
*/
|
||||
@SuppressWarnings({"unused", "SqlSourceToSinkFlow"})
|
||||
public String DeleteTable(@NonNull String tablename){
|
||||
try(Connection conn = DriverManager.getConnection(url+database, user, password)){
|
||||
String sql = String.format("DROP TABLE IF EXISTS %s", correcttablename(tablename));
|
||||
Statement stmt = conn.createStatement();
|
||||
//System.out.println(sql);
|
||||
// result for drop table is always 0
|
||||
stmt.executeUpdate(sql);
|
||||
return "success" ;
|
||||
} catch (Exception e) {
|
||||
raise_log(String.format("Error deleting table %s, error: %s", tablename, e.getMessage()));
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert data to table
|
||||
* @param tablename Table name, usually in format yyyyMMdd
|
||||
* @param datas FisData objects to insert
|
||||
* @return String value, "success" | "failed" | "error"
|
||||
*/
|
||||
@SuppressWarnings({"unused", "SqlSourceToSinkFlow"})
|
||||
public String InsertData(@NonNull String tablename, @NonNull FisData... datas){
|
||||
try(Connection conn = DriverManager.getConnection(url+database, user, password)){
|
||||
conn.beginRequest();
|
||||
int xx = 0;
|
||||
int successcount = 0;
|
||||
for(FisData data : datas){
|
||||
var stmt = conn.prepareStatement(String.format("INSERT INTO %s (AirlineCode, FlightNumber, DA, DI, Origin, Destination, Gate, Terminal, Baggage, CheckinCounter, LastUpdated, ScheduledTime, EstimatedTime, ActualTime, GateScheduledTime, GateEstimatedTime, GateActualTime, Weather, TemperatureCelsius, Remarks, IsRead) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", correcttablename(tablename)));
|
||||
fisdata_to_preparedstatement(data, stmt);
|
||||
int _result = stmt.executeUpdate();
|
||||
|
||||
if (_result>0){
|
||||
//raise_log(String.format("Data %d inserted to %s successfully", xx, tablename));
|
||||
successcount++;
|
||||
} else {
|
||||
raise_log(String.format("Data %d insertion to %s failed",xx,tablename));
|
||||
}
|
||||
xx++;
|
||||
}
|
||||
conn.endRequest();
|
||||
|
||||
raise_log(String.format("Target Insert: %d, Success: %d data inserted to %s", datas.length, successcount, tablename));
|
||||
return successcount == datas.length ? "success" : "failed";
|
||||
} catch (Exception e) {
|
||||
raise_log(String.format("Error inserting data to %s, error: %s", tablename, e.getMessage()));
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Common code for InsertData and Update Data, to convert FisData object to PreparedStatement
|
||||
* @param data FisData object
|
||||
* @param stmt PreparedStatement object
|
||||
* @throws SQLException if error occurs
|
||||
*/
|
||||
private void fisdata_to_preparedstatement(FisData data, PreparedStatement stmt) throws SQLException {
|
||||
stmt.setString(1, data.AirlineCode);
|
||||
stmt.setString(2, data.FlightNumber);
|
||||
stmt.setString(3, String.valueOf(data.DA));
|
||||
stmt.setString(4, String.valueOf(data.DI));
|
||||
stmt.setString(5, data.Origin);
|
||||
stmt.setString(6, data.Destination);
|
||||
stmt.setString(7, data.Gate);
|
||||
stmt.setString(8, data.Terminal);
|
||||
stmt.setString(9, data.BaggageClaim);
|
||||
stmt.setString(10, data.CheckinCounter);
|
||||
stmt.setString(11, FisData.LocalDateTimeToString(data.lastUpdated));
|
||||
stmt.setString(12, FisData.LocalDateTimeToString(data.scheduledTime));
|
||||
stmt.setString(13, FisData.LocalDateTimeToString(data.estimatedTime));
|
||||
stmt.setString(14, FisData.LocalDateTimeToString(data.actualTime));
|
||||
stmt.setString(15, FisData.LocalDateTimeToString(data.gateScheduledTime));
|
||||
stmt.setString(16, FisData.LocalDateTimeToString(data.gateEstimatedTime));
|
||||
stmt.setString(17, FisData.LocalDateTimeToString(data.gateActualTime));
|
||||
stmt.setString(18, data.weather);
|
||||
stmt.setString(19, String.format("%.1f",data.temperatureC));
|
||||
stmt.setString(20, data.Remark);
|
||||
stmt.setString(21, data.IsRead);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data in table
|
||||
* @param tablename Table name, usually in format yyyyMMdd
|
||||
* @param index Index of data to update
|
||||
* @param data FisData object to update
|
||||
* @return String value, "success" | "failed" | "error"
|
||||
*/
|
||||
@SuppressWarnings({"unused", "SqlSourceToSinkFlow"})
|
||||
public String UpdateData(@NonNull String tablename, int index, @NonNull FisData data){
|
||||
try(Connection conn = DriverManager.getConnection(url+database, user, password)){
|
||||
String sql = String.format("UPDATE %s SET AirlineCode=?, FlightNumber=?, DA=?, DI=?, Origin=?, Destination=?, Gate=?, Terminal=?, Baggage=?, CheckinCounter=?, LastUpdated=?, ScheduledTime=?, EstimatedTime=?, ActualTime=?, GateScheduledTime=?, GateEstimatedTime=?, GateActualTime=?, Weather=?, TemperatureCelsius=?, Remarks=?, IsRead=? WHERE id=?", correcttablename(tablename));
|
||||
var stmt = conn.prepareStatement(sql);
|
||||
fisdata_to_preparedstatement(data, stmt);
|
||||
stmt.setInt(22, index);
|
||||
int result = stmt.executeUpdate();
|
||||
return result>0 ? "success" : "failed";
|
||||
|
||||
} catch (Exception e) {
|
||||
raise_log(String.format("Error updating data in %s, error: %s", tablename, e.getMessage()));
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete data from table
|
||||
* @param tablename Table name, usually in format yyyyMMdd
|
||||
* @param index Index of data to delete
|
||||
* @return String value, "success" | "failed" | "error"
|
||||
*/
|
||||
@SuppressWarnings({"unused", "SqlSourceToSinkFlow"})
|
||||
public String DeleteData(@NonNull String tablename, int index){
|
||||
try(Connection conn = DriverManager.getConnection(url+database, user, password)){
|
||||
String sql = String.format("DELETE FROM %s WHERE id=?", correcttablename(tablename));
|
||||
var stmt = conn.prepareStatement(sql);
|
||||
stmt.setInt(1, index);
|
||||
int result = stmt.executeUpdate();
|
||||
return result>0 ? "success" : "failed";
|
||||
|
||||
} catch (Exception e) {
|
||||
raise_log(String.format("Error deleting data from %s, error: %s", tablename, e.getMessage()));
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"SqlSourceToSinkFlow","unused"})
|
||||
public String ReorderingIndex(@NonNull String tablename){
|
||||
try(Connection conn = DriverManager.getConnection(url+database, user,password)){
|
||||
conn.beginRequest();
|
||||
var stmt = conn.createStatement();
|
||||
stmt.execute("SET @count = 0");
|
||||
stmt.execute(String.format("UPDATE %s SET id = @count:=@count+1", correcttablename(tablename)));
|
||||
stmt.execute(String.format("ALTER TABLE %s AUTO_INCREMENT=1", correcttablename(tablename)));
|
||||
conn.endRequest();
|
||||
return "success";
|
||||
} catch(Exception e){
|
||||
raise_log(String.format("Error reordering index in %s, error: %s", tablename, e.getMessage()));
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
private boolean validstring(String str){
|
||||
return str != null && !str.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from table
|
||||
* @param tablename Table name, usually in format yyyyMMdd
|
||||
* @param filter_airlinecode if need to filter by AirlineCode, put the value here, if not needed, put null
|
||||
* @param filter_flightnumber if need to filter by FlightNumber, put the value here, if not needed, put null
|
||||
* @param filter_da if need to filter by DA, put the value here, if not needed, put null
|
||||
* @param filter_di if need to filter by DI, put the value here, if not needed, put null
|
||||
* @return Array of FisData objects
|
||||
*/
|
||||
@SuppressWarnings({"unused", "SqlSourceToSinkFlow"})
|
||||
public FisData[] GetData(@NonNull String tablename, String filter_airlinecode, String filter_flightnumber, String filter_da, String filter_di){
|
||||
try(Connection conn = DriverManager.getConnection(url+database, user, password)){
|
||||
boolean have_filter = validstring(filter_airlinecode);
|
||||
|
||||
if (validstring(filter_flightnumber)){
|
||||
have_filter = true;
|
||||
}
|
||||
if (validstring(filter_da)){
|
||||
have_filter = true;
|
||||
}
|
||||
if (validstring(filter_di)){
|
||||
have_filter = true;
|
||||
}
|
||||
|
||||
|
||||
ResultSet rs;
|
||||
if (!have_filter){
|
||||
raise_log("No filter provided, returning all data");
|
||||
var stmt = conn.createStatement();
|
||||
rs = stmt.executeQuery(String.format("SELECT * FROM %s", correcttablename(tablename)));
|
||||
|
||||
} else {
|
||||
StringBuilder sql = new StringBuilder("SELECT * FROM ");
|
||||
sql.append(correcttablename(tablename));
|
||||
sql.append(" WHERE ");
|
||||
if (filter_airlinecode != null && !filter_airlinecode.isEmpty()){
|
||||
sql.append("AirlineCode='");
|
||||
sql.append(filter_airlinecode);
|
||||
sql.append("' AND ");
|
||||
}
|
||||
if (filter_flightnumber != null && !filter_flightnumber.isEmpty()){
|
||||
sql.append("FlightNumber='");
|
||||
sql.append(filter_flightnumber);
|
||||
sql.append("' AND ");
|
||||
}
|
||||
if (filter_da != null && !filter_da.isEmpty()){
|
||||
sql.append("DA='");
|
||||
sql.append(filter_da);
|
||||
sql.append("' AND ");
|
||||
}
|
||||
if (filter_di != null && !filter_di.isEmpty()){
|
||||
sql.append("DI='");
|
||||
sql.append(filter_di);
|
||||
sql.append("' AND ");
|
||||
}
|
||||
sql.delete(sql.length()-5, sql.length());
|
||||
var stmt = conn.createStatement();
|
||||
rs = stmt.executeQuery(sql.toString());
|
||||
}
|
||||
|
||||
List<FisData> result = new ArrayList<>();
|
||||
while(rs.next()){
|
||||
var temp = new FisData();
|
||||
temp.id = rs.getInt("id");
|
||||
temp.setAirlineCode(rs.getString("AirlineCode"));
|
||||
temp.setFlightNumber(rs.getString("FlightNumber"));
|
||||
String tempDA = rs.getString("DA");
|
||||
if (tempDA != null && !tempDA.isEmpty()){
|
||||
char tempchar = tempDA.charAt(0);
|
||||
if (tempchar == 'D' || tempchar == 'A'){
|
||||
temp.setDA(tempchar);
|
||||
}
|
||||
}
|
||||
String tempDI = rs.getString("DI");
|
||||
if (tempDI != null && !tempDI.isEmpty()){
|
||||
char tempchar = tempDI.charAt(0);
|
||||
if (tempchar == 'D' || tempchar == 'I'){
|
||||
temp.setDI(tempchar);
|
||||
}
|
||||
}
|
||||
//temp.setOrigin(GsonFormatter.fromJson(rs.getString("Origin"), CityDetail.class));
|
||||
//temp.setDestination(GsonFormatter.fromJson(rs.getString("Destination"), CityDetail[].class));
|
||||
temp.Origin = (rs.getString("Origin"));
|
||||
temp.Destination = (rs.getString("Destination"));
|
||||
temp.Gate = (rs.getString("Gate"));
|
||||
temp.Terminal = (rs.getString("Terminal"));
|
||||
temp.BaggageClaim = (rs.getString("Baggage"));
|
||||
temp.CheckinCounter = (rs.getString("CheckinCounter"));
|
||||
String tempLU = rs.getString("LastUpdated");
|
||||
temp.setLastUpdated(FisData.CorrectTimeFormat(tempLU) ? tempLU : null);
|
||||
String tempST = rs.getString("ScheduledTime");
|
||||
temp.setScheduledTime(FisData.CorrectTimeFormat(tempST) ? tempST : null);
|
||||
String tempET = rs.getString("EstimatedTime");
|
||||
temp.setEstimatedTime(FisData.CorrectTimeFormat(tempET) ? tempET : null);
|
||||
String tempAT = rs.getString("ActualTime");
|
||||
temp.setActualTime(FisData.CorrectTimeFormat(tempAT) ? tempAT : null);
|
||||
String tempGST = rs.getString("GateScheduledTime");
|
||||
temp.setGateScheduledTime(FisData.CorrectTimeFormat(tempGST) ? tempGST : null);
|
||||
String tempGET = rs.getString("GateEstimatedTime");
|
||||
temp.setGateEstimatedTime(FisData.CorrectTimeFormat(tempGET) ? tempGET : null);
|
||||
String tempGAT = rs.getString("GateActualTime");
|
||||
temp.setGateActualTime(FisData.CorrectTimeFormat(tempGAT) ? tempGAT : null);
|
||||
temp.weather = (rs.getString("Weather"));
|
||||
temp.temperatureC = (Double.parseDouble(rs.getString("TemperatureCelsius")));
|
||||
temp.Remark = (rs.getString("Remarks"));
|
||||
temp.IsRead = (rs.getString("IsRead"));
|
||||
result.add(temp);
|
||||
}
|
||||
return result.toArray(new FisData[0]);
|
||||
|
||||
} catch (Exception e) {
|
||||
raise_log(String.format("Error GetData from %s, error: %s", tablename, e.getMessage()));
|
||||
}
|
||||
return new FisData[0];
|
||||
}
|
||||
|
||||
private void raise_log(String msg){
|
||||
System.out.println(msg);
|
||||
}
|
||||
}
|
||||
61
src/GPIO/LedController.java
Normal file
61
src/GPIO/LedController.java
Normal file
@@ -0,0 +1,61 @@
|
||||
package GPIO;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class LedController {
|
||||
|
||||
DatagramSocket socket;
|
||||
private final InetSocketAddress target;
|
||||
|
||||
/**
|
||||
* Initialize LedController
|
||||
* @param targetip Target IP where the LedController is located, usually localhost
|
||||
* @param targetport Target port where the LedController is listening
|
||||
*/
|
||||
public LedController(String targetip, int targetport){
|
||||
target = new InetSocketAddress(targetip, targetport);
|
||||
try{
|
||||
socket = new DatagramSocket();
|
||||
} catch (Exception e){
|
||||
System.out.println("Error creating socket: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the state of a LED
|
||||
* @param type Type of LED
|
||||
* @param state State of LED
|
||||
* @return True if the command was sent successfully
|
||||
*/
|
||||
public boolean SetLed(LedType type, LedState state){
|
||||
if (IsOpened()){
|
||||
byte[] cmd = new byte[]{(byte)0xCC, type.value, state.value, (byte)0xDD};
|
||||
try{
|
||||
DatagramPacket packet = new DatagramPacket(cmd, cmd.length, target);
|
||||
socket.send(packet);
|
||||
return true;
|
||||
} catch (Exception e){
|
||||
System.out.println("Error sending packet: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if LedController is opened
|
||||
* @return true if opened
|
||||
*/
|
||||
public boolean IsOpened(){
|
||||
return socket!=null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close LedController
|
||||
*/
|
||||
public void Close(){
|
||||
if (socket!=null) socket.close();
|
||||
}
|
||||
}
|
||||
11
src/GPIO/LedState.java
Normal file
11
src/GPIO/LedState.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package GPIO;
|
||||
|
||||
public enum LedState {
|
||||
OFF((byte)0),
|
||||
ON((byte)1),
|
||||
BLINK((byte)2);
|
||||
final byte value;
|
||||
LedState(byte value){
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
11
src/GPIO/LedType.java
Normal file
11
src/GPIO/LedType.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package GPIO;
|
||||
|
||||
public enum LedType {
|
||||
PILOT((byte)1),
|
||||
NETWORK((byte)2),
|
||||
ACTION((byte)3);
|
||||
public final byte value;
|
||||
LedType(byte value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
27
src/Gson/DateTimeHelper.java
Normal file
27
src/Gson/DateTimeHelper.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package Gson;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class DateTimeHelper {
|
||||
|
||||
private static final DateTimeFormatter dtf1 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
/**
|
||||
* Check if the date is valid in the format yyyy-MM-dd
|
||||
* @param date the date to check
|
||||
* @return true if the date is valid, false otherwise
|
||||
*/
|
||||
public static boolean isValidDate_yyyyMMdd(String date) {
|
||||
try {
|
||||
dtf1.parse(date);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static String ConvertToTableName(String date) {
|
||||
return dtf2.format(dtf1.parse(date));
|
||||
}
|
||||
}
|
||||
36
src/Gson/GsonFormatter.java
Normal file
36
src/Gson/GsonFormatter.java
Normal file
@@ -0,0 +1,36 @@
|
||||
package Gson;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class GsonFormatter {
|
||||
private static final Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeSerializer())
|
||||
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeDeserializer())
|
||||
.create();
|
||||
|
||||
public static String toJson(Object obj){
|
||||
return gson.toJson(obj);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String json, Class<T> clazz){
|
||||
try {
|
||||
return gson.fromJson(json, clazz);
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static JsonObject toJsonObject(Object obj, Class<?> clazz){
|
||||
try {
|
||||
return gson.toJsonTree(obj, clazz).getAsJsonObject();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
109
src/Gson/JsonHelper.java
Normal file
109
src/Gson/JsonHelper.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package Gson;
|
||||
|
||||
import Database.CityDetail;
|
||||
import Database.FisData;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public class JsonHelper {
|
||||
public static String GetStringValue(JsonObject obj, String key, String defaultValue){
|
||||
if (obj!=null){
|
||||
if (obj.has(key)){
|
||||
String value = obj.get(key).getAsString();
|
||||
if (value!=null){
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static Character GetCharacterValue(JsonObject obj, String key, Character defaultValue){
|
||||
if (obj!=null){
|
||||
if (obj.has(key)){
|
||||
String value = obj.get(key).getAsString();
|
||||
if (value!=null && !value.isEmpty()){
|
||||
return value.charAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static LocalDateTime GetLocalDateTimeValue(JsonObject obj, String key, LocalDateTime defaultValue){
|
||||
if (obj!=null){
|
||||
if (obj.has(key)){
|
||||
String value = obj.get(key).getAsString();
|
||||
if (value!=null && !value.isEmpty()){
|
||||
try{
|
||||
return LocalDateTime.parse(value, FisData.dtf);
|
||||
} catch (Exception ignored){
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static int GetIntValue(JsonObject obj, String key, int defaultValue){
|
||||
if (obj!=null){
|
||||
if (obj.has(key)){
|
||||
return obj.get(key).getAsInt();
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static double GetDoubleValue(JsonObject obj, String key, double defaultValue){
|
||||
if (obj!=null){
|
||||
if (obj.has(key)){
|
||||
return obj.get(key).getAsDouble();
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static CityDetail[] GetCityDetailValues(JsonObject obj, String key, CityDetail[] defaultValue){
|
||||
if (obj!=null){
|
||||
if (obj.has(key)){
|
||||
String vv = obj.get(key).getAsString();
|
||||
if (vv!=null && !vv.isEmpty()){
|
||||
try{
|
||||
CityDetail[] cd = GsonFormatter.fromJson(vv, CityDetail[].class);
|
||||
if (cd!=null){
|
||||
return cd;
|
||||
}
|
||||
} catch (Exception ignored){
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static CityDetail GetCityDetailValue(JsonObject obj, String key, CityDetail defaultValue){
|
||||
if (obj!=null){
|
||||
if (obj.has(key)){
|
||||
String vv = obj.get(key).getAsString();
|
||||
if (vv!=null && !vv.isEmpty()){
|
||||
try{
|
||||
CityDetail cd = GsonFormatter.fromJson(vv, CityDetail.class);
|
||||
if (cd!=null){
|
||||
return cd;
|
||||
}
|
||||
} catch (Exception ignored){
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public static byte[] JsonObjectToBytes(JsonObject obj){
|
||||
return obj!=null ? obj.toString().getBytes() : null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
18
src/Gson/LocalDateTimeDeserializer.java
Normal file
18
src/Gson/LocalDateTimeDeserializer.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package Gson;
|
||||
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class LocalDateTimeDeserializer implements JsonDeserializer<LocalDateTime> {
|
||||
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Override
|
||||
public LocalDateTime deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) {
|
||||
return LocalDateTime.parse(jsonElement.getAsString(), formatter);
|
||||
}
|
||||
}
|
||||
18
src/Gson/LocalDateTimeSerializer.java
Normal file
18
src/Gson/LocalDateTimeSerializer.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package Gson;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonSerializationContext;
|
||||
import com.google.gson.JsonSerializer;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class LocalDateTimeSerializer implements JsonSerializer<LocalDateTime> {
|
||||
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(LocalDateTime localDateTime, Type type, JsonSerializationContext jsonSerializationContext) {
|
||||
return jsonSerializationContext.serialize(localDateTime.format(formatter));
|
||||
}
|
||||
}
|
||||
318
src/MiniFIS.java
Normal file
318
src/MiniFIS.java
Normal file
@@ -0,0 +1,318 @@
|
||||
import Database.FisData;
|
||||
import Database.MySQLDatabase;
|
||||
import GPIO.LedController;
|
||||
import GPIO.LedState;
|
||||
import GPIO.LedType;
|
||||
import Gson.DateTimeHelper;
|
||||
import Web.*;
|
||||
import XLSX.ExcelFile;
|
||||
import lombok.val;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class MiniFIS {
|
||||
public static ConfigStructure config;
|
||||
private static final DateTimeFormatter dtfexport = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss");
|
||||
public static LedController ledController;
|
||||
private static Map<String, SocketIOConnections> ConnectionMap ;
|
||||
|
||||
/**
|
||||
* Get System Enviroment and change config if needed
|
||||
*/
|
||||
private static void GetEnviromentVariables(){
|
||||
String databasehost = System.getenv("DATABASE_HOST");
|
||||
String databaseport = System.getenv("DATABASE_PORT");
|
||||
String databaseuser = System.getenv("DATABASE_USER");
|
||||
String databasepassword = System.getenv("DATABASE_PASSWORD");
|
||||
String databasename = System.getenv("DATABASE_NAME");
|
||||
boolean changed = false;
|
||||
if (databasename!=null && !databasename.isEmpty()){
|
||||
if (!databasename.equals(config.MySQLDatabase)){
|
||||
config.MySQLDatabase = databasename;
|
||||
System.out.println("Database name changed to: "+databasename);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (databasehost!=null && !databasehost.isEmpty()){
|
||||
if (!databasehost.equals(config.MySQLHost)){
|
||||
config.MySQLHost = databasehost;
|
||||
System.out.println("Database host changed to: "+databasehost);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (databaseport!=null && !databaseport.isEmpty()){
|
||||
try{
|
||||
int port = Integer.parseInt(databaseport);
|
||||
if (port!=config.MySQLPort){
|
||||
config.MySQLPort = port;
|
||||
System.out.println("Database port changed to: "+port);
|
||||
changed = true;
|
||||
}
|
||||
} catch (Exception e){
|
||||
System.out.println("Error parsing DATABASE_PORT: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
if (databaseuser!=null && !databaseuser.isEmpty()){
|
||||
if (!databaseuser.equals(config.MySQLAdminUser)){
|
||||
config.MySQLAdminUser = databaseuser;
|
||||
System.out.println("Database user changed to: "+databaseuser);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (databasepassword!=null && !databasepassword.isEmpty()){
|
||||
if (!databasepassword.equals(config.MySQLAdminPassword)){
|
||||
config.MySQLAdminPassword = databasepassword;
|
||||
System.out.println("Database password changed to: "+databasepassword);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
Path path = Path.of(System.getProperty("user.dir"), "config", "config.json");
|
||||
if (config.SaveToFile(path)) {
|
||||
System.out.println("Config file updated");
|
||||
} else {
|
||||
System.out.println("Failed to update config file");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void LoadConfig(){
|
||||
String userdir = System.getProperty("user.dir");
|
||||
Path configpath = Path.of(userdir,"config","config.json");
|
||||
if (Files.exists(configpath)) {
|
||||
System.out.println("Config file exists");
|
||||
config = ConfigStructure.LoadFromFile(configpath);
|
||||
} else {
|
||||
System.out.println("Config file does not exist");
|
||||
config = new ConfigStructure();
|
||||
if (config.SaveToFile(configpath)) {
|
||||
System.out.println("Config file created");
|
||||
} else {
|
||||
System.out.println("Failed to create config file");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("MiniFIS version 1.0.9");
|
||||
|
||||
LoadConfig();
|
||||
|
||||
GetEnviromentVariables();
|
||||
|
||||
String ledhost = System.getenv("LED_CONTROLLER_HOST");
|
||||
if (ledhost==null) ledhost = "localhost";
|
||||
if (ledhost.isEmpty()) ledhost = "localhost";
|
||||
|
||||
// mesti di final karena dipakai di dalam thread
|
||||
final String ledcontrollerhost = ledhost;
|
||||
ledController = new LedController(ledcontrollerhost, 4000);
|
||||
|
||||
AtomicBoolean continueblinking = new AtomicBoolean(false);
|
||||
new Thread(()->{
|
||||
// blinking controller
|
||||
continueblinking.set(true);
|
||||
System.out.println("Blinking Controller started, targeting "+ledcontrollerhost);
|
||||
while (continueblinking.get()){
|
||||
ledController.SetLed(LedType.PILOT, LedState.BLINK);
|
||||
try {
|
||||
Thread.sleep(1000);
|
||||
} catch (InterruptedException ignored) {
|
||||
continueblinking.set(false);
|
||||
}
|
||||
}
|
||||
System.out.println("Blinking Controller stopped");
|
||||
}).start();
|
||||
|
||||
MySQLDatabase db = new MySQLDatabase(config.MySQLHost, config.MySQLPort, config.MySQLAdminUser, config.MySQLAdminPassword, config.MySQLDatabase);
|
||||
WebServer web = new WebServer((tablename, filename) -> {
|
||||
System.out.println("WebServerEvent: onImport: "+tablename+", "+filename);
|
||||
if (DateTimeHelper.isValidDate_yyyyMMdd(tablename)){
|
||||
tablename = DateTimeHelper.ConvertToTableName(tablename);
|
||||
System.out.println("Converted tablename: "+tablename);
|
||||
if (Arrays.asList(db.GetTableNames()).contains(tablename)) {
|
||||
System.out.printf("ImportFromXLSX: Table %s exists, dropping...\n", tablename);
|
||||
db.DeleteTable(tablename);
|
||||
}
|
||||
if ("success".equals(db.CreateTable(tablename))){
|
||||
System.out.printf("ImportFromXLSX: Table %s created\n", tablename);
|
||||
FisData[] data = ExcelFile.LoadFromXLSX(filename.toString(),tablename);
|
||||
if (data.length>0){
|
||||
String insertresult = db.InsertData(tablename, data);
|
||||
System.out.println("MiniFIS insert result: " + insertresult);
|
||||
ledController.SetLed(LedType.ACTION, LedState.BLINK);
|
||||
return insertresult;
|
||||
} else return "no data from xlsx";
|
||||
} else return "failed to create table";
|
||||
} else return "invalid tablename";
|
||||
|
||||
});
|
||||
web.start(config.WebHost, config.WebPort);
|
||||
|
||||
ConnectionMap = new HashMap<>();
|
||||
|
||||
SocketIO socket = new SocketIO(config.WebHost,config.SocketIOPort);
|
||||
socket.Start(new SocketIOEvents() {
|
||||
@Override
|
||||
public void Connected(String remoteAddress, String sessionId) {
|
||||
if (!ConnectionMap.containsKey(sessionId)){
|
||||
System.out.println("Socket.io Client Connected: " + remoteAddress + " , id: " + sessionId);
|
||||
val conn = new SocketIOConnections();
|
||||
conn.id = sessionId;
|
||||
conn.remoteAddress = remoteAddress;
|
||||
conn.role = "";
|
||||
ConnectionMap.put(sessionId, conn);
|
||||
}
|
||||
ledController.SetLed(LedType.NETWORK, LedState.BLINK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void Disconnected(String remoteAddress, String sessionId) {
|
||||
if (ConnectionMap.containsKey(sessionId)){
|
||||
ConnectionMap.remove(sessionId);
|
||||
System.out.println("Socket.io Client Disconnected: " + remoteAddress + " , id: " + sessionId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] GetTables(String remoteAddress, String sessionId) {
|
||||
ledController.SetLed(LedType.NETWORK, LedState.BLINK);
|
||||
val conn = ConnectionMap.get(sessionId);
|
||||
if (conn.role!=null && !conn.role.isEmpty()){ // semua role bisa lihat tabel
|
||||
ledController.SetLed(LedType.ACTION, LedState.BLINK);
|
||||
return db.GetTableNames();
|
||||
} else return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String CreateTable(String remoteAddress, String sessionId, String tableName) {
|
||||
ledController.SetLed(LedType.NETWORK, LedState.BLINK);
|
||||
val conn = ConnectionMap.get(sessionId);
|
||||
if (conn!=null && config.MySQLAdminUser.equals(conn.role)){ // hanya admin yang bisa create
|
||||
String result = db.CreateTable(tableName);
|
||||
ledController.SetLed(LedType.ACTION, LedState.BLINK);
|
||||
return result;
|
||||
}
|
||||
else return "unauthorized";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String DropTable(String remoteAddress, String sessionId, String tableName) {
|
||||
ledController.SetLed(LedType.NETWORK, LedState.BLINK);
|
||||
val conn = ConnectionMap.get(sessionId);
|
||||
if (conn!=null && config.MySQLAdminUser.equals(conn.role)){ // hanya admin yang bisa drop
|
||||
String result = db.DeleteTable(tableName);
|
||||
ledController.SetLed(LedType.ACTION, LedState.BLINK);
|
||||
return result;
|
||||
}
|
||||
else return "unauthorized";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String InsertFisDataToTable(String remoteAddress, String sessionId, String tableName, FisData... data) {
|
||||
ledController.SetLed(LedType.NETWORK, LedState.BLINK);
|
||||
val conn = ConnectionMap.get(sessionId);
|
||||
if (conn!=null && config.MySQLAdminUser.equals(conn.role)){ // hanya admin yang bisa insert
|
||||
String result = db.InsertData(tableName, data);
|
||||
ledController.SetLed(LedType.ACTION, LedState.BLINK);
|
||||
return result;
|
||||
}
|
||||
else return "unauthorized";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String DeleteFisDataFromTable(String remoteAddress, String sessionId, String tableName, int index) {
|
||||
ledController.SetLed(LedType.NETWORK, LedState.BLINK);
|
||||
val conn = ConnectionMap.get(sessionId);
|
||||
if (conn!=null && config.MySQLAdminUser.equals(conn.role)){ // hanya admin yang bisa delete
|
||||
String deleteresult = db.DeleteData(tableName, index);
|
||||
db.ReorderingIndex(tableName);
|
||||
ledController.SetLed(LedType.ACTION, LedState.BLINK);
|
||||
return deleteresult;
|
||||
}
|
||||
else return "unauthorized";
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String UpdateFisDataInTable(String remoteAddress, String sessionId, String tableName, int index, FisData data) {
|
||||
ledController.SetLed(LedType.NETWORK, LedState.BLINK);
|
||||
val conn = ConnectionMap.get(sessionId);
|
||||
if (conn!=null && !conn.role.isEmpty()){ // semua role bisa update
|
||||
String updateresult = db.UpdateData(tableName, index, data);
|
||||
ledController.SetLed(LedType.ACTION, LedState.BLINK);
|
||||
return updateresult;
|
||||
}
|
||||
else return "unauthorized";
|
||||
}
|
||||
|
||||
@Override
|
||||
public FisData[] GetFisDataFromTable(String remoteAddress, String sessionId, String tableName, String filter_airlinecode, String filter_flightnumber, String filter_DA, String filter_DI) {
|
||||
ledController.SetLed(LedType.NETWORK, LedState.BLINK);
|
||||
val conn = ConnectionMap.get(sessionId);
|
||||
if (conn!=null && !conn.role.isEmpty()){ // semua role bisa baca
|
||||
FisData[] result = db.GetData(tableName, filter_airlinecode, filter_flightnumber, filter_DA, filter_DI);
|
||||
ledController.SetLed(LedType.ACTION, LedState.BLINK);
|
||||
return result;
|
||||
}
|
||||
else return new FisData[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path ExportToXLSX(String remoteAddress, String sessionId, String tablename) {
|
||||
FisData[] data = db.GetData(tablename, null, null, null, null);
|
||||
System.out.printf("ExportToXLSX: Got %d rows from %s\n", data.length, tablename);
|
||||
ledController.SetLed(LedType.NETWORK, LedState.BLINK);
|
||||
String exportfilename = "export_"+ dtfexport.format(LocalDateTime.now())+".xlsx";
|
||||
Path exportpath = web.getExportpath().resolve(exportfilename);
|
||||
if (ExcelFile.SaveToXLSX(exportpath.toString(), tablename, data)){
|
||||
System.out.printf("ExportToXLSX: Saved to %s\n", exportpath);
|
||||
ledController.SetLed(LedType.ACTION, LedState.BLINK);
|
||||
return exportpath;
|
||||
} else System.out.println("ExportToXLSX: Failed to save to xlsx");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String Login(String remoteAddress, String sessionId, String username, String password) {
|
||||
ledController.SetLed(LedType.NETWORK, LedState.BLINK);
|
||||
if (username.equals(config.MySQLAdminUser) && password.equals(config.MySQLAdminPassword)){
|
||||
ledController.SetLed(LedType.ACTION, LedState.BLINK);
|
||||
val conn = ConnectionMap.get(sessionId);
|
||||
if (conn!=null) conn.role = "adminfis";
|
||||
return "welcome adminfis";
|
||||
} else {
|
||||
if (config.users!=null && config.users.length>0){
|
||||
for (Users user : config.users){
|
||||
if (user.username.equals(username) && user.password.equals(password)){
|
||||
ledController.SetLed(LedType.ACTION, LedState.BLINK);
|
||||
val conn = ConnectionMap.get(sessionId);
|
||||
if (conn!=null) conn.role = user.role;
|
||||
return "welcome "+username;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return "unauthorized";
|
||||
}
|
||||
});
|
||||
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
System.out.println("Shutting down MiniFIS...");
|
||||
continueblinking.set(false);
|
||||
web.stop();
|
||||
socket.Stop();
|
||||
ledController.Close();
|
||||
}));
|
||||
}
|
||||
}
|
||||
13
src/Web/DeleteRowStructure.java
Normal file
13
src/Web/DeleteRowStructure.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package Web;
|
||||
|
||||
import Gson.GsonFormatter;
|
||||
|
||||
public class DeleteRowStructure {
|
||||
public String tablename;
|
||||
public int rowid;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return GsonFormatter.toJson(this);
|
||||
}
|
||||
}
|
||||
15
src/Web/EditRowStructure.java
Normal file
15
src/Web/EditRowStructure.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package Web;
|
||||
|
||||
import Database.FisData;
|
||||
import Gson.GsonFormatter;
|
||||
|
||||
public class EditRowStructure {
|
||||
public String tablename;
|
||||
public int rowid;
|
||||
public FisData newdata;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return GsonFormatter.toJson(this);
|
||||
}
|
||||
}
|
||||
10
src/Web/ExportXLSXStructure.java
Normal file
10
src/Web/ExportXLSXStructure.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package Web;
|
||||
|
||||
/**
|
||||
* Structure used for Exporting XLSX files
|
||||
* Export : client request XLSX file from server
|
||||
*/
|
||||
public class ExportXLSXStructure
|
||||
{
|
||||
public String tablename;
|
||||
}
|
||||
12
src/Web/GetDataFilter.java
Normal file
12
src/Web/GetDataFilter.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package Web;
|
||||
|
||||
/**
|
||||
* Dipakai di dalam class GetDataStructure
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class GetDataFilter {
|
||||
public String airlinecode;
|
||||
public String flightnumber;
|
||||
public String DA;
|
||||
public String DI;
|
||||
}
|
||||
17
src/Web/GetDataStructure.java
Normal file
17
src/Web/GetDataStructure.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package Web;
|
||||
|
||||
import Gson.GsonFormatter;
|
||||
|
||||
/**
|
||||
* Dipakai di server.addEventListener("getdata")
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class GetDataStructure {
|
||||
public String tablename;
|
||||
public GetDataFilter filter;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return GsonFormatter.toJson(this);
|
||||
}
|
||||
}
|
||||
20
src/Web/InsertDataStructure.java
Normal file
20
src/Web/InsertDataStructure.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package Web;
|
||||
|
||||
import Database.FisData;
|
||||
import Gson.GsonFormatter;
|
||||
|
||||
/**
|
||||
* Dipakai di server.addEventListener("insertrow")
|
||||
*/
|
||||
public class InsertDataStructure {
|
||||
public String tablename;
|
||||
public FisData fisdata;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "{tablename: "+tablename
|
||||
+", fisdata: "+fisdata.toString()
|
||||
+"}";
|
||||
//return GsonFormatter.toJson(this);
|
||||
}
|
||||
}
|
||||
6
src/Web/LoginStructure.java
Normal file
6
src/Web/LoginStructure.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package Web;
|
||||
|
||||
public class LoginStructure {
|
||||
public String username;
|
||||
public String password;
|
||||
}
|
||||
15
src/Web/ReplyArrayStringStructure.java
Normal file
15
src/Web/ReplyArrayStringStructure.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package Web;
|
||||
|
||||
import Gson.GsonFormatter;
|
||||
|
||||
public class ReplyArrayStringStructure {
|
||||
public String command;
|
||||
public String value;
|
||||
public String[] data;
|
||||
public String result;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return GsonFormatter.toJson(this);
|
||||
}
|
||||
}
|
||||
14
src/Web/ReplyDeleteRowStructure.java
Normal file
14
src/Web/ReplyDeleteRowStructure.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package Web;
|
||||
|
||||
import Gson.GsonFormatter;
|
||||
|
||||
public class ReplyDeleteRowStructure {
|
||||
public String command;
|
||||
public DeleteRowStructure value;
|
||||
public String result;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return GsonFormatter.toJson(this);
|
||||
}
|
||||
}
|
||||
12
src/Web/ReplyEditRowStructure.java
Normal file
12
src/Web/ReplyEditRowStructure.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package Web;
|
||||
|
||||
public class ReplyEditRowStructure {
|
||||
public String command;
|
||||
public EditRowStructure value;
|
||||
public String result;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return Gson.GsonFormatter.toJson(this);
|
||||
}
|
||||
}
|
||||
8
src/Web/ReplyExportXLSXStructure.java
Normal file
8
src/Web/ReplyExportXLSXStructure.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package Web;
|
||||
|
||||
public class ReplyExportXLSXStructure {
|
||||
public String tablename;
|
||||
public String filename;
|
||||
public int size;
|
||||
public String result;
|
||||
}
|
||||
20
src/Web/ReplyGetDataStructure.java
Normal file
20
src/Web/ReplyGetDataStructure.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package Web;
|
||||
|
||||
|
||||
import Database.FisData;
|
||||
import Gson.GsonFormatter;
|
||||
|
||||
/**
|
||||
* Dipakai sebagai reply dari server.addEventListener("getdata")
|
||||
*/
|
||||
public class ReplyGetDataStructure {
|
||||
public String command;
|
||||
public GetDataStructure value;
|
||||
public FisData[] data;
|
||||
public String result;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return GsonFormatter.toJson(this);
|
||||
}
|
||||
}
|
||||
14
src/Web/ReplyInsertDataStructure.java
Normal file
14
src/Web/ReplyInsertDataStructure.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package Web;
|
||||
|
||||
import Gson.GsonFormatter;
|
||||
|
||||
public class ReplyInsertDataStructure {
|
||||
public String command;
|
||||
public InsertDataStructure value;
|
||||
public String result;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return GsonFormatter.toJson(this);
|
||||
}
|
||||
}
|
||||
7
src/Web/ReplyLoginStructure.java
Normal file
7
src/Web/ReplyLoginStructure.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package Web;
|
||||
|
||||
public class ReplyLoginStructure {
|
||||
public String command;
|
||||
public LoginStructure value;
|
||||
public String result;
|
||||
}
|
||||
17
src/Web/ReplyStringStructure.java
Normal file
17
src/Web/ReplyStringStructure.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package Web;
|
||||
|
||||
import Gson.GsonFormatter;
|
||||
|
||||
/**
|
||||
* Dipakai di server.addEventListener yang butuh jawaban result String saja
|
||||
*/
|
||||
public class ReplyStringStructure {
|
||||
public String command;
|
||||
public String value;
|
||||
public String result;
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return GsonFormatter.toJson(this);
|
||||
}
|
||||
}
|
||||
434
src/Web/SocketIO.java
Normal file
434
src/Web/SocketIO.java
Normal file
@@ -0,0 +1,434 @@
|
||||
package Web;
|
||||
|
||||
import Gson.DateTimeHelper;
|
||||
import Gson.GsonFormatter;
|
||||
import com.corundumstudio.socketio.Configuration;
|
||||
import com.corundumstudio.socketio.SocketIOServer;
|
||||
import com.corundumstudio.socketio.protocol.JacksonJsonSupport;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.google.gson.JsonObject;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
|
||||
|
||||
public class SocketIO {
|
||||
private final Configuration config;
|
||||
private SocketIOServer server;
|
||||
|
||||
/**
|
||||
* Create Socket.io Server
|
||||
* @param host listening host, default to localhost
|
||||
* @param port listening port, default to 9092
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public SocketIO(String host, int port){
|
||||
if (port<1 || port>65535) port = 9092;
|
||||
config = new Configuration();
|
||||
config.setHostname(host);
|
||||
config.setPort(port);
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
|
||||
// Register LocalDateTime serializer and deserializer
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
|
||||
|
||||
objectMapper.registerModule(javaTimeModule);
|
||||
config.setJsonSupport(new JacksonJsonSupport(javaTimeModule));
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the server
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void Start(final SocketIOEvents event){
|
||||
server = new SocketIOServer(config);
|
||||
server.start();
|
||||
raise_log(String.format("SocketIO server started on host %s port %d", config.getHostname(), config.getPort()));
|
||||
|
||||
server.addConnectListener(client -> {
|
||||
String remoteAddress = client.getRemoteAddress().toString();
|
||||
String sessionId = client.getSessionId().toString();
|
||||
if (event!=null) event.Connected(remoteAddress, sessionId);
|
||||
});
|
||||
|
||||
server.addDisconnectListener(client -> {
|
||||
String remoteAddress = client.getRemoteAddress().toString();
|
||||
String sessionId = client.getSessionId().toString();
|
||||
if (event!=null) event.Disconnected(remoteAddress, sessionId);
|
||||
});
|
||||
|
||||
server.addEventListener("login",LoginStructure.class, (client, data, ackSender)->{
|
||||
String remoteAddress = client.getRemoteAddress().toString();
|
||||
String sessionId = client.getSessionId().toString();
|
||||
raise_log(String.format("Client %s with id=%s request to login", remoteAddress, sessionId));
|
||||
|
||||
ReplyLoginStructure reply = new ReplyLoginStructure();
|
||||
reply.command = "login";
|
||||
reply.value = data;
|
||||
reply.result = "invalid data"; // default reply sebelum process
|
||||
if (data instanceof LoginStructure login){
|
||||
if (login.username!=null && login.password!=null){
|
||||
if (!login.username.isEmpty() && !login.password.isEmpty()){
|
||||
if (event!=null){
|
||||
reply.result = event.Login(remoteAddress, sessionId, login.username, login.password);
|
||||
} else reply.result = "no event handler";
|
||||
}
|
||||
}
|
||||
}
|
||||
ackSender.sendAckData(reply);
|
||||
});
|
||||
|
||||
/*
|
||||
Get table names event
|
||||
data: null
|
||||
<p/>
|
||||
return: {
|
||||
command: "gettablenames",
|
||||
result: "success" or "no data",
|
||||
data: JsonArray of table names
|
||||
}
|
||||
*/
|
||||
server.addEventListener("gettablenames", String.class, (client, data, ackSender) -> {
|
||||
String remoteAddress = client.getRemoteAddress().toString();
|
||||
String sessionId = client.getSessionId().toString();
|
||||
raise_log(String.format("Client %s with id=%s request to get tables", remoteAddress, sessionId));
|
||||
ReplyArrayStringStructure reply = new ReplyArrayStringStructure();
|
||||
reply.command = "gettablenames";
|
||||
|
||||
if (event!=null){
|
||||
reply.data = event.GetTables(remoteAddress, sessionId);
|
||||
reply.result = reply.data!=null?"success":"no data";
|
||||
} else reply.result = "no event handler";
|
||||
|
||||
ackSender.sendAckData(reply);
|
||||
});
|
||||
|
||||
/*
|
||||
Create table event
|
||||
data: "table name"
|
||||
<p/>
|
||||
return: {
|
||||
command: "createtable",
|
||||
value: "table name",
|
||||
result: "success" or "failed"
|
||||
}
|
||||
*/
|
||||
server.addEventListener("createtable",String.class, (client, data, ackSender) -> {
|
||||
String remoteAddress = client.getRemoteAddress().toString();
|
||||
String sessionId = client.getSessionId().toString();
|
||||
// data is tablename in format yyyy-MM-dd
|
||||
// but SQL table name cannot contain -
|
||||
// so tablename will be converted to yyyyMMdd
|
||||
ReplyStringStructure reply = new ReplyStringStructure();
|
||||
reply.command = "createtable";
|
||||
reply.value = data;
|
||||
reply.result = "invalid data"; // default reply sebelum process
|
||||
if (data instanceof String tt){
|
||||
if (!tt.isEmpty()){
|
||||
if (DateTimeHelper.isValidDate_yyyyMMdd(tt)){
|
||||
String tablename = DateTimeHelper.ConvertToTableName(tt);
|
||||
raise_log(String.format("Client %s with id=%s request to create table %s", remoteAddress, sessionId, tablename));
|
||||
if (event!=null){
|
||||
reply.result = event.CreateTable(remoteAddress, sessionId, tablename);
|
||||
} else reply.result = "no event handler";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ackSender.sendAckData(reply);
|
||||
});
|
||||
|
||||
/*
|
||||
Drop table event
|
||||
data: "table name"
|
||||
return: {
|
||||
command: "droptable",
|
||||
value: "table name",
|
||||
result: "success" or "failed"
|
||||
}
|
||||
*/
|
||||
server.addEventListener("droptable",String.class, (client, data, ackSender) -> {
|
||||
String remoteAddress = client.getRemoteAddress().toString();
|
||||
String sessionId = client.getSessionId().toString();
|
||||
ReplyStringStructure reply = new ReplyStringStructure();
|
||||
reply.command = "droptable";
|
||||
reply.value = data;
|
||||
reply.result = "invalid data"; // default reply sebelum process
|
||||
if (data instanceof String tt){
|
||||
if (!tt.isEmpty()){
|
||||
if (DateTimeHelper.isValidDate_yyyyMMdd(tt)){
|
||||
String tablename = DateTimeHelper.ConvertToTableName(tt);
|
||||
// data is tablename in format yyyy-MM-dd
|
||||
// but SQL table name cannot contain -
|
||||
// so tablename will be converted to yyyyMMdd
|
||||
raise_log(String.format("Client %s with id=%s request to drop table %s", remoteAddress, sessionId, tablename));
|
||||
if (event!=null){
|
||||
reply.result = event.DropTable(remoteAddress, sessionId, tablename);
|
||||
} else reply.result = "no event handler";
|
||||
}
|
||||
}
|
||||
}
|
||||
ackSender.sendAckData(reply);
|
||||
});
|
||||
|
||||
/*
|
||||
Insert row event
|
||||
data: {
|
||||
tablename: "table name",
|
||||
fisdata: FisData in JsonObject, or JsonArray of FisData in JsonObject
|
||||
}
|
||||
<p/>
|
||||
return: {
|
||||
command: "insertrow",
|
||||
value: FisData in JsonObject,
|
||||
result: "success" or "failed"
|
||||
}
|
||||
*/
|
||||
server.addEventListener("insertrow", InsertDataStructure.class, (client, data, ackSender) -> {
|
||||
String remoteAddress = client.getRemoteAddress().toString();
|
||||
String sessionId = client.getSessionId().toString();
|
||||
raise_log(String.format("Client %s with id=%s request to insert table %s", remoteAddress, sessionId, data));
|
||||
ReplyInsertDataStructure reply = new ReplyInsertDataStructure();
|
||||
reply.command = "insertrow";
|
||||
reply.value = data;
|
||||
reply.result = "invalid data"; // default reply sebelum process
|
||||
|
||||
if (!data.tablename.isEmpty()){
|
||||
if (DateTimeHelper.isValidDate_yyyyMMdd(data.tablename)){
|
||||
var tablename = DateTimeHelper.ConvertToTableName(data.tablename);
|
||||
if (event!=null){
|
||||
reply.result = event.InsertFisDataToTable(remoteAddress, sessionId, tablename, data.fisdata);
|
||||
System.out.println("SocketIO insertrow result: "+reply.result);
|
||||
} else reply.result = "no event handler";
|
||||
}
|
||||
}
|
||||
ackSender.sendAckData(reply);
|
||||
});
|
||||
|
||||
/*
|
||||
Delete Row event
|
||||
data: {
|
||||
tablename: "table name",
|
||||
rowid: id of the row to be deleted, in integer
|
||||
}
|
||||
<p/>
|
||||
return: {
|
||||
command: "deleterow",
|
||||
value: {
|
||||
tablename: "table name",
|
||||
rowid: id of the row to be deleted, in integer
|
||||
},
|
||||
result: "success" or "failed"
|
||||
*/
|
||||
server.addEventListener("deleterow", DeleteRowStructure.class, (client, data, ackSender) -> {
|
||||
String remoteAddress = client.getRemoteAddress().toString();
|
||||
String sessionId = client.getSessionId().toString();
|
||||
raise_log(String.format("Client %s with id=%s request to delete row %s", remoteAddress, sessionId, data));
|
||||
|
||||
ReplyDeleteRowStructure reply = new ReplyDeleteRowStructure();
|
||||
reply.command = "deleterow";
|
||||
reply.value = data;
|
||||
reply.result = "invalid data"; // default reply sebelum process
|
||||
|
||||
if (!data.tablename.isEmpty()){
|
||||
if (DateTimeHelper.isValidDate_yyyyMMdd(data.tablename)){
|
||||
var tablename = DateTimeHelper.ConvertToTableName(data.tablename);
|
||||
if (event!=null){
|
||||
reply.result = event.DeleteFisDataFromTable(remoteAddress, sessionId, tablename, data.rowid);
|
||||
} else reply.result = "no event handler";
|
||||
}
|
||||
}
|
||||
ackSender.sendAckData(reply);
|
||||
});
|
||||
|
||||
/*
|
||||
Edit Row event
|
||||
data: {
|
||||
tablename: "table name",
|
||||
rowid: id of the row to be edited, in integer
|
||||
newdata: FisData in JsonObject
|
||||
}
|
||||
<p/>
|
||||
return: {
|
||||
command: "editrow",
|
||||
value: {
|
||||
tablename: "table name",
|
||||
rowid: id of the row to be edited, in integer
|
||||
newdata: FisData in JsonObject
|
||||
},
|
||||
result: "success" or "failed"
|
||||
*/
|
||||
server.addEventListener("editrow", EditRowStructure.class, (client, data, ackSender) -> {
|
||||
String remoteAddress = client.getRemoteAddress().toString();
|
||||
String sessionId = client.getSessionId().toString();
|
||||
raise_log(String.format("Client %s with id=%s request to edit row %s", remoteAddress, sessionId, data));
|
||||
|
||||
ReplyEditRowStructure reply = new ReplyEditRowStructure();
|
||||
reply.command = "editrow";
|
||||
reply.value = data;
|
||||
reply.result = "invalid data"; // default reply sebelum process
|
||||
|
||||
if (!data.tablename.isEmpty()){
|
||||
if (DateTimeHelper.isValidDate_yyyyMMdd(data.tablename)){
|
||||
var tablename = DateTimeHelper.ConvertToTableName(data.tablename);
|
||||
if (event!=null){
|
||||
reply.result = event.UpdateFisDataInTable(remoteAddress, sessionId, tablename, data.rowid, data.newdata);
|
||||
} else reply.result = "no event handler";
|
||||
}
|
||||
}
|
||||
ackSender.sendAckData(reply);
|
||||
});
|
||||
|
||||
/*
|
||||
Get Data event
|
||||
data: {
|
||||
tablename: "table name",
|
||||
filter: { // optional, can be null
|
||||
airlinecode: "filter by airline code", // optional, can be null
|
||||
flightnumber: "filter by flight number", // optional, can be null
|
||||
DA: "filter by DA", // optional, can be null
|
||||
DI: "filter by DI" // optional, can be null
|
||||
}
|
||||
}
|
||||
<p/>
|
||||
return: {
|
||||
command: "getdata",
|
||||
value: {
|
||||
tablename: "table name",
|
||||
filter: {
|
||||
airlinecode: "filter by airline code",
|
||||
flightnumber: "filter by flight number",
|
||||
DA: "filter by DA",
|
||||
DI: "filter by DI"
|
||||
}
|
||||
},
|
||||
result: "success" or "failed" or "no data",
|
||||
data: FisData in JsonArray
|
||||
}
|
||||
*/
|
||||
server.addEventListener("getdata", GetDataStructure.class, (client, data, ackSender) -> {
|
||||
String remoteAddress = client.getRemoteAddress().toString();
|
||||
String sessionId = client.getSessionId().toString();
|
||||
raise_log(String.format("Client %s with id=%s request to get data %s", remoteAddress, sessionId, GsonFormatter.toJson(data)));
|
||||
|
||||
ReplyGetDataStructure reply = new ReplyGetDataStructure();
|
||||
reply.command = "getdata";
|
||||
reply.value = data;
|
||||
reply.result = "invalid data"; // default reply sebelum process
|
||||
if (!data.tablename.isEmpty()){
|
||||
String tablename = DateTimeHelper.ConvertToTableName(data.tablename);
|
||||
try{
|
||||
if (event!=null){
|
||||
reply.data = event.GetFisDataFromTable(remoteAddress, sessionId, tablename,
|
||||
data.filter!=null ? data.filter.airlinecode:null,
|
||||
data.filter !=null ? data.filter.flightnumber:null,
|
||||
data.filter !=null ?data.filter.DA:null,
|
||||
data.filter !=null ? data.filter.DI:null);
|
||||
|
||||
|
||||
if (reply.data!=null && reply.data.length>0){
|
||||
reply.result = "success";
|
||||
|
||||
} else reply.result = "no data";
|
||||
} else reply.result = "no event handler";
|
||||
} catch (Exception e){
|
||||
System.out.println("SocketIO getdata error: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//System.out.println("SocketIO getdata reply: "+reply);
|
||||
ackSender.sendAckData(reply);
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
Export to XLSX event
|
||||
data: {
|
||||
tablename: "table name"
|
||||
}
|
||||
<p/>
|
||||
return: {
|
||||
command: "exportxlsx",
|
||||
tablename: "table name",
|
||||
result: "success" or "failed" or "no data",
|
||||
filename: "filename",
|
||||
size: file size in bytes
|
||||
}
|
||||
*/
|
||||
server.addEventListener("exportxlsx", ExportXLSXStructure.class, ((socketIOClient, exportXL, ackRequest) -> {
|
||||
String remoteAddress = socketIOClient.getRemoteAddress().toString();
|
||||
String sessionId = socketIOClient.getSessionId().toString();
|
||||
raise_log(String.format("Client %s with id=%s request to export xlsx %s", remoteAddress, sessionId, GsonFormatter.toJson(exportXL)));
|
||||
|
||||
ReplyExportXLSXStructure reply = new ReplyExportXLSXStructure();
|
||||
reply.tablename = exportXL.tablename;
|
||||
reply.result = "invalid data"; // default reply sebelum process
|
||||
|
||||
if (exportXL.tablename!=null && !exportXL.tablename.isEmpty()){
|
||||
if (DateTimeHelper.isValidDate_yyyyMMdd(exportXL.tablename)){
|
||||
var tablename = DateTimeHelper.ConvertToTableName(exportXL.tablename);
|
||||
if (event!=null){
|
||||
Path path = event.ExportToXLSX(remoteAddress, sessionId, tablename);
|
||||
if (path!=null){
|
||||
if (path.toFile().isFile()){
|
||||
reply.size = (int) path.toFile().length();
|
||||
reply.filename = "/export/"+path.getFileName().toString();
|
||||
reply.result = "success";
|
||||
} else reply.result = "file not found";
|
||||
} else reply.result = "no file or unauthorized";
|
||||
} else reply.result = "no event handler";
|
||||
}
|
||||
}
|
||||
|
||||
//System.out.printf("SocketIO exportxlsx reply: %s, length: %d\n", reply.result, reply.size);
|
||||
ackRequest.sendAckData(reply);
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Socket.io data object to JsonObject
|
||||
*
|
||||
* @param data must be instance of JsonObject
|
||||
* @param keys must contains these keys
|
||||
* @return JsonObject if data is instance of JsonObject and contains the keys, otherwise return null
|
||||
*/
|
||||
private JsonObject ConvertDataToJsonObject(Object data, String... keys){
|
||||
if (data != null){
|
||||
if (data instanceof JsonObject obj){
|
||||
if (keys!=null && keys.length>0){
|
||||
for(String key : keys){
|
||||
if (!obj.has(key)) return null;
|
||||
Object val = obj.get(key);
|
||||
if (val==null) return null;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the server
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public void Stop(){
|
||||
|
||||
if (server!=null) server.stop();
|
||||
raise_log("SocketIO server stopped");
|
||||
}
|
||||
|
||||
private void raise_log(String msg){
|
||||
System.out.println(msg);
|
||||
}
|
||||
}
|
||||
7
src/Web/SocketIOConnections.java
Normal file
7
src/Web/SocketIOConnections.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package Web;
|
||||
|
||||
public class SocketIOConnections {
|
||||
public String id;
|
||||
public String remoteAddress;
|
||||
public String role;
|
||||
}
|
||||
19
src/Web/SocketIOEvents.java
Normal file
19
src/Web/SocketIOEvents.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package Web;
|
||||
|
||||
import Database.FisData;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface SocketIOEvents {
|
||||
void Connected(String remoteAddress, String sessionId);
|
||||
void Disconnected(String remoteAddress, String sessionId);
|
||||
String[] GetTables(String remoteAddress, String sessionId);
|
||||
String CreateTable(String remoteAddress, String sessionId, String tableName);
|
||||
String DropTable(String remoteAddress, String sessionId,String tableName);
|
||||
String InsertFisDataToTable(String remoteAddress, String sessionId, String tableName, FisData... data);
|
||||
String DeleteFisDataFromTable(String remoteAddress, String sessionId, String tableName, int index);
|
||||
String UpdateFisDataInTable(String remoteAddress, String sessionId, String tableName, int index, FisData data);
|
||||
FisData[] GetFisDataFromTable(String remoteAddress, String sessionId, String tableName, String filter_airlinecode, String filter_flightnumber, String filter_DA, String filter_DI);
|
||||
Path ExportToXLSX(String remoteAddress, String sessionId, String tablename);
|
||||
String Login(String remoteAddress, String sessionId, String username, String password);
|
||||
}
|
||||
125
src/Web/WebServer.java
Normal file
125
src/Web/WebServer.java
Normal file
@@ -0,0 +1,125 @@
|
||||
package Web;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import io.javalin.Javalin;
|
||||
import io.javalin.http.UploadedFile;
|
||||
import io.javalin.http.staticfiles.Location;
|
||||
import lombok.Getter;
|
||||
import org.eclipse.jetty.util.Loader;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
public class WebServer {
|
||||
final Javalin app;
|
||||
private @Getter final Path publicpath;
|
||||
private @Getter final Path exportpath;
|
||||
private @Getter final Path importpath;
|
||||
private final WebServerEvent we;
|
||||
public WebServer(WebServerEvent event){
|
||||
this.we = event;
|
||||
String userdir = System.getProperty("user.dir");
|
||||
System.out.println("User dir: "+userdir);
|
||||
|
||||
exportpath = Paths.get(userdir, "export");
|
||||
importpath = Paths.get(userdir, "import");
|
||||
Path temp = Paths.get(userdir, "temp");
|
||||
|
||||
createfolder(exportpath);
|
||||
createfolder(importpath);
|
||||
createfolder(temp);
|
||||
|
||||
app = Javalin.create(config -> {
|
||||
config.staticFiles.add("/public");
|
||||
if (exportpath.toFile().isDirectory()){
|
||||
config.staticFiles.add(exportconfig ->{
|
||||
exportconfig.hostedPath = "/export";
|
||||
exportconfig.directory = exportpath.toString();
|
||||
exportconfig.location = Location.EXTERNAL;
|
||||
});
|
||||
|
||||
}
|
||||
if (importpath.toFile().isDirectory()){
|
||||
config.staticFiles.add(importconfig ->{
|
||||
importconfig.hostedPath = "/import";
|
||||
importconfig.directory = importpath.toString();
|
||||
importconfig.location = Location.EXTERNAL;
|
||||
});
|
||||
}
|
||||
config.jetty.multipartConfig.cacheDirectory(temp.toString());
|
||||
});
|
||||
|
||||
String resourcefolder = Loader.getResource("public").toString().replace("file:/","").replace('/', File.separatorChar);
|
||||
publicpath = Paths.get(resourcefolder);
|
||||
|
||||
app.post("/import", ctx ->{
|
||||
String tablename = ctx.formParam("tablename");
|
||||
UploadedFile file = ctx.uploadedFile("file");
|
||||
|
||||
JsonObject response = new JsonObject();
|
||||
if (tablename!=null && !tablename.isEmpty()){
|
||||
response.addProperty("tablename", tablename);
|
||||
if (file!=null){
|
||||
response.addProperty("filename", file.filename());
|
||||
System.out.println("Import request: tablename: "+tablename+", filename: "+file.filename()+", size: "+file.size());
|
||||
try(InputStream is = file.content()){
|
||||
Path dest = importpath.resolve(file.filename());
|
||||
Files.copy(is,dest, StandardCopyOption.REPLACE_EXISTING);
|
||||
|
||||
if (we!=null){
|
||||
String result = we.onImport(tablename, dest);
|
||||
response.addProperty("result",result);
|
||||
ctx.status("success".equals(result)?200:500).result(response.toString());
|
||||
} else {
|
||||
response.addProperty("result","no event handler");
|
||||
ctx.status(500).result(response.toString());
|
||||
}
|
||||
} catch (Exception e){
|
||||
response.addProperty("result","error copying file: "+e.getMessage());
|
||||
ctx.status(500).result(response.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
} else {
|
||||
response.addProperty("result","file not provided");
|
||||
ctx.status(400).result(response.toString());
|
||||
}
|
||||
} else {
|
||||
response.addProperty("result","tablename not provided");
|
||||
ctx.status(400).result(response.toString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private void createfolder(Path path){
|
||||
if (Files.exists(path)) return;
|
||||
try {
|
||||
Files.createDirectories(path);
|
||||
System.out.println("Folder created: "+path);
|
||||
} catch (Exception e){
|
||||
System.out.println("Error creating folder: "+path+", error: "+e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void start(String localip, int port){
|
||||
app.start(localip, port);
|
||||
}
|
||||
|
||||
public void start(int port){
|
||||
app.start(port);
|
||||
|
||||
}
|
||||
|
||||
public void stop(){
|
||||
app.stop();
|
||||
}
|
||||
}
|
||||
7
src/Web/WebServerEvent.java
Normal file
7
src/Web/WebServerEvent.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package Web;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface WebServerEvent {
|
||||
String onImport(String tablename, Path filename);
|
||||
}
|
||||
492
src/XLSX/ExcelFile.java
Normal file
492
src/XLSX/ExcelFile.java
Normal file
@@ -0,0 +1,492 @@
|
||||
package XLSX;
|
||||
|
||||
import Database.FisData;
|
||||
import lombok.Cleanup;
|
||||
import lombok.val;
|
||||
import org.apache.poi.ss.usermodel.*;
|
||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class ExcelFile {
|
||||
|
||||
/**
|
||||
* Create Cell with specified value and style
|
||||
* @param row target row to add cell to
|
||||
* @param index cell index, start from 0
|
||||
* @param value cell value
|
||||
* @param style cell style
|
||||
* @return created cell
|
||||
*/
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
private static Cell CreateCell(Row row, int index, String value, CellStyle style){
|
||||
val cell = row.createCell(index);
|
||||
cell.setCellValue(value);
|
||||
cell.setCellStyle(style);
|
||||
return cell;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write FisData to Row
|
||||
* @param row target row to write data
|
||||
* @param data FisData to write
|
||||
*/
|
||||
private static void WriteFisData(Row row, FisData data){
|
||||
row.createCell(0).setCellValue(data.id);
|
||||
row.createCell(1).setCellValue(data.AirlineCode);
|
||||
row.createCell(2).setCellValue(data.FlightNumber);
|
||||
row.createCell(3).setCellValue(data.DA);
|
||||
row.createCell(4).setCellValue(data.DI);
|
||||
row.createCell(5).setCellValue(data.Origin);
|
||||
row.createCell(6).setCellValue(data.Destination);
|
||||
row.createCell(7).setCellValue(data.Gate);
|
||||
row.createCell(8).setCellValue(data.Terminal);
|
||||
row.createCell(9).setCellValue(data.BaggageClaim);
|
||||
row.createCell(10).setCellValue(data.CheckinCounter);
|
||||
row.createCell(11).setCellValue(dtf.format(data.lastUpdated));
|
||||
row.createCell(12).setCellValue(dtf.format(data.scheduledTime));
|
||||
row.createCell(13).setCellValue(dtf.format(data.estimatedTime));
|
||||
row.createCell(14).setCellValue(dtf.format(data.actualTime));
|
||||
row.createCell(15).setCellValue(dtf.format(data.gateScheduledTime));
|
||||
row.createCell(16).setCellValue(dtf.format(data.gateEstimatedTime));
|
||||
row.createCell(17).setCellValue(dtf.format(data.gateActualTime));
|
||||
row.createCell(18).setCellValue(data.weather);
|
||||
row.createCell(19).setCellValue(String.format("%.1f",data.temperatureC));
|
||||
row.createCell(20).setCellValue(data.Remark);
|
||||
row.createCell(21).setCellValue(data.IsRead);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Empty XLSX Workbook with specified filename and sheetname<br/>
|
||||
* The created workbook will have the following columns:<br/>
|
||||
* ID, AirlineCode, FlightNumber, DA, DI, Origin, Destination, Gate, Terminal, BaggageClaim, CheckinCounter, lastUpdated, scheduledTime, estimatedTime, actualTime, gateScheduledTime, gateEstimatedTime, gateActualTime, weather, temperatureC, Remark, IsRead
|
||||
* @param Filename target filename
|
||||
* @param Sheetname target sheetname
|
||||
* @return true if success, false if failed
|
||||
*/
|
||||
public static boolean CreateEmptyWorkbook(String Filename, String Sheetname){
|
||||
try {
|
||||
@SuppressWarnings("unused")
|
||||
int maxrow = 100;
|
||||
// create workbook
|
||||
@Cleanup val workbook = new XSSFWorkbook();
|
||||
// create sheet
|
||||
val sheet = workbook.createSheet(Sheetname);
|
||||
|
||||
// create font
|
||||
val headerFont = workbook.createFont();
|
||||
headerFont.setBold(true);
|
||||
headerFont.setFontHeightInPoints((short) 16);
|
||||
headerFont.setFontName("Arial");
|
||||
|
||||
// create cell style
|
||||
val headerCellStyle = workbook.createCellStyle();
|
||||
headerCellStyle.setFont(headerFont);
|
||||
|
||||
// create cell headers
|
||||
val headerRow = sheet.createRow(0);
|
||||
CreateCell(headerRow, 0, "ID", headerCellStyle);
|
||||
//RestrictColumnToNumbersOnly(sheet, 0, 0, 1, maxrow, 1, 999999);
|
||||
CreateCell(headerRow, 1, "AirlineCode", headerCellStyle);
|
||||
//RestrictColumnToExactLength(sheet, 1, 1, 1, maxrow, 2);
|
||||
CreateCell(headerRow, 2, "FlightNumber", headerCellStyle);
|
||||
//RestrictColumnToNumbersOnly(sheet, 2, 2, 1, maxrow, 1, 9999);
|
||||
CreateCell(headerRow, 3, "DA", headerCellStyle);
|
||||
//RestrictCellToCharacter(sheet, 3, 3, 1, maxrow, "D", "A");
|
||||
CreateCell(headerRow, 4, "DI", headerCellStyle);
|
||||
//RestrictCellToCharacter(sheet, 4, 4, 1, maxrow, "D", "I");
|
||||
CreateCell(headerRow, 5, "Origin", headerCellStyle);
|
||||
//RestrictColumnToExactLength(sheet, 5, 5, 1, maxrow, 3);
|
||||
CreateCell(headerRow, 6, "Destination", headerCellStyle);
|
||||
|
||||
// Berikut ini belum ada batasan
|
||||
CreateCell(headerRow, 7, "Gate", headerCellStyle);
|
||||
CreateCell(headerRow, 8, "Terminal", headerCellStyle);
|
||||
CreateCell(headerRow, 9, "BaggageClaim", headerCellStyle);
|
||||
CreateCell(headerRow, 10, "CheckinCounter", headerCellStyle);
|
||||
|
||||
CreateCell(headerRow, 11, "lastUpdated", headerCellStyle);
|
||||
CreateCell(headerRow, 12, "scheduledTime", headerCellStyle);
|
||||
CreateCell(headerRow, 13, "estimatedTime", headerCellStyle);
|
||||
CreateCell(headerRow, 14, "actualTime", headerCellStyle);
|
||||
CreateCell(headerRow, 15, "gateScheduledTime", headerCellStyle);
|
||||
CreateCell(headerRow, 16, "gateEstimatedTime", headerCellStyle);
|
||||
CreateCell(headerRow, 17, "gateActualTime", headerCellStyle);
|
||||
//RestrictCellToDateTime(sheet, 11, 17, 1, maxrow);
|
||||
|
||||
CreateCell(headerRow, 18, "weather", headerCellStyle);
|
||||
CreateCell(headerRow, 19, "temperatureC", headerCellStyle);
|
||||
CreateCell(headerRow, 20, "Remark", headerCellStyle);
|
||||
CreateCell(headerRow, 21, "IsRead", headerCellStyle);
|
||||
//RestrictCellToCharacter(sheet, 21, 21, 1, maxrow, "Y", "N", "0", "1");
|
||||
|
||||
@Cleanup val out = new FileOutputStream(Filename);
|
||||
workbook.write(out);
|
||||
return true;
|
||||
} catch (Exception e){
|
||||
System.out.println("Error creating empty workbook "+Filename+ " with sheetname "+Sheetname+", Exception: "+e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save FisData array to XLSX file<br/>
|
||||
* If the file does not exist, it will create a new file with the specified filename and sheetname<br/>
|
||||
* If the file already exists, it will replace the sheet with the specified sheetname with the new data<br/>
|
||||
* Row 0 is header, Row 1 and so on is the data<br/>
|
||||
* @param filename target filename for XLSX file
|
||||
* @param sheetname target sheetname inside XLSX file
|
||||
* @param data FisData array to save
|
||||
* @return true if success, false if failed
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static boolean SaveToXLSX(String filename, String sheetname, FisData[] data){
|
||||
try{
|
||||
File ff = new File(filename);
|
||||
if (!ff.exists()){
|
||||
if (!CreateEmptyWorkbook(filename, sheetname)){
|
||||
return false;
|
||||
} else System.out.println("New workbook "+filename+" with sheetname "+sheetname+" created");
|
||||
}
|
||||
|
||||
// sampai sini file ada
|
||||
if (data!=null){
|
||||
@Cleanup FileInputStream fis = new FileInputStream(filename);
|
||||
@Cleanup val workbook = new XSSFWorkbook(fis);
|
||||
var sheet = workbook.getSheet(sheetname);
|
||||
if (sheet==null) sheet = workbook.createSheet(sheetname);
|
||||
|
||||
for(int i=0; i<data.length; i++){
|
||||
val row = sheet.createRow(1+i);
|
||||
WriteFisData(row, data[i]);
|
||||
}
|
||||
|
||||
@Cleanup val out = new FileOutputStream(filename);
|
||||
workbook.write(out);
|
||||
|
||||
return true;
|
||||
} else System.out.println("No data to save to "+filename);
|
||||
} catch (Exception e){
|
||||
System.out.println("Error saving to XLSX: "+e.getMessage());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private final static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/**
|
||||
* Load FisData from XLSX file<br/>
|
||||
* @param filename target filename for XLSX file
|
||||
* @param sheetname target sheetname inside XLSX file, if null or empty, will load all sheet
|
||||
* @return FisData array, empty if failed
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public static FisData[] LoadFromXLSX(String filename, String sheetname){
|
||||
System.out.println("LoadFromXLSX: "+filename+", sheetname: "+sheetname);
|
||||
try{
|
||||
File ff = new File(filename);
|
||||
if (ff.isFile()){
|
||||
@Cleanup val fis = new FileInputStream(ff);
|
||||
@Cleanup val workbook = new XSSFWorkbook(fis);
|
||||
if (sheetname!=null && !sheetname.isEmpty()){
|
||||
// spesifik sheet
|
||||
val sheet = workbook.getSheet(sheetname);
|
||||
if (sheet!=null){
|
||||
val result = new ArrayList<FisData>();
|
||||
val header = sheet.getRow(0);
|
||||
if (HeaderRowComplete(header)){
|
||||
System.out.println("LoadFromXLSX Header row complete");
|
||||
// baca mulai row 1
|
||||
for(int i=1; i<=sheet.getLastRowNum(); i++){
|
||||
val rr = sheet.getRow(i);
|
||||
if (rr!=null){
|
||||
try{
|
||||
val data = RowToFisData(result, rr);
|
||||
//System.out.println("Row read: "+data);
|
||||
} catch (Exception e){
|
||||
System.out.println("Error reading row: "+e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return result.toArray(new FisData[0]);
|
||||
} else System.out.println("Sheet "+sheetname+" not found in "+filename);
|
||||
}
|
||||
} else System.out.println("LoadFromXLSX failed, File "+filename+" not found");
|
||||
} catch (Exception e){
|
||||
System.out.println("Error loading from XLSX "+filename+", Exception: " +e.getMessage());
|
||||
}
|
||||
return new FisData[0];
|
||||
}
|
||||
|
||||
private static double getValueasDouble(Cell cc){
|
||||
if (cc!=null){
|
||||
CellType ct = cc.getCellType();
|
||||
return switch (ct){
|
||||
case STRING -> {
|
||||
try{
|
||||
yield Double.parseDouble(cc.getStringCellValue());
|
||||
} catch (Exception e){
|
||||
yield 0;
|
||||
}
|
||||
}
|
||||
case NUMERIC -> cc.getNumericCellValue();
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static String getValueastring(Cell cc){
|
||||
if (cc!=null){
|
||||
CellType ct = cc.getCellType();
|
||||
return switch (ct){
|
||||
case STRING -> cc.getStringCellValue();
|
||||
case NUMERIC -> String.valueOf((int)cc.getNumericCellValue());
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static int getValueasInt(Cell cc){
|
||||
if (cc!=null){
|
||||
CellType ct = cc.getCellType();
|
||||
return switch (ct){
|
||||
case STRING ->{
|
||||
try{
|
||||
yield Integer.parseInt(cc.getStringCellValue());
|
||||
} catch (Exception e){
|
||||
yield 0;
|
||||
}
|
||||
}
|
||||
case NUMERIC -> (int) cc.getNumericCellValue();
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static char getValueasChar(Cell cc){
|
||||
if (cc!=null){
|
||||
CellType ct = cc.getCellType();
|
||||
return switch (ct){
|
||||
case STRING -> cc.getStringCellValue().charAt(0);
|
||||
case NUMERIC -> (char) cc.getNumericCellValue();
|
||||
default -> 0;
|
||||
};
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static LocalDateTime getValueasDateTime(Cell cc){
|
||||
if (cc!=null){
|
||||
CellType ct = cc.getCellType();
|
||||
return switch (ct){
|
||||
case STRING -> {
|
||||
String val = cc.getStringCellValue();
|
||||
try{
|
||||
yield LocalDateTime.parse(val, dtf);
|
||||
} catch (Exception e){
|
||||
yield null;
|
||||
}
|
||||
}
|
||||
case NUMERIC -> {
|
||||
try{
|
||||
yield cc.getLocalDateTimeCellValue();
|
||||
} catch (Exception e){
|
||||
yield null;
|
||||
}
|
||||
}
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static FisData RowToFisData(ArrayList<FisData> result, Row rr) {
|
||||
val data = new FisData();
|
||||
data.id = getValueasInt(rr.getCell(0));
|
||||
data.AirlineCode = getValueastring(rr.getCell(1));
|
||||
data.FlightNumber = getValueastring(rr.getCell(2));
|
||||
data.DA = getValueasChar(rr.getCell(3));
|
||||
data.DI = getValueasChar(rr.getCell(4));
|
||||
data.Origin = getValueastring(rr.getCell(5));
|
||||
data.Destination = getValueastring(rr.getCell(6));
|
||||
data.Gate = getValueastring(rr.getCell(7));
|
||||
data.Terminal = getValueastring(rr.getCell(8));
|
||||
data.BaggageClaim = getValueastring(rr.getCell(9));
|
||||
data.CheckinCounter = getValueastring(rr.getCell(10));
|
||||
data.lastUpdated = getValueasDateTime(rr.getCell(11));
|
||||
data.scheduledTime = getValueasDateTime(rr.getCell(12));
|
||||
data.estimatedTime = getValueasDateTime(rr.getCell(13));
|
||||
data.actualTime = getValueasDateTime(rr.getCell(14));
|
||||
data.gateScheduledTime = getValueasDateTime(rr.getCell(15));
|
||||
data.gateEstimatedTime = getValueasDateTime(rr.getCell(16));
|
||||
data.gateActualTime = getValueasDateTime(rr.getCell(17));
|
||||
data.weather = getValueastring(rr.getCell(18));
|
||||
data.temperatureC = getValueasDouble(rr.getCell(19));
|
||||
data.Remark = getValueastring(rr.getCell(20));
|
||||
data.IsRead = getValueastring(rr.getCell(21));
|
||||
result.add(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save byte array to XLSX file<br/>
|
||||
* The file will be saved with the specified filename<br/>
|
||||
* The file will be checked if it is a valid XLSX file<br/>
|
||||
*
|
||||
* @param filename target filename for XLSX file
|
||||
* @param size size of filedata
|
||||
* @param filedata byte array to save
|
||||
* @return success if success, error: message if failed
|
||||
*/
|
||||
public static String SaveToXLSX(String filename, int size, byte[] filedata) {
|
||||
if (filename!=null && !filename.isEmpty()){
|
||||
if (size>0){
|
||||
if (filedata!=null && filedata.length==size){
|
||||
try{
|
||||
Files.write(Path.of(filename), filedata);
|
||||
} catch (Exception e){
|
||||
return "error: "+e.getMessage();
|
||||
}
|
||||
|
||||
// sampai sini file sudah tertulis
|
||||
// cek apakah valid XLSX file
|
||||
try{
|
||||
@Cleanup val fis = new FileInputStream(filename);
|
||||
@Cleanup val workbook = new XSSFWorkbook(fis);
|
||||
val sheet = workbook.getSheetAt(0);
|
||||
if (sheet!=null){
|
||||
if (HeaderRowComplete(sheet.getRow(0))){
|
||||
|
||||
return "success";
|
||||
} else return "error: header row not complete";
|
||||
} else return "error: sheet is null, not valid XLSX file";
|
||||
} catch (Exception e){
|
||||
return "error: "+e.getMessage();
|
||||
}
|
||||
} else return "error: filedata is null or size is not equal to filedata length";
|
||||
} else return "error: size is 0";
|
||||
} else return "error: filename is empty";
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if header row is complete<br/>
|
||||
* A complete header row contains ID, AirlineCode, FlightNumber, DA, DI, Origin, Destination, Gate, Terminal, BaggageClaim, CheckinCounter, lastUpdated, scheduledTime, estimatedTime, actualTime, gateScheduledTime, gateEstimatedTime, gateActualTime, weather, temperatureC, Remark, IsRead
|
||||
* @param header Row to check
|
||||
* @return true if complete, false if not complete
|
||||
*/
|
||||
private static boolean HeaderRowComplete(Row header){
|
||||
if (header!=null){
|
||||
if (!header.getCell(0).getStringCellValue().equals("ID")) return false;
|
||||
if (!header.getCell(1).getStringCellValue().equals("AirlineCode")) return false;
|
||||
if (!header.getCell(2).getStringCellValue().equals("FlightNumber")) return false;
|
||||
if (!header.getCell(3).getStringCellValue().equals("DA")) return false;
|
||||
if (!header.getCell(4).getStringCellValue().equals("DI")) return false;
|
||||
if (!header.getCell(5).getStringCellValue().equals("Origin")) return false;
|
||||
if (!header.getCell(6).getStringCellValue().equals("Destination")) return false;
|
||||
if (!header.getCell(7).getStringCellValue().equals("Gate")) return false;
|
||||
if (!header.getCell(8).getStringCellValue().equals("Terminal")) return false;
|
||||
if (!header.getCell(9).getStringCellValue().equals("BaggageClaim")) return false;
|
||||
if (!header.getCell(10).getStringCellValue().equals("CheckinCounter")) return false;
|
||||
if (!header.getCell(11).getStringCellValue().equals("lastUpdated")) return false;
|
||||
if (!header.getCell(12).getStringCellValue().equals("scheduledTime")) return false;
|
||||
if (!header.getCell(13).getStringCellValue().equals("estimatedTime")) return false;
|
||||
if (!header.getCell(14).getStringCellValue().equals("actualTime")) return false;
|
||||
if (!header.getCell(15).getStringCellValue().equals("gateScheduledTime")) return false;
|
||||
if (!header.getCell(16).getStringCellValue().equals("gateEstimatedTime")) return false;
|
||||
if (!header.getCell(17).getStringCellValue().equals("gateActualTime")) return false;
|
||||
if (!header.getCell(18).getStringCellValue().equals("weather")) return false;
|
||||
if (!header.getCell(19).getStringCellValue().equals("temperatureC")) return false;
|
||||
if (!header.getCell(20).getStringCellValue().equals("Remark")) return false;
|
||||
if (!header.getCell(21).getStringCellValue().equals("IsRead")) return false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void RestrictColumnToNumbersOnly(Sheet sheet, int columnstart, int columnend, int rowstart, int rowend, int minvalue, int maxvalue){
|
||||
if (sheet!=null){
|
||||
DataValidationHelper dvHelper = sheet.getDataValidationHelper();
|
||||
DataValidationConstraint dvConstraint = dvHelper.createNumericConstraint(DataValidationConstraint.ValidationType.INTEGER, DataValidationConstraint.OperatorType.BETWEEN, String.valueOf(minvalue), String.valueOf(maxvalue));
|
||||
CellRangeAddressList addressList = new CellRangeAddressList(rowstart, rowend, columnstart, columnend);
|
||||
DataValidation validation = dvHelper.createValidation(dvConstraint, addressList);
|
||||
sheet.addValidationData(validation);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void RestrictColumnToExactLength(Sheet sheet, int columnstart, int columnend, int rowstart, int rowend, int length){
|
||||
if (sheet!=null){
|
||||
DataValidationHelper dvHelper = sheet.getDataValidationHelper();
|
||||
|
||||
DataValidationConstraint dvConstraint = dvHelper.createTextLengthConstraint(DataValidationConstraint.OperatorType.EQUAL, String.valueOf(length), null);
|
||||
CellRangeAddressList addressList = new CellRangeAddressList(rowstart, rowend, columnstart, columnend);
|
||||
DataValidation validation = dvHelper.createValidation(dvConstraint, addressList);
|
||||
sheet.addValidationData(validation);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void RestrictCellToDateTime(Sheet sheet, int columnstart, int columnend, int rowstart, int rowend){
|
||||
if (sheet!=null){
|
||||
DataValidationHelper dvHelper = sheet.getDataValidationHelper();
|
||||
DataValidationConstraint dvConstraint = dvHelper.createDateConstraint(DataValidationConstraint.OperatorType.BETWEEN, "1970-01-01", "2099-12-31", "yyyy-MM-dd HH:mm:ss");
|
||||
CellRangeAddressList addressList = new CellRangeAddressList(rowstart, rowend, columnstart, columnend);
|
||||
DataValidation validation = dvHelper.createValidation(dvConstraint, addressList);
|
||||
sheet.addValidationData(validation);
|
||||
|
||||
CellStyle cs = sheet.getWorkbook().createCellStyle();
|
||||
CreationHelper createHelper = sheet.getWorkbook().getCreationHelper();
|
||||
cs.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd HH:mm:ss"));
|
||||
for(int i=rowstart; i<=rowend; i++){
|
||||
Row row = sheet.getRow(i);
|
||||
if (row!=null){
|
||||
for(int j=columnstart; j<=columnend; j++){
|
||||
Cell cell = row.getCell(j);
|
||||
if (cell!=null){
|
||||
cell.setCellStyle(cs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static void RestrictCellToCharacter(Sheet sheet, int columnstart, int columend, int rowstart, int rowend, String... allowedchar){
|
||||
if (sheet!=null){
|
||||
DataValidationHelper dvHelper = sheet.getDataValidationHelper();
|
||||
|
||||
DataValidationConstraint dvConstraint = dvHelper.createExplicitListConstraint(allowedchar);
|
||||
CellRangeAddressList addressList = new CellRangeAddressList(rowstart, rowend, columnstart, columend);
|
||||
DataValidation validation = dvHelper.createValidation(dvConstraint, addressList);
|
||||
validation.setShowErrorBox(true);
|
||||
sheet.addValidationData(validation);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
3
src/resources/META-INF/MANIFEST.MF
Normal file
3
src/resources/META-INF/MANIFEST.MF
Normal file
@@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Main-Class: MiniFIS
|
||||
|
||||
46
src/resources/public/FisData.js
Normal file
46
src/resources/public/FisData.js
Normal file
@@ -0,0 +1,46 @@
|
||||
class FisData{
|
||||
constructor(){
|
||||
this.id = 0;
|
||||
// AirlineCode is 2 characters string
|
||||
this.AirlineCode = null;
|
||||
// FlightNumber is 4 digits string
|
||||
this.FlightNumber = null;
|
||||
// DA is 'D' for Departure, 'A' for Arrival
|
||||
this.DA = null;
|
||||
// DI is 'D' for Domestic, 'I' for International
|
||||
this.DI = null;
|
||||
// Origin is CityDetail object
|
||||
this.Origin = null;
|
||||
// Destination is Array of CityDetail object
|
||||
this.Destination = null;
|
||||
// Gate is string
|
||||
this.Gate = null;
|
||||
// Terminal is string
|
||||
this.Terminal = null;
|
||||
// BaggageClaim is string
|
||||
this.BaggageClaim = null;
|
||||
// CheckinCounter is string
|
||||
this.CheckinCounter = null;
|
||||
// lastUpdated is String in format yyyy-MM-dd HH:mm:ss
|
||||
this.lastUpdated = null;
|
||||
// scheduledTime is String in format yyyy-MM-dd HH:mm:ss
|
||||
this.scheduledTime = null;
|
||||
// estimatedTime is String in format yyyy-MM-dd HH:mm:ss
|
||||
this.estimatedTime = null;
|
||||
// actualTime is String in format yyyy-MM-dd HH:mm:ss
|
||||
this.actualTime = null;
|
||||
// gateScheduledTime is String in format yyyy-MM-dd HH:mm:ss
|
||||
this.gateScheduledTime = null;
|
||||
// gateEstimatedTime is String in format yyyy-MM-dd HH:mm:ss
|
||||
this.gateEstimatedTime = null;
|
||||
// gateActualTime is String in format yyyy-MM-dd HH:mm:ss
|
||||
this.gateActualTime = null;
|
||||
// weather is String
|
||||
this.weather = null;
|
||||
// temperatureF is number
|
||||
this.temperatureC = 0.0;
|
||||
// Remark is String
|
||||
this.Remark = null;
|
||||
this.IsRead = null;
|
||||
}
|
||||
}
|
||||
18
src/resources/public/citydetail.js
Normal file
18
src/resources/public/citydetail.js
Normal file
@@ -0,0 +1,18 @@
|
||||
class CityDetail{
|
||||
constructor(){
|
||||
this.CityName = '';
|
||||
this.AirportName = '';
|
||||
this.AirportCode = '';
|
||||
this.StateCode = '';
|
||||
this.Country = '';
|
||||
}
|
||||
/*
|
||||
constructor(CityName, AirportName, AirportCode, StateCode, Country){
|
||||
this.CityName = CityName;
|
||||
this.AirportName = AirportName;
|
||||
this.AirportCode = AirportCode;
|
||||
this.StateCode = StateCode;
|
||||
this.Country = Country
|
||||
}
|
||||
*/
|
||||
}
|
||||
342
src/resources/public/communication.js
Normal file
342
src/resources/public/communication.js
Normal file
@@ -0,0 +1,342 @@
|
||||
|
||||
/**
|
||||
* Check if socket.io-client still connected
|
||||
* @param {Socket} socket
|
||||
* @returns true if connected
|
||||
*/
|
||||
function socketvalid(socket){
|
||||
return (socket!=null && socket.connected);
|
||||
}
|
||||
|
||||
function gettablenames(socket, callback){
|
||||
if (socketvalid(socket)){
|
||||
socket.emit("gettablenames", (reply)=>{
|
||||
callback?.(reply?.data);
|
||||
});
|
||||
} else callback?.("no socket");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create table
|
||||
* @param {Socket} socket Socketio connection
|
||||
* @param {String} tablename in format yyyy-MM-dd
|
||||
* @param {Function} callback Callback function to handle result with result in string : "success" | "failed" | "invalid data" | "no event handler"
|
||||
*/
|
||||
function createtable(socket, tablename,callback){
|
||||
if (socketvalid(socket)){
|
||||
socket.emit("createtable", tablename, (reply)=>{
|
||||
callback?.(reply?.result);
|
||||
})
|
||||
|
||||
} else callback?.("no socket");
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop table
|
||||
* @param {Socket} socket Socketio connection
|
||||
* @param {String} tablename in format yyyy-MM-dd
|
||||
* @param {Function} callback Callback function to handle result with result in string : "success" | "failed" | "invalid data" | "no event handler"
|
||||
*/
|
||||
function droptable(socket, tablename, callback){
|
||||
if (socketvalid(socket)){
|
||||
socket.emit("droptable",tablename,(reply)=>{
|
||||
callback?.(reply?.result);
|
||||
})
|
||||
|
||||
} else callback?.("no socket");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete row in a table
|
||||
*
|
||||
* The request data is {
|
||||
* tablename: tablename,
|
||||
* rowid: rowid
|
||||
* }
|
||||
*
|
||||
* The reply format is {
|
||||
* command: "deleterow",
|
||||
* value: {
|
||||
* tablename: tablename,
|
||||
* rowid: rowid,
|
||||
* }
|
||||
* result: "success" | "failed" | "invalid data" | "no event handler"
|
||||
* }
|
||||
* @param {Socket} socket Socketio connection
|
||||
* @param {String} tablename Table name in format yyyy-MM-dd
|
||||
* @param {number} rowid Row Index to delete
|
||||
* @param {Function} callback Callback function to handle result with result in string : "success" | "failed" | "invalid data" | "no event handler"
|
||||
*/
|
||||
function deleterow(socket, tablename, rowid, callback){
|
||||
if (socketvalid){
|
||||
let value = {
|
||||
tablename: tablename,
|
||||
rowid: rowid
|
||||
}
|
||||
socket.emit("deleterow",value,(reply)=>{
|
||||
callback?.(reply?.result);
|
||||
})
|
||||
} else callback?.("no socket");
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert fisdata object to database
|
||||
*
|
||||
* The request data is {
|
||||
* tablename: tablename,
|
||||
* fisdata: fisdata
|
||||
* }
|
||||
*
|
||||
* The reply format is {
|
||||
* command: "insertrow",
|
||||
* value: {
|
||||
* tablename: tablename,
|
||||
* fisdata: fisdata
|
||||
* }
|
||||
* result: "success" | "failed" | "invalid data" | "no event handler"
|
||||
* }
|
||||
* @param {Socket} socket Socketio connection
|
||||
* @param {String|null} tablename tablename in format yyyy-MM-dd
|
||||
* @param {FisData} fisdata FisData object to insert
|
||||
* @param {Function} callback Callback function to handle result with result in string : "success" | "failed" | "invalid data" | "no event handler"
|
||||
*/
|
||||
function insertrow(socket, tablename, fisdata, callback){
|
||||
if (socketvalid){
|
||||
let value = {
|
||||
tablename: tablename,
|
||||
fisdata: fisdata
|
||||
}
|
||||
socket.emit("insertrow",value,(reply)=>{
|
||||
callback?.(reply?.result);
|
||||
})
|
||||
} else callback?.("no socket");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Data from database
|
||||
*
|
||||
* The request data is {
|
||||
* tablename: tablename,
|
||||
* filter: {
|
||||
* airlinecode: filter_airline,
|
||||
* flightnumber: filter_flightnumber,
|
||||
* DA: filter_da,
|
||||
* DI: filter_di
|
||||
* }
|
||||
*
|
||||
* The reply format is {
|
||||
* command: "getdata",
|
||||
* value: {
|
||||
* tablename: tablename,
|
||||
* filter: {
|
||||
* airlinecode: filter_airline,
|
||||
* flightnumber: filter_flightnumber,
|
||||
* DA: filter_da,
|
||||
* DI: filter_di
|
||||
* }
|
||||
* result: "success" | "failed" | "invalid data" | "no event handler"
|
||||
* data: [FisData]
|
||||
* }
|
||||
*
|
||||
* return of this function is object {
|
||||
* result: "success" | "failed" | "invalid data" | "no event handler",
|
||||
* data: [FisData]
|
||||
* }
|
||||
*
|
||||
* @param {Socket} socket socketio connection
|
||||
* @param {String} tablename tablename in format yyyy-MM-dd
|
||||
* @param {String|null} filter_airline use to filter airline code
|
||||
* @param {String|null} filter_flightnumber use to filter flight number
|
||||
* @param {String|null} filter_da use to filter Departure or Arrival
|
||||
* @param {String|null} filter_di use to filter Domestic or International
|
||||
* @param {Function} callback Callback function to handle result with result in string : "success" | "failed" | "invalid data" | "no event handler"
|
||||
*/
|
||||
function getdata(socket, tablename, filter_airline, filter_flightnumber, filter_da, filter_di, callback){
|
||||
if (socketvalid){
|
||||
let value = {
|
||||
tablename: tablename,
|
||||
filter: {
|
||||
airlinecode: filter_airline,
|
||||
flightnumber: filter_flightnumber,
|
||||
DA: filter_da,
|
||||
DI: filter_di
|
||||
}
|
||||
}
|
||||
socket.emit("getdata",value,(reply)=>{
|
||||
callback?.({result:reply?.result, data:reply?.data});
|
||||
})
|
||||
} else callback?.({result:"no socket", data:[]});
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Row in database
|
||||
*
|
||||
* The request data is {
|
||||
* tablename: tablename,
|
||||
* rowid: rowid,
|
||||
* newdata: fisdata
|
||||
* }
|
||||
*
|
||||
* The reply format is {
|
||||
* command: "editrow",
|
||||
* value: {
|
||||
* tablename: tablename,
|
||||
* rowid: rowid,
|
||||
* newdata: fisdata
|
||||
* }
|
||||
* result: "success" | "failed" | "invalid data" | "no event handler"
|
||||
* }
|
||||
* @param {Socket} socket Socketio connection
|
||||
* @param {String} tablename tablename in format yyyy-MM-dd
|
||||
* @param {number} rowid row index to edit
|
||||
* @param {FisData} newdata data to replace at row index
|
||||
* @param {Function} callback Callback function to handle result with result in string : "success" | "failed" | "invalid data" | "no event handler"
|
||||
* @returns status in string : "success" | "failed" | "invalid data" | "no event handler"
|
||||
*/
|
||||
function editrow(socket, tablename, rowid, newdata, callback){
|
||||
if (socketvalid){
|
||||
let value = {
|
||||
tablename: tablename,
|
||||
rowid: rowid,
|
||||
newdata: newdata
|
||||
}
|
||||
socket.emit("editrow",value,(reply)=>{
|
||||
callback?.(reply?.result);
|
||||
})
|
||||
} else callback?.("no socket");
|
||||
}
|
||||
|
||||
/**
|
||||
* Import data from xls file
|
||||
*
|
||||
* The request data is {
|
||||
* tablename: tablename,
|
||||
* filename: filename,
|
||||
* size: size,
|
||||
* filedata: ArrayBuffer
|
||||
* }
|
||||
*
|
||||
* size and filedata is read from filename by FileReader
|
||||
*
|
||||
* The reply format is {
|
||||
* command: "importfromxls",
|
||||
* tablename: tablename,
|
||||
* filename: filename,
|
||||
* size: size,
|
||||
* result: "success" | "failed" | "invalid data" | "no event handler"
|
||||
* }
|
||||
*
|
||||
* @param {Socket} socket
|
||||
* @param {String} tablename
|
||||
* @param {String} filename
|
||||
* @param {Blob} file
|
||||
* @param {Function} callback
|
||||
*/
|
||||
function importfromxls(socket, tablename, filename, file, callback){
|
||||
if (socketvalid){
|
||||
let value = {
|
||||
tablename: tablename,
|
||||
filename: filename,
|
||||
size: 0,
|
||||
filedata: null
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e)=>{
|
||||
console.log("File read complete, size: "+e.target.result.byteLength);
|
||||
value.size = e.target.result.byteLength;
|
||||
value.filedata = new Uint8Array(e.target.result) ;
|
||||
socket.emit("importxlsx",value,(reply)=>{
|
||||
console.log("Result from importfromxls: "+JSON.stringify(reply));
|
||||
callback?.(reply?.result);
|
||||
})
|
||||
}
|
||||
|
||||
reader.readAsArrayBuffer(file);
|
||||
|
||||
|
||||
} else callback?.("no socket");
|
||||
}
|
||||
|
||||
/**
|
||||
* Download data to xls file
|
||||
* <br/>
|
||||
* The request data is {<br/>
|
||||
* tablename: tablename <br/>
|
||||
* }<br/>
|
||||
*<br/>
|
||||
* The reply format is {<br/>
|
||||
* command: "exporttoxls",<br/>
|
||||
* tablename: tablename,<br/>
|
||||
* result: "success" | "failed" | "invalid data" | "no event handler"<br/>
|
||||
* size: size,<br/>
|
||||
* filename: exports/{filename in xlsx}<br/>
|
||||
* }<br/>
|
||||
* <br/>
|
||||
* Use the filename to download file from server
|
||||
*
|
||||
* callback value is {
|
||||
* result: "success" | "failed" | "invalid data" | "no event handler",
|
||||
* filename: filename
|
||||
* size: size
|
||||
* }
|
||||
* @param socket
|
||||
* @param tablename
|
||||
* @param callback
|
||||
*/
|
||||
function exporttoxls(socket, tablename, callback){
|
||||
if (socketvalid){
|
||||
let value = {
|
||||
tablename: tablename
|
||||
}
|
||||
let vv = {
|
||||
result: "",
|
||||
filename: "",
|
||||
size: 0
|
||||
}
|
||||
socket.emit("exportxlsx",value,(reply)=>{
|
||||
vv.result = reply?.result;
|
||||
vv.filename = reply?.filename;
|
||||
vv.size = reply?.size;
|
||||
callback?.(vv);
|
||||
})
|
||||
} else {
|
||||
vv.result = "no socket";
|
||||
callback?.(vv);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Login to server
|
||||
* The request data is {
|
||||
* username: username,
|
||||
* password: password
|
||||
* }
|
||||
*
|
||||
* The reply format is {
|
||||
* command: "login",
|
||||
* value: {
|
||||
* username: username,
|
||||
* password: password
|
||||
* }
|
||||
* result: "welcome ..." | "failed" | "invalid data" | "no event handler"
|
||||
* }
|
||||
* Use the result to check if login success
|
||||
* @param socket
|
||||
* @param username
|
||||
* @param password
|
||||
* @param callback
|
||||
*/
|
||||
function login(socket, username, password, callback){
|
||||
if (socketvalid){
|
||||
let value = {
|
||||
username: username,
|
||||
password: password
|
||||
}
|
||||
socket.emit("login",value,(reply)=>{
|
||||
callback?.(reply.result);
|
||||
})
|
||||
} else callback?.("no socket");
|
||||
}
|
||||
9
src/resources/public/css/all.min.css
vendored
Normal file
9
src/resources/public/css/all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
src/resources/public/css/bootstrap.min.css
vendored
Normal file
7
src/resources/public/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/resources/public/css/bootstrap.min.css.map
Normal file
1
src/resources/public/css/bootstrap.min.css.map
Normal file
File diff suppressed because one or more lines are too long
7
src/resources/public/css/bootstrap.rtl.min.css
vendored
Normal file
7
src/resources/public/css/bootstrap.rtl.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/resources/public/css/bootstrap.rtl.min.css.map
Normal file
1
src/resources/public/css/bootstrap.rtl.min.css.map
Normal file
File diff suppressed because one or more lines are too long
515
src/resources/public/css/dataTables.bootstrap5.css
Normal file
515
src/resources/public/css/dataTables.bootstrap5.css
Normal file
@@ -0,0 +1,515 @@
|
||||
@charset "UTF-8";
|
||||
:root {
|
||||
--dt-row-selected: 13, 110, 253;
|
||||
--dt-row-selected-text: 255, 255, 255;
|
||||
--dt-row-selected-link: 9, 10, 11;
|
||||
--dt-row-stripe: 0, 0, 0;
|
||||
--dt-row-hover: 0, 0, 0;
|
||||
--dt-column-ordering: 0, 0, 0;
|
||||
--dt-html-background: white;
|
||||
}
|
||||
:root.dark {
|
||||
--dt-html-background: rgb(33, 37, 41);
|
||||
}
|
||||
|
||||
table.dataTable td.dt-control {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
table.dataTable td.dt-control:before {
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
content: "";
|
||||
border-top: 5px solid transparent;
|
||||
border-left: 10px solid rgba(0, 0, 0, 0.5);
|
||||
border-bottom: 5px solid transparent;
|
||||
border-right: 0px solid transparent;
|
||||
}
|
||||
table.dataTable tr.dt-hasChild td.dt-control:before {
|
||||
border-top: 10px solid rgba(0, 0, 0, 0.5);
|
||||
border-left: 5px solid transparent;
|
||||
border-bottom: 0px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
}
|
||||
|
||||
html.dark table.dataTable td.dt-control:before,
|
||||
:root[data-bs-theme=dark] table.dataTable td.dt-control:before,
|
||||
:root[data-theme=dark] table.dataTable td.dt-control:before {
|
||||
border-left-color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
html.dark table.dataTable tr.dt-hasChild td.dt-control:before,
|
||||
:root[data-bs-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before,
|
||||
:root[data-theme=dark] table.dataTable tr.dt-hasChild td.dt-control:before {
|
||||
border-top-color: rgba(255, 255, 255, 0.5);
|
||||
border-left-color: transparent;
|
||||
}
|
||||
|
||||
div.dt-scroll {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.dt-scroll-body thead tr,
|
||||
div.dt-scroll-body tfoot tr {
|
||||
height: 0;
|
||||
}
|
||||
div.dt-scroll-body thead tr th, div.dt-scroll-body thead tr td,
|
||||
div.dt-scroll-body tfoot tr th,
|
||||
div.dt-scroll-body tfoot tr td {
|
||||
height: 0 !important;
|
||||
padding-top: 0px !important;
|
||||
padding-bottom: 0px !important;
|
||||
border-top-width: 0px !important;
|
||||
border-bottom-width: 0px !important;
|
||||
}
|
||||
div.dt-scroll-body thead tr th div.dt-scroll-sizing, div.dt-scroll-body thead tr td div.dt-scroll-sizing,
|
||||
div.dt-scroll-body tfoot tr th div.dt-scroll-sizing,
|
||||
div.dt-scroll-body tfoot tr td div.dt-scroll-sizing {
|
||||
height: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
table.dataTable thead > tr > th:active,
|
||||
table.dataTable thead > tr > td:active {
|
||||
outline: none;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before {
|
||||
position: absolute;
|
||||
display: block;
|
||||
bottom: 50%;
|
||||
content: "▲";
|
||||
content: "▲"/"";
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 50%;
|
||||
content: "▼";
|
||||
content: "▼"/"";
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc, table.dataTable thead > tr > th.dt-ordering-asc, table.dataTable thead > tr > th.dt-ordering-desc,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc {
|
||||
position: relative;
|
||||
padding-right: 30px;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 12px;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-orderable-desc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:after, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
|
||||
left: 0;
|
||||
opacity: 0.125;
|
||||
line-height: 9px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-asc, table.dataTable thead > tr > th.dt-orderable-desc,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc {
|
||||
cursor: pointer;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-orderable-asc:hover, table.dataTable thead > tr > th.dt-orderable-desc:hover,
|
||||
table.dataTable thead > tr > td.dt-orderable-asc:hover,
|
||||
table.dataTable thead > tr > td.dt-orderable-desc:hover {
|
||||
outline: 2px solid rgba(0, 0, 0, 0.05);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
table.dataTable thead > tr > th.dt-ordering-asc span.dt-column-order:before, table.dataTable thead > tr > th.dt-ordering-desc span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.dt-ordering-asc span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.dt-ordering-desc span.dt-column-order:after {
|
||||
opacity: 0.6;
|
||||
}
|
||||
table.dataTable thead > tr > th.sorting_desc_disabled span.dt-column-order:after, table.dataTable thead > tr > th.sorting_asc_disabled span.dt-column-order:before,
|
||||
table.dataTable thead > tr > td.sorting_desc_disabled span.dt-column-order:after,
|
||||
table.dataTable thead > tr > td.sorting_asc_disabled span.dt-column-order:before {
|
||||
display: none;
|
||||
}
|
||||
table.dataTable thead > tr > th:active,
|
||||
table.dataTable thead > tr > td:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
div.dt-scroll-body > table.dataTable > thead > tr > th,
|
||||
div.dt-scroll-body > table.dataTable > thead > tr > td {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:root.dark table.dataTable thead > tr > th.dt-orderable-asc:hover, :root.dark table.dataTable thead > tr > th.dt-orderable-desc:hover,
|
||||
:root.dark table.dataTable thead > tr > td.dt-orderable-asc:hover,
|
||||
:root.dark table.dataTable thead > tr > td.dt-orderable-desc:hover,
|
||||
:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-asc:hover,
|
||||
:root[data-bs-theme=dark] table.dataTable thead > tr > th.dt-orderable-desc:hover,
|
||||
:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-asc:hover,
|
||||
:root[data-bs-theme=dark] table.dataTable thead > tr > td.dt-orderable-desc:hover {
|
||||
outline: 2px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
div.dt-processing {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 200px;
|
||||
margin-left: -100px;
|
||||
margin-top: -22px;
|
||||
text-align: center;
|
||||
padding: 2px;
|
||||
z-index: 10;
|
||||
}
|
||||
div.dt-processing > div:last-child {
|
||||
position: relative;
|
||||
width: 80px;
|
||||
height: 15px;
|
||||
margin: 1em auto;
|
||||
}
|
||||
div.dt-processing > div:last-child > div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border-radius: 50%;
|
||||
background: rgb(13, 110, 253);
|
||||
background: rgb(var(--dt-row-selected));
|
||||
animation-timing-function: cubic-bezier(0, 1, 1, 0);
|
||||
}
|
||||
div.dt-processing > div:last-child > div:nth-child(1) {
|
||||
left: 8px;
|
||||
animation: datatables-loader-1 0.6s infinite;
|
||||
}
|
||||
div.dt-processing > div:last-child > div:nth-child(2) {
|
||||
left: 8px;
|
||||
animation: datatables-loader-2 0.6s infinite;
|
||||
}
|
||||
div.dt-processing > div:last-child > div:nth-child(3) {
|
||||
left: 32px;
|
||||
animation: datatables-loader-2 0.6s infinite;
|
||||
}
|
||||
div.dt-processing > div:last-child > div:nth-child(4) {
|
||||
left: 56px;
|
||||
animation: datatables-loader-3 0.6s infinite;
|
||||
}
|
||||
|
||||
@keyframes datatables-loader-1 {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
@keyframes datatables-loader-3 {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
@keyframes datatables-loader-2 {
|
||||
0% {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
100% {
|
||||
transform: translate(24px, 0);
|
||||
}
|
||||
}
|
||||
table.dataTable.nowrap th, table.dataTable.nowrap td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.dataTable th,
|
||||
table.dataTable td {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
table.dataTable th.dt-left,
|
||||
table.dataTable td.dt-left {
|
||||
text-align: left;
|
||||
}
|
||||
table.dataTable th.dt-center,
|
||||
table.dataTable td.dt-center {
|
||||
text-align: center;
|
||||
}
|
||||
table.dataTable th.dt-right,
|
||||
table.dataTable td.dt-right {
|
||||
text-align: right;
|
||||
}
|
||||
table.dataTable th.dt-justify,
|
||||
table.dataTable td.dt-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
table.dataTable th.dt-nowrap,
|
||||
table.dataTable td.dt-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.dataTable th.dt-empty,
|
||||
table.dataTable td.dt-empty {
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
}
|
||||
table.dataTable th.dt-type-numeric, table.dataTable th.dt-type-date,
|
||||
table.dataTable td.dt-type-numeric,
|
||||
table.dataTable td.dt-type-date {
|
||||
text-align: right;
|
||||
}
|
||||
table.dataTable thead th,
|
||||
table.dataTable thead td,
|
||||
table.dataTable tfoot th,
|
||||
table.dataTable tfoot td {
|
||||
text-align: left;
|
||||
}
|
||||
table.dataTable thead th.dt-head-left,
|
||||
table.dataTable thead td.dt-head-left,
|
||||
table.dataTable tfoot th.dt-head-left,
|
||||
table.dataTable tfoot td.dt-head-left {
|
||||
text-align: left;
|
||||
}
|
||||
table.dataTable thead th.dt-head-center,
|
||||
table.dataTable thead td.dt-head-center,
|
||||
table.dataTable tfoot th.dt-head-center,
|
||||
table.dataTable tfoot td.dt-head-center {
|
||||
text-align: center;
|
||||
}
|
||||
table.dataTable thead th.dt-head-right,
|
||||
table.dataTable thead td.dt-head-right,
|
||||
table.dataTable tfoot th.dt-head-right,
|
||||
table.dataTable tfoot td.dt-head-right {
|
||||
text-align: right;
|
||||
}
|
||||
table.dataTable thead th.dt-head-justify,
|
||||
table.dataTable thead td.dt-head-justify,
|
||||
table.dataTable tfoot th.dt-head-justify,
|
||||
table.dataTable tfoot td.dt-head-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
table.dataTable thead th.dt-head-nowrap,
|
||||
table.dataTable thead td.dt-head-nowrap,
|
||||
table.dataTable tfoot th.dt-head-nowrap,
|
||||
table.dataTable tfoot td.dt-head-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
table.dataTable tbody th.dt-body-left,
|
||||
table.dataTable tbody td.dt-body-left {
|
||||
text-align: left;
|
||||
}
|
||||
table.dataTable tbody th.dt-body-center,
|
||||
table.dataTable tbody td.dt-body-center {
|
||||
text-align: center;
|
||||
}
|
||||
table.dataTable tbody th.dt-body-right,
|
||||
table.dataTable tbody td.dt-body-right {
|
||||
text-align: right;
|
||||
}
|
||||
table.dataTable tbody th.dt-body-justify,
|
||||
table.dataTable tbody td.dt-body-justify {
|
||||
text-align: justify;
|
||||
}
|
||||
table.dataTable tbody th.dt-body-nowrap,
|
||||
table.dataTable tbody td.dt-body-nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/*! Bootstrap 5 integration for DataTables
|
||||
*
|
||||
* ©2020 SpryMedia Ltd, all rights reserved.
|
||||
* License: MIT datatables.net/license/mit
|
||||
*/
|
||||
table.table.dataTable {
|
||||
clear: both;
|
||||
margin-bottom: 0;
|
||||
max-width: none;
|
||||
border-spacing: 0;
|
||||
}
|
||||
table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
|
||||
box-shadow: none;
|
||||
}
|
||||
table.table.dataTable > :not(caption) > * > * {
|
||||
background-color: var(--bs-table-bg);
|
||||
}
|
||||
table.table.dataTable > tbody > tr {
|
||||
background-color: transparent;
|
||||
}
|
||||
table.table.dataTable > tbody > tr.selected > * {
|
||||
box-shadow: inset 0 0 0 9999px rgb(13, 110, 253);
|
||||
box-shadow: inset 0 0 0 9999px rgb(var(--dt-row-selected));
|
||||
color: rgb(255, 255, 255);
|
||||
color: rgb(var(--dt-row-selected-text));
|
||||
}
|
||||
table.table.dataTable > tbody > tr.selected a {
|
||||
color: rgb(9, 10, 11);
|
||||
color: rgb(var(--dt-row-selected-link));
|
||||
}
|
||||
table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1) > * {
|
||||
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-stripe), 0.05);
|
||||
}
|
||||
table.table.dataTable.table-striped > tbody > tr:nth-of-type(2n+1).selected > * {
|
||||
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.95);
|
||||
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.95);
|
||||
}
|
||||
table.table.dataTable.table-hover > tbody > tr:hover > * {
|
||||
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-hover), 0.075);
|
||||
}
|
||||
table.table.dataTable.table-hover > tbody > tr.selected:hover > * {
|
||||
box-shadow: inset 0 0 0 9999px rgba(13, 110, 253, 0.975);
|
||||
box-shadow: inset 0 0 0 9999px rgba(var(--dt-row-selected), 0.975);
|
||||
}
|
||||
|
||||
div.dt-container div.dt-layout-start > *:not(:last-child) {
|
||||
margin-right: 1em;
|
||||
}
|
||||
div.dt-container div.dt-layout-end > *:not(:first-child) {
|
||||
margin-left: 1em;
|
||||
}
|
||||
div.dt-container div.dt-layout-full {
|
||||
width: 100%;
|
||||
}
|
||||
div.dt-container div.dt-layout-full > *:only-child {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
div.dt-container div.dt-layout-table > div {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
div.dt-container div.dt-layout-start > *:not(:last-child) {
|
||||
margin-right: 0;
|
||||
}
|
||||
div.dt-container div.dt-layout-end > *:not(:first-child) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
div.dt-container div.dt-length label {
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
}
|
||||
div.dt-container div.dt-length select {
|
||||
width: auto;
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
div.dt-container div.dt-search {
|
||||
text-align: right;
|
||||
}
|
||||
div.dt-container div.dt-search label {
|
||||
font-weight: normal;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
div.dt-container div.dt-search input {
|
||||
margin-left: 0.5em;
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
div.dt-container div.dt-paging {
|
||||
margin: 0;
|
||||
}
|
||||
div.dt-container div.dt-paging ul.pagination {
|
||||
margin: 2px 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
div.dt-container div.dt-row {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.dt-scroll-head table.dataTable {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
div.dt-scroll-body {
|
||||
border-bottom-color: var(--bs-border-color);
|
||||
border-bottom-width: var(--bs-border-width);
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
div.dt-scroll-body > table {
|
||||
border-top: none;
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
div.dt-scroll-body > table > tbody > tr:first-child {
|
||||
border-top-width: 0;
|
||||
}
|
||||
div.dt-scroll-body > table > thead > tr {
|
||||
border-width: 0 !important;
|
||||
}
|
||||
div.dt-scroll-body > table > tbody > tr:last-child > * {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
div.dt-scroll-foot > .dt-scroll-footInner {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
div.dt-scroll-foot > .dt-scroll-footInner > table {
|
||||
margin-top: 0 !important;
|
||||
border-top: none;
|
||||
}
|
||||
div.dt-scroll-foot > .dt-scroll-footInner > table > tfoot > tr:first-child {
|
||||
border-top-width: 0 !important;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 767px) {
|
||||
div.dt-container div.dt-length,
|
||||
div.dt-container div.dt-search,
|
||||
div.dt-container div.dt-info,
|
||||
div.dt-container div.dt-paging {
|
||||
text-align: center;
|
||||
}
|
||||
div.dt-container .row {
|
||||
--bs-gutter-y: 0.5rem;
|
||||
}
|
||||
div.dt-container div.dt-paging ul.pagination {
|
||||
justify-content: center !important;
|
||||
}
|
||||
}
|
||||
table.dataTable.table-sm > thead > tr th.dt-orderable-asc, table.dataTable.table-sm > thead > tr th.dt-orderable-desc, table.dataTable.table-sm > thead > tr th.dt-ordering-asc, table.dataTable.table-sm > thead > tr th.dt-ordering-desc,
|
||||
table.dataTable.table-sm > thead > tr td.dt-orderable-asc,
|
||||
table.dataTable.table-sm > thead > tr td.dt-orderable-desc,
|
||||
table.dataTable.table-sm > thead > tr td.dt-ordering-asc,
|
||||
table.dataTable.table-sm > thead > tr td.dt-ordering-desc {
|
||||
padding-right: 20px;
|
||||
}
|
||||
table.dataTable.table-sm > thead > tr th.dt-orderable-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-orderable-desc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-asc span.dt-column-order, table.dataTable.table-sm > thead > tr th.dt-ordering-desc span.dt-column-order,
|
||||
table.dataTable.table-sm > thead > tr td.dt-orderable-asc span.dt-column-order,
|
||||
table.dataTable.table-sm > thead > tr td.dt-orderable-desc span.dt-column-order,
|
||||
table.dataTable.table-sm > thead > tr td.dt-ordering-asc span.dt-column-order,
|
||||
table.dataTable.table-sm > thead > tr td.dt-ordering-desc span.dt-column-order {
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
div.dt-scroll-head table.table-bordered {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
div.table-responsive > div.dt-container > div.row {
|
||||
margin: 0;
|
||||
}
|
||||
div.table-responsive > div.dt-container > div.row > div[class^=col-]:first-child {
|
||||
padding-left: 0;
|
||||
}
|
||||
div.table-responsive > div.dt-container > div.row > div[class^=col-]:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
:root[data-bs-theme=dark] {
|
||||
--dt-row-hover: 255, 255, 255;
|
||||
--dt-row-stripe: 255, 255, 255;
|
||||
--dt-column-ordering: 255, 255, 255;
|
||||
}
|
||||
4
src/resources/public/css/font-awesome.min.css
vendored
Normal file
4
src/resources/public/css/font-awesome.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
27987
src/resources/public/css/fontawesome/all.css
vendored
Normal file
27987
src/resources/public/css/fontawesome/all.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
12
src/resources/public/css/fontawesome/all.min.css
vendored
Normal file
12
src/resources/public/css/fontawesome/all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/resources/public/css/jquery.dataTables.min.css
vendored
Normal file
1
src/resources/public/css/jquery.dataTables.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
src/resources/public/css/webfonts/fa-regular-400.ttf
Normal file
BIN
src/resources/public/css/webfonts/fa-regular-400.ttf
Normal file
Binary file not shown.
BIN
src/resources/public/css/webfonts/fa-regular-400.woff2
Normal file
BIN
src/resources/public/css/webfonts/fa-regular-400.woff2
Normal file
Binary file not shown.
BIN
src/resources/public/css/webfonts/fa-solid-900.ttf
Normal file
BIN
src/resources/public/css/webfonts/fa-solid-900.ttf
Normal file
Binary file not shown.
BIN
src/resources/public/css/webfonts/fa-solid-900.woff2
Normal file
BIN
src/resources/public/css/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
154
src/resources/public/editFlight.html
Normal file
154
src/resources/public/editFlight.html
Normal file
@@ -0,0 +1,154 @@
|
||||
<div class="container">
|
||||
<div class="card-borderless">
|
||||
<div class="card-header heading-black">
|
||||
<h4 class="text-center fw-bold"> Edit Data Flight</h4>
|
||||
</div>
|
||||
<div class="card-body card-flight">
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col-6">
|
||||
<label class="fw-bold mb-2">Arrival/Departure</label>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6 mb-2">
|
||||
<input type="radio" id="edit_AD_A" name="AD" value="A" disabled >
|
||||
<label for="edit_AD_A">Arrival</label>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
|
||||
<input type="radio" id="edit_AD_D" name="AD" value="D" disabled >
|
||||
<label for="edit_AD_D">Departure</label><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="fw-bold mb-2">Domestic/International</label>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6 mb-2">
|
||||
<input type="radio" id="edit_DI_D" name="DI" value="D" disabled >
|
||||
<label for="edit_DI_D">Domestic</label>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
|
||||
<input type="radio" id="edit_DI_I" name="DI" value="I" disabled >
|
||||
<label for="edit_DI_I">International</label><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col">
|
||||
<label for="edit_Alcode" class="fw-bold">Airline Code</label><br>
|
||||
<input id="edit_Alcode" class="form-control" placeholder="2 letters IATA" pattern="[A-Z]{2}" minlength="2" maxlength="2" required disabled/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="edit_FlightNumber" class="fw-bold">Flight Number</label><br>
|
||||
<input id="edit_FlightNumber" class="form-control" placeholder="1 - 4 digits" pattern="\d{1,4}" minlength="1" maxlength="4" required disabled/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col">
|
||||
<label for="edit_Origin" class="fw-bold">Origin</label><br>
|
||||
<input id="edit_Origin" class="form-control" placeholder="3 letters IATA" pattern="[A-Z]{3}" minlength="3" maxlength="3" disabled/>
|
||||
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="edit_Destination" class="fw-bold">Destination</label><br>
|
||||
<input id="edit_Destination" class="form-control" placeholder="3 letters IATA, put comma for multi destinations" pattern="([A-Z]{3})(,[A-Z]{3})*" minlength="3" disabled/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col">
|
||||
<label for="edit_Gate" class="fw-bold">Gate</label><br>
|
||||
<input id="edit_Gate" class="form-control" placeholder="Gate code" pattern="[A-Z0-9]{1,3}" minlength="1" value="1" required/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="edit_Terminal" class="fw-bold">Terminal</label><br>
|
||||
<input id="edit_Terminal" class="form-control" placeholder="Terminal code" pattern="[A-Z0-9]{1,3}" minlength="1" value="1" required/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="edit_BaggageClaim" class="fw-bold">Baggage Claim</label><br>
|
||||
<input id="edit_BaggageClaim" class="form-control" placeholder="Baggage Conveyor code" pattern="[A-Z0-9]{1,3}" minlength="1" value="1" required/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="edit_CheckIn" class="fw-bold">Check In Counter</label><br>
|
||||
<input id="edit_CheckIn" class="form-control" placeholder="Checkin Counter" pattern="[0-9]{1,3}" minlength="1" value="1" required/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4 mb-3">
|
||||
<label for="edit_scheduled" class="fw-bold">Scheduled Time</label><br>
|
||||
<input id="edit_scheduled" type="datetime-local" step="1" class="form-control class100" disabled>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4 mb-3">
|
||||
<label for="edit_estimated" class="fw-bold">Estimated Time</label><br>
|
||||
<input id="edit_estimated" type="datetime-local" step="1" class="form-control class100">
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4" >
|
||||
<label for="edit_actual" class="fw-bold">Actual Time</label><br>
|
||||
<input id="edit_actual" type="datetime-local" step="1" class="form-control class100">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4 mb-3">
|
||||
<label for="edit_gateScheduled" class="fw-bold">Gate Scheduled Time</label><br>
|
||||
<input id="edit_gateScheduled" type="datetime-local" step="1" class="form-control class100" disabled>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4 mb-3">
|
||||
<label for="edit_gateEstimated" class="fw-bold">Gate Estimated Time</label><br>
|
||||
<input id="edit_gateEstimated" type="datetime-local" step="1" class="form-control class100">
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4">
|
||||
<label for="edit_gateActual" class="fw-bold">Gate Actual Time</label><br>
|
||||
<input id="edit_gateActual" type="datetime-local" step="1" class="form-control class100">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<!-- <div class="col">-->
|
||||
<!-- <label class="fw-bold">Weather</label><br>-->
|
||||
<!-- <input id="edit_weather" class="form-control" value="Sunny"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="col">-->
|
||||
<!-- <label class="fw-bold">Temperature</label><br>-->
|
||||
<!-- <input id="edit_temperature" class="form-control" value="19.5"/>-->
|
||||
<!-- </div>-->
|
||||
<div class="col">
|
||||
<label for="edit_remark" class="fw-bold">Remark</label><br>
|
||||
<select id="edit_remark" class="pad-option class100 mb-2">
|
||||
<option value="BOARDING">BOARDING</option>
|
||||
<option value="CANCELLED">CANCELLED</option>
|
||||
<option value="CHARTERED FLIGHT">CHARTERED FLIGHT</option>
|
||||
<option value="CHECK IN CLOSE">CHECK IN CLOSE</option>
|
||||
<option value="DELAYED">DELAYED</option>
|
||||
<option value="DEPARTED">DEPARTED</option>
|
||||
<option value="ESTIMATE">ESTIMATE</option>
|
||||
<option value="FIRST CALL">FIRST CALL</option>
|
||||
<option value="GATE CLOSE">GATE CLOSE</option>
|
||||
<option value="GATE OPEN">GATE OPEN</option>
|
||||
<option value="LANDED">LANDED</option>
|
||||
<option value="LAST CALL">LAST CALL</option>
|
||||
<option value="SCHEDULLED">SCHEDULLED</option>
|
||||
<option value="SECOND CALL">SECOND CALL</option>
|
||||
<option value="TO WAITING ROOM">TO WAITING ROOM</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col"></div>
|
||||
<div class="col">
|
||||
<button class="btn btn-secondary class100" id="cancelbutton">Cancel</button>
|
||||
|
||||
</div>
|
||||
<div class="col">
|
||||
<button class="btn btn-primary class100" id="savebutton">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
196
src/resources/public/fisTable.html
Normal file
196
src/resources/public/fisTable.html
Normal file
@@ -0,0 +1,196 @@
|
||||
<div class="container-fluid">
|
||||
<br>
|
||||
<!-- <div class="row">-->
|
||||
<!-- <div class="col-4 col-sm-12 col-md-3 col-lg-2 col-xl-2 mb-2">-->
|
||||
<!-- <select id="tableexisting" class="form-control class100">-->
|
||||
<!-- <option>Table 1 </option>-->
|
||||
<!-- <option>Table 2 </option>-->
|
||||
<!-- </select>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<!-- <div class="col-4 col-sm-12 col-md-3 col-lg-2 col-xl-2 mb-2">-->
|
||||
<!-- <button id="getdata" class="btn btn-secondary class100">Get FIS Data-->
|
||||
<!-- <i class="fa-regular fa-calendar-arrow-down"></i>-->
|
||||
<!-- </button>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="col-4 col-sm-12 col-md-3 col-lg-2 col-xl-2 mb-2">-->
|
||||
<!-- <button id="droptable" class="btn btn-danger class100">Drop FIS Table-->
|
||||
<!-- <i class="fa-solid fa-trash-can"></i>-->
|
||||
<!-- </button>-->
|
||||
<!-- </div>-->
|
||||
|
||||
|
||||
<!-- <div class="col-12 col-sm-12 col-md-3 col-lg-2 col-xl-2 mb-2 mb-2"></div>-->
|
||||
<!-- <div class="col-6 col-sm-12 col-md-3 col-lg-2 col-xl-2 mb-2">-->
|
||||
<!-- <!– tablename to create, edit, delete, get data –>-->
|
||||
<!-- <input type="date" id="tablename" class="form-control class100"></input>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="col-6 col-sm-12 col-md-3 col-lg-2 col-xl-2 mb-2">-->
|
||||
<!-- <!– untuk keperluan create table dengan nama tablename –>-->
|
||||
<!-- <button id="createtable" class="btn btn-primary class100">-->
|
||||
<!-- Create FIS Table <i class="fa-solid fa-folder-plus"></i></button>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
<!-- <br>-->
|
||||
<!-- <br>-->
|
||||
|
||||
<!-- <!– Table to display the fis data –>-->
|
||||
|
||||
<!-- <div class="row">-->
|
||||
<!-- <div class="col">-->
|
||||
<!-- <h4 id="tabletitle">All Flight</h4>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="col">-->
|
||||
<!-- <!– untuk keperluan insert data ke tablename –>-->
|
||||
<!-- <a id="insertdata" class="btn btn-primary btn-insert btn-sm "> Insert Data-->
|
||||
<!-- <i class="fa-solid fa-plus"></i>-->
|
||||
<!-- </a>-->
|
||||
<!-- </div>-->
|
||||
<!-- </div>-->
|
||||
|
||||
<div class="row padding-head">
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-5 pad-top">
|
||||
<div class="row input-group form-control form-pad-right">
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4">
|
||||
<label for="tableexisting" hidden="hidden"></label>
|
||||
<select id="tableexisting" class= "pad-option class100">
|
||||
<option>Table 1 </option>
|
||||
<option>Table 2 </option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4">
|
||||
<button id="getdata" class="btn btn-secondary class100"><i class="fa-regular fa-calendar-arrow-down "></i>   Get Data</button>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4">
|
||||
<button id="droptable" class="btn btn-danger btn-md class100" onclick="DropTable()"><i class="fa-solid fa-trash-can"></i>   Drop Table </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-1 mb-2 btn-pad-top">
|
||||
<button type="button" class="btn btn-success btn-md dropdown-toggle hide-toggle class100" data-bs-toggle="dropdown" aria-expanded="false" aria-haspopup="true"> Action </button>
|
||||
<ul class="dropdown-menu">
|
||||
<li class="opt">
|
||||
<div class="row input-group form-control form-pad-right">
|
||||
<div class="col">
|
||||
<label for="tablename" hidden="hidden"></label>
|
||||
<input type="date" id="tablename" class="form-control class100"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button id="createtable" class="btn btn-primary class100">
|
||||
<i class="fa-solid fa-folder-plus"></i>   Create Table </button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="opt">
|
||||
<div class="row input-group form-control form-pad-right">
|
||||
<div class="col-7 col-sm-7 col-md-7 col-lg-8 col-xl-7">
|
||||
<input id="filename" type="file" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" class="class100">
|
||||
</div>
|
||||
<div class="col-5 col-sm-5 col-md-5 col-lg-4 col-xl-5">
|
||||
<button id="importfromexcel" class="btn btn-secondary btn-insert btn-sm "><i class="fa-solid fa-file-arrow-down"></i>   Import</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li class="opt">
|
||||
<div class="row input-group form-control form-pad-right">
|
||||
<button id="exporttoexcel" class="btn btn-warning btn-insert btn-sm class100 text-white pad-top"><i class="fa-solid fa-file-arrow-up"></i>   Export</button>
|
||||
</div>
|
||||
</li>
|
||||
<li class="opt">
|
||||
<div class="row input-group form-control form-pad-right pad-left-right">
|
||||
<button id="insertdata" class="btn btn-primary btn-insert btn-sm"> <i class="fa-solid fa-plus"></i>   Insert Data </button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-lg-6 col-xl-5"></div>
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-6 col-xl-1 btn-pad-top btn-pad-top mb-2">
|
||||
<button id="logoutbutton" class="btn btn-dark btn-md class100"> Logout
|
||||
<i class="fa-duotone fa-solid fa-right-from-bracket"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-3 col-lg-3 col-xl-6 pad-top">
|
||||
<h4 id="tabletitle">Flight Schedule</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table id="fistable" class="table table-striped table-responsive table-light pad-top">
|
||||
<thead class="table-dark">
|
||||
<tr>
|
||||
<th>Index</th>
|
||||
<th>Airline</th>
|
||||
<th>Flight Number</th>
|
||||
<th>Dep/Arr</th>
|
||||
<th>Dom/Int</th>
|
||||
<th>Origin</th>
|
||||
<th>Destination</th>
|
||||
<th>Gate</th>
|
||||
<th>Terminal</th>
|
||||
<th>Estimated Time</th>
|
||||
<th>Remark</th>
|
||||
<th class="text-center">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="fistablebody">
|
||||
<tr>
|
||||
<td>
|
||||
<label id="ID">1</label>
|
||||
</td>
|
||||
<td>
|
||||
<label id="AirlineCode"> AA</label>
|
||||
</td>
|
||||
<td>
|
||||
<label id="FlightNumber">1234</label>
|
||||
</td>
|
||||
<td>
|
||||
<label id="labelDA">D</label>
|
||||
</td>
|
||||
<td>
|
||||
<label id="labelDI">D</label>
|
||||
</td>
|
||||
<td>
|
||||
<label id="Origin"> CGK </label>
|
||||
</td>
|
||||
<td>
|
||||
<label id="Destination"> SUB,DPS </label>
|
||||
</td>
|
||||
<td>
|
||||
<label id="Gate"> A1</label>
|
||||
</td>
|
||||
<td>
|
||||
<label id="Terminal">1 </label>
|
||||
</td>
|
||||
<td>
|
||||
<label id="Estimated"> 2024-08-01 12:00:00 </label>
|
||||
</td>
|
||||
<td>
|
||||
<label id="Remark" >On Time</label>
|
||||
</td>
|
||||
<td>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<!-- untuk keperluan update data ke tablename -->
|
||||
<a href="editFlight.html">
|
||||
<button id="editdata" class="btn btn-sm btn-primary">
|
||||
<span class="fa-solid fa-pencil"></span>
|
||||
</button>
|
||||
|
||||
</a>
|
||||
</div>
|
||||
<div class="col">
|
||||
<!-- untuk keperluan delete row dari tablename -->
|
||||
<button id="deleterow" class="btn btn-sm btn-danger">
|
||||
<span class="fa-solid fa fa-trash"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
BIN
src/resources/public/images/Airport33.png
Normal file
BIN
src/resources/public/images/Airport33.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 664 KiB |
BIN
src/resources/public/images/background-login.jpg
Normal file
BIN
src/resources/public/images/background-login.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
53
src/resources/public/index.html
Normal file
53
src/resources/public/index.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Mini FIS Dashboard version 1.0.8</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="socket.io.min.js"></script> <!-- Socket.io version 4.7.5 -->
|
||||
<script src="citydetail.js"></script> <!-- citydetail script -->
|
||||
<script src="FisData.js"></script> <!-- FisData script -->
|
||||
<script src="communication.js"></script> <!-- communication script -->
|
||||
<script src="script.js"></script> <!-- application script -->
|
||||
|
||||
<link rel="stylesheet" href="./css/bootstrap.min.css">
|
||||
<script src="./js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<script src="js/jquery-3.7.1.js"></script>
|
||||
<link rel="stylesheet" href="css/jquery.dataTables.min.css">
|
||||
<script src="js/jquery.dataTables.min.js"></script>
|
||||
|
||||
<!-- Font Awesome 6.5 pro-->
|
||||
<link rel="stylesheet" href="css/fontawesome/all.css">
|
||||
<link rel="stylesheet" href="css/fontawesome/all.min.css">
|
||||
|
||||
<script src="js/fontawesome/all.js"></script>
|
||||
<script src="js/fontawesome/all.min.js"></script>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-dark bg-dark" aria-label="First navbar example">
|
||||
<div class="container-fluid">
|
||||
<a class=" font-dashboard" href="#">Flight Information System</a>
|
||||
<div class="row row-status">
|
||||
<div class="col">
|
||||
<p id="text_status" class="text-status">Disconnected</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div id="status_connection" class="btn-disconnected"> </div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Status Connected pakai class = "btn-connected" & ganti text_status
|
||||
Status Disconnected pakai class = "btn-disconnected"-->
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
<div class="container-fluid" id="snackbar"/>
|
||||
</div>
|
||||
|
||||
<div id="appcontent"/>
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
153
src/resources/public/inputFlight.html
Normal file
153
src/resources/public/inputFlight.html
Normal file
@@ -0,0 +1,153 @@
|
||||
<div class="container">
|
||||
<div class="card-borderless">
|
||||
<div class="card-header heading-black">
|
||||
<h4> Input New Data Flight</h4>
|
||||
</div>
|
||||
<div class="card-body card-flight">
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col-6">
|
||||
<label class="fw-bold mb-2">Arrival/Departure</label>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6 mb-2">
|
||||
<input type="radio" id="input_AD_A" name="AD" value="A">
|
||||
<label for="input_AD_A">Arrival</label>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
|
||||
<input type="radio" id="input_AD_D" name="AD" value="D">
|
||||
<label for="input_AD_D">Departure</label><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<label class="fw-bold mb-2">Domestic/International</label>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6 mb-2">
|
||||
<input type="radio" id="input_DI_D" name="DI" value="D">
|
||||
<label for="input_DI_D">Domestic</label>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-6 col-lg-6 col-xl-6">
|
||||
<input type="radio" id="input_DI_I" name="DI" value="I">
|
||||
<label for="input_DI_I">International</label><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col">
|
||||
<label for="input_Alcode" class="fw-bold">Airline Code</label><br>
|
||||
<input id="input_Alcode" class="form-control" placeholder="2 letters IATA" pattern="[A-Z]{2}" minlength="2" maxlength="2" required/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="input_FlightNumber" class="fw-bold">Flight Number</label><br>
|
||||
<input id="input_FlightNumber" class="form-control" placeholder="1 - 4 digits" pattern="\d{1,4}" minlength="1" maxlength="4" required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col">
|
||||
<label for="input_Origin" class="fw-bold">Origin</label><br>
|
||||
<input id="input_Origin" class="form-control" placeholder="3 letters IATA" pattern="[A-Z]{3}" minlength="3" maxlength="3" required/>
|
||||
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="input_Destination" class="fw-bold">Destination</label><br>
|
||||
<input id="input_Destination" placeholder="3 letters IATA, put comma for multi destinations " class="form-control" pattern="([A-Z]{3})(,[A-Z]{3})*" minlength="3" required/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col">
|
||||
<label for="input_Gate" class="fw-bold">Gate</label><br>
|
||||
<input id="input_Gate" class="form-control" placeholder="Gate code" pattern="[A-Z0-9]{1,3}" minlength="1" value="1" required />
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="input_Terminal" class="fw-bold">Terminal</label><br>
|
||||
<input id="input_Terminal" class="form-control" placeholder="Terminal code" pattern="[A-Z0-9]{1,3}" minlength="1" value="1" required />
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="input_BaggageClaim" class="fw-bold">Baggage Claim</label><br>
|
||||
<input id="input_BaggageClaim" class="form-control" placeholder="Baggage Conveyor code" pattern="[A-Z0-9]{1,3}" minlength="1" value="1" required />
|
||||
</div>
|
||||
<div class="col">
|
||||
<label for="input_CheckIn" class="fw-bold">Check In Counter</label><br>
|
||||
<input id="input_CheckIn" class="form-control" value="1" placeholder="Checkin Counter" pattern="[0-9]{1,3}" minlength="1" required/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4 mb-3">
|
||||
<label for="input_scheduled" class="fw-bold">Scheduled Time</label><br>
|
||||
<input id="input_scheduled" type="datetime-local" step="1" class="form-control class100">
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4 mb-3">
|
||||
<label for="input_estimated" class="fw-bold">Estimated Time</label><br>
|
||||
<input id="input_estimated" type="datetime-local" step="1" class="form-control class100" disabled>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4">
|
||||
<label for="input_actual" class="fw-bold">Actual Time</label><br>
|
||||
<input id="input_actual" type="datetime-local" step="1" class="form-control class100" disabled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4 mb-3">
|
||||
<label for="input_gateScheduled" class="fw-bold">Gate Scheduled Time</label><br>
|
||||
<input id="input_gateScheduled" type="datetime-local" step="1" class="form-control class100" >
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4 mb-3">
|
||||
<label for="input_gateEstimated" class="fw-bold">Gate Estimated Time</label><br>
|
||||
<input id="input_gateEstimated" type="datetime-local" step="1" class="form-control class100" disabled>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12 col-md-4 col-lg-4 col-xl-4">
|
||||
<label for="input_gateActual" class="fw-bold">Gate Actual Time</label><br>
|
||||
<input id="input_gateActual" type="datetime-local" step="1" class="form-control class100" disabled>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<!-- <div class="col">-->
|
||||
<!-- <label class="fw-bold">Weather</label><br>-->
|
||||
<!-- <input id="input_weather" class="form-control" value="Sunny"/>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="col">-->
|
||||
<!-- <label class="fw-bold">Temperature</label><br>-->
|
||||
<!-- <input id="input_temperature" class="form-control" value="19.5"/>-->
|
||||
<!-- </div>-->
|
||||
<div class="col-12 col-sm-12 col-md-12 col-lg-12 col-xl-12 mb-2">
|
||||
<label for="input_remark" class="fw-bold">Remark</label><br>
|
||||
<select id="input_remark" class="pad-option class100">
|
||||
<option value="BOARDING">BOARDING</option>
|
||||
<option value="CANCELLED">CANCELLED</option>
|
||||
<option value="CHARTERED FLIGHT">CHARTERED FLIGHT</option>
|
||||
<option value="CHECK IN CLOSE">CHECK IN CLOSE</option>
|
||||
<option value="DELAYED">DELAYED</option>
|
||||
<option value="DEPARTED">DEPARTED</option>
|
||||
<option value="ESTIMATE">ESTIMATE</option>
|
||||
<option value="FIRST CALL">FIRST CALL</option>
|
||||
<option value="GATE CLOSE">GATE CLOSE</option>
|
||||
<option value="GATE OPEN">GATE OPEN</option>
|
||||
<option value="LANDED">LANDED</option>
|
||||
<option value="LAST CALL">LAST CALL</option>
|
||||
<option value="SCHEDULLED">SCHEDULLED</option>
|
||||
<option value="SECOND CALL">SECOND CALL</option>
|
||||
<option value="TO WAITING ROOM">TO WAITING ROOM</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row position-relative mb-3">
|
||||
<div class="col"></div>
|
||||
<div class="col">
|
||||
<button id="cancelbutton" class="btn btn-secondary class100">Cancel</button>
|
||||
</div>
|
||||
<div class="col">
|
||||
<button id="savebutton" class="btn btn-primary class100">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
7
src/resources/public/js/bootstrap.bundle.min.js
vendored
Normal file
7
src/resources/public/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/resources/public/js/bootstrap.bundle.min.js.map
Normal file
1
src/resources/public/js/bootstrap.bundle.min.js.map
Normal file
File diff suppressed because one or more lines are too long
21834
src/resources/public/js/fontawesome/all.js
Normal file
21834
src/resources/public/js/fontawesome/all.js
Normal file
File diff suppressed because one or more lines are too long
6
src/resources/public/js/fontawesome/all.min.js
vendored
Normal file
6
src/resources/public/js/fontawesome/all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10716
src/resources/public/js/jquery-3.7.1.js
vendored
Normal file
10716
src/resources/public/js/jquery-3.7.1.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
4
src/resources/public/js/jquery.dataTables.min.js
vendored
Normal file
4
src/resources/public/js/jquery.dataTables.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
39
src/resources/public/login.html
Normal file
39
src/resources/public/login.html
Normal file
@@ -0,0 +1,39 @@
|
||||
<div class="bg-img">
|
||||
<div class="content">
|
||||
<header>Login</header>
|
||||
<div class="field space input-rad">
|
||||
<span class="fa fa-user padding-icon"></span>
|
||||
<label for="login_username" hidden="hidden"></label>
|
||||
<input class="input-rad" id='login_username' type='text' name='username' placeholder='Username' required />
|
||||
</div>
|
||||
<div class="field space input-rad">
|
||||
<span class="fa-solid fa-lock-keyhole padding-icon"></span>
|
||||
<label for="login_password" hidden="hidden"></label>
|
||||
<input id='login_password' type='password' placeholder='Password' name='password' required/>
|
||||
<span id='buttonShowPasswordLogin' class="fa-solid fa-eye padding-icon" onclick="showPasswordLogin()"></span>
|
||||
</div>
|
||||
<div class="class100 space">
|
||||
<button id="loginbutton" class="btn btn-primary btn-lg w-100"> Login</button>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function showPasswordLogin() {
|
||||
console.log("button clicked");
|
||||
const passwordInput = document.getElementById('login_password');
|
||||
const show = passwordInput.type;
|
||||
|
||||
if (show === 'password') {
|
||||
passwordInput.type = 'text';
|
||||
document.getElementById("buttonShowPasswordLogin").classList.remove('fa-eye');
|
||||
document.getElementById("buttonShowPasswordLogin").classList.add('fa-eye-slash');
|
||||
} else {
|
||||
passwordInput.type = 'password';
|
||||
document.getElementById("buttonShowPasswordLogin").classList.remove('fa-eye-slash');
|
||||
document.getElementById("buttonShowPasswordLogin").classList.add('fa-eye');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
981
src/resources/public/script.js
Normal file
981
src/resources/public/script.js
Normal file
@@ -0,0 +1,981 @@
|
||||
/**
|
||||
* @type{String[]} tablenames
|
||||
*/
|
||||
let tablenames;
|
||||
let socket;
|
||||
let toasttimeout = 2000;
|
||||
|
||||
function fetchcontent(url, cb){
|
||||
fetch(url)
|
||||
.then((response)=>response.text())
|
||||
.then((data)=>cb(data))
|
||||
.catch((err)=>console.log("Error: "+err));
|
||||
}
|
||||
|
||||
function getLoginBody(loadcomplete){
|
||||
let div = document.getElementById("appcontent")
|
||||
if (div){
|
||||
fetchcontent("login.html", (data)=>{
|
||||
div.innerHTML = data;
|
||||
loadcomplete();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function HandleLoginForm(){
|
||||
localStorage.removeItem("username");
|
||||
getLoginBody(()=>{
|
||||
let loginbutton = document.getElementById("loginbutton");
|
||||
loginbutton?.addEventListener("click",()=>{
|
||||
|
||||
let username = document.getElementById("login_username")?.value;
|
||||
let password = document.getElementById("login_password")?.value;
|
||||
if (username && password){
|
||||
login(socket, username, password, (loginresult)=>{
|
||||
if (loginresult.startsWith("welcome ")){
|
||||
localStorage.setItem("username", username);
|
||||
HandleFisTableForm(null);
|
||||
}else alert("Invalid Username or Password");
|
||||
});
|
||||
} else alert("Username and Password must be filled");
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load content of fisTable.html to div#appcontent
|
||||
* @param loadcomplete callback called when load complete
|
||||
*/
|
||||
function getFisTableBody(loadcomplete){
|
||||
let div = document.getElementById("appcontent")
|
||||
if (div){
|
||||
fetchcontent("fisTable.html", (data)=>{
|
||||
div.innerHTML = data;
|
||||
loadcomplete();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load content of fisTable.html to div#appcontent
|
||||
* @param {String|null} tablename if supplied with tablename, will getdata from server for that table
|
||||
*/
|
||||
function HandleFisTableForm(tablename){
|
||||
getFisTableBody(()=>{
|
||||
// clear table
|
||||
clear_fistablebody();
|
||||
// clear existing table list
|
||||
regettablenames(()=>{
|
||||
let tableexisting = document.getElementById("tableexisting");
|
||||
if (valid_date_yyyymmdd(tablename)){
|
||||
tableexisting.value = tablename;
|
||||
let getdata = document.getElementById("getdata");
|
||||
getdata.click();
|
||||
}
|
||||
});
|
||||
CreateTableEventHandler(document.getElementById("createtable"));
|
||||
DropTableEventHandler(document.getElementById("droptable"));
|
||||
GetDataEventHandler(document.getElementById("getdata"));
|
||||
InsertDataEventHandler(document.getElementById("insertdata"));
|
||||
ExportToExcelEventHandler(document.getElementById("exporttoexcel"));
|
||||
ImportFromExcelEventHandler(document.getElementById("importfromexcel"));
|
||||
LogoutEventHandler(document.getElementById("logoutbutton"));
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function geteditFlight(loadcomplete){
|
||||
let div = document.getElementById("appcontent")
|
||||
if (div){
|
||||
fetchcontent("editFlight.html", (data)=>{
|
||||
div.innerHTML = data;
|
||||
loadcomplete();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function HandleEditFlightForm(tablename, fisdata){
|
||||
if (tablename!=null && tablename.length>0){
|
||||
if (fisdata!=null){
|
||||
geteditFlight(()=>{
|
||||
let edit_AD_A = document.getElementById("edit_AD_A");
|
||||
let edit_AD_D = document.getElementById("edit_AD_D");
|
||||
if ("A"===fisdata.DA) edit_AD_A.checked = true; else edit_AD_D.checked = true;
|
||||
let edit_DI_D = document.getElementById("edit_DI_D");
|
||||
let edit_DI_I = document.getElementById("edit_DI_I");
|
||||
if ("I"===fisdata.DI) edit_DI_I.checked = true; else edit_DI_D.checked = true;
|
||||
let airlineCode = document.getElementById("edit_Alcode");
|
||||
airlineCode.value = fisdata.AirlineCode;
|
||||
let flightNumber = document.getElementById("edit_FlightNumber");
|
||||
flightNumber.value = fisdata.FlightNumber;
|
||||
let origin = document.getElementById("edit_Origin");
|
||||
origin.value = fisdata.Origin;
|
||||
let destination = document.getElementById("edit_Destination");
|
||||
destination.value = fisdata.Destination;
|
||||
let gate = document.getElementById("edit_Gate");
|
||||
gate.value = fisdata.Gate;
|
||||
let terminal = document.getElementById("edit_Terminal");
|
||||
terminal.value = fisdata.Terminal;
|
||||
let baggageClaim = document.getElementById("edit_BaggageClaim");
|
||||
baggageClaim.value = fisdata.BaggageClaim;
|
||||
if ("D"===fisdata.DA) baggageClaim.disabled = true; // departure --> disable Baggage Conveyor belt
|
||||
let checkinCounter = document.getElementById("edit_CheckIn");
|
||||
checkinCounter.value = fisdata.CheckinCounter;
|
||||
if ("A"===fisdata.DA) checkinCounter.disabled = true; // arrival --> disable check in counter
|
||||
let scheduledTime = document.getElementById("edit_scheduled");
|
||||
scheduledTime.value = DateTimeContainsT(fisdata.scheduledTime) ? fisdata.scheduledTime : ConvertToTFormat(fisdata.scheduledTime);
|
||||
let estimatedTime = document.getElementById("edit_estimated");
|
||||
estimatedTime.value = DateTimeContainsT(fisdata.estimatedTime) ? fisdata.estimatedTime : ConvertToTFormat(fisdata.estimatedTime);
|
||||
let actualTime = document.getElementById("edit_actual");
|
||||
actualTime.value = DateTimeContainsT(fisdata.actualTime) ? fisdata.actualTime : ConvertToTFormat(fisdata.actualTime);
|
||||
let gatescheduledTime = document.getElementById("edit_gateScheduled");
|
||||
gatescheduledTime.value = DateTimeContainsT(fisdata.gateScheduledTime) ? fisdata.gateScheduledTime : ConvertToTFormat(fisdata.gateScheduledTime);
|
||||
let gateestimatedTime = document.getElementById("edit_gateEstimated");
|
||||
gateestimatedTime.value = DateTimeContainsT(fisdata.gateEstimatedTime) ? fisdata.gateEstimatedTime : ConvertToTFormat(fisdata.gateEstimatedTime);
|
||||
let gateactualTime = document.getElementById("edit_gateActual");
|
||||
gateactualTime.value = DateTimeContainsT(fisdata.gateActualTime) ? fisdata.gateActualTime : ConvertToTFormat(fisdata.gateActualTime);
|
||||
let remark = document.getElementById("edit_remark");
|
||||
remark.value = fisdata.Remark;
|
||||
|
||||
let cancel = document.getElementById("cancelbutton");
|
||||
if (cancelbutton){
|
||||
cancelbutton.addEventListener("click",()=>{
|
||||
HandleFisTableForm(tablename);
|
||||
});
|
||||
}
|
||||
let savebutton = document.getElementById("savebutton");
|
||||
if (savebutton){
|
||||
savebutton.addEventListener("click",()=>{
|
||||
let need_save = false;
|
||||
// item lain di-disabled, tidak bisa edit
|
||||
if (gate.value!==fisdata.Gate) need_save = true;
|
||||
if (terminal.value!==fisdata.Terminal) need_save = true;
|
||||
if (baggageClaim.value!==fisdata.BaggageClaim) need_save = true;
|
||||
if (checkinCounter.value!==fisdata.CheckinCounter) need_save = true;
|
||||
if (estimatedTime.value!==fisdata.estimatedTime) need_save = true;
|
||||
if (actualTime.value!==fisdata.actualTime) need_save = true;
|
||||
if (gateestimatedTime.value!==fisdata.gateEstimatedTime) need_save = true;
|
||||
if (gateactualTime.value!==fisdata.gateActualTime) need_save = true;
|
||||
if (remark.value!==fisdata.Remark) need_save = true;
|
||||
if (need_save){
|
||||
fisdata.Gate = gate.value;
|
||||
fisdata.Terminal = terminal.value;
|
||||
fisdata.BaggageClaim = baggageClaim.value;
|
||||
fisdata.CheckinCounter = checkinCounter.value;
|
||||
fisdata.estimatedTime = DateTimeContainsT(estimatedTime.value) ? ConvertToSpaceFormat(estimatedTime.value) : estimatedTime.value;
|
||||
fisdata.actualTime = DateTimeContainsT(actualTime.value) ? ConvertToSpaceFormat(actualTime.value) : actualTime.value;
|
||||
fisdata.gateEstimatedTime = DateTimeContainsT(gateestimatedTime.value) ? ConvertToSpaceFormat(gateestimatedTime.value) : gateestimatedTime.value;
|
||||
fisdata.gateActualTime = DateTimeContainsT(gateactualTime.value) ? ConvertToSpaceFormat(gateactualTime.value) : gateactualTime.value;
|
||||
|
||||
if (remark.value!==fisdata.Remark){
|
||||
fisdata.Remark = remark.value;
|
||||
// supaya AAS broadcast lagi
|
||||
fisdata.IsRead="0";
|
||||
}
|
||||
fisdata.lastUpdated = DateToString(Date.now());
|
||||
editrow(socket, tablename, fisdata.id, fisdata, (editresult)=>{
|
||||
if ("success"===editresult){
|
||||
ToastMessage("Row edited successfully", toasttimeout);
|
||||
HandleFisTableForm(tablename);
|
||||
} else ToastMessage("Failed to edit row, result = "+editresult, toasttimeout);
|
||||
});
|
||||
|
||||
} else {
|
||||
ToastMessage("No changes to save", toasttimeout);
|
||||
HandleFisTableForm(tablename);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else ToastMessage("Select a row to edit data", toasttimeout);
|
||||
} else ToastMessage("Select a table to edit data", toasttimeout);
|
||||
|
||||
}
|
||||
|
||||
function getinputFlight(loadcomplete){
|
||||
let div = document.getElementById("appcontent")
|
||||
if (div){
|
||||
fetchcontent("inputFlight.html", (data)=>{
|
||||
div.innerHTML = data;
|
||||
loadcomplete();
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Input Flight Form on tablename
|
||||
* @param tablename table name to insert data
|
||||
*/
|
||||
function HandleInputFlightForm(tablename){
|
||||
getinputFlight(()=>{
|
||||
let radioAD_A = document.getElementById("input_AD_A");
|
||||
let radioAD_D = document.getElementById("input_AD_D");
|
||||
let radioDI_D = document.getElementById("input_DI_D");
|
||||
let radioDI_I = document.getElementById("input_DI_I");
|
||||
let airlineCode = document.getElementById("input_Alcode");
|
||||
let flightNumber = document.getElementById("input_FlightNumber");
|
||||
let origin = document.getElementById("input_Origin");
|
||||
let destination = document.getElementById("input_Destination");
|
||||
let gate = document.getElementById("input_Gate");
|
||||
let terminal = document.getElementById("input_Terminal");
|
||||
let baggageClaim = document.getElementById("input_BaggageClaim");
|
||||
let checkinCounter = document.getElementById("input_CheckIn");
|
||||
let scheduledTime = document.getElementById("input_scheduled");
|
||||
let estimatedTime = document.getElementById("input_estimated");
|
||||
let actualTime = document.getElementById("input_actual");
|
||||
let gatescheduledTime = document.getElementById("input_gateScheduled");
|
||||
let gateestimatedTime = document.getElementById("input_gateEstimated");
|
||||
let gateactualTime = document.getElementById("input_gateActual");
|
||||
let remark = document.getElementById("input_remark");
|
||||
|
||||
radioAD_A.addEventListener("change",()=>{
|
||||
if (radioAD_A.checked) {
|
||||
// arrival --> disable check in counter
|
||||
checkinCounter.disabled = true;
|
||||
if (checkinCounter.value.length>0) checkinCounter.prevvalue = checkinCounter.value;
|
||||
checkinCounter.value = "0";
|
||||
// arrival --> butuh Baggage Conveyor belt
|
||||
baggageClaim.disabled = false;
|
||||
if (baggageClaim.prevvalue) baggageClaim.value = baggageClaim.prevvalue;
|
||||
}
|
||||
});
|
||||
radioAD_D.addEventListener("change",()=>{
|
||||
if (radioAD_D.checked){
|
||||
// departure --> disable Baggage Conveyor belt
|
||||
baggageClaim.disabled = true;
|
||||
if (baggageClaim.value.length>0) baggageClaim.prevvalue = baggageClaim.value;
|
||||
baggageClaim.value = "0";
|
||||
// departure --> butuh Checkin Counter
|
||||
checkinCounter.disabled = false;
|
||||
if (checkinCounter.prevvalue) checkinCounter.value = checkinCounter.prevvalue;
|
||||
}
|
||||
});
|
||||
|
||||
scheduledTime.addEventListener("change",()=>{
|
||||
if (DateTimeContainsT(scheduledTime.value)){
|
||||
// data valid
|
||||
// ketika inputflight, estimatedTime dan actualTime akan sama dengan scheduledTime
|
||||
estimatedTime.value = scheduledTime.value;
|
||||
actualTime.value = scheduledTime.value;
|
||||
} else {
|
||||
ToastMessage("Invalid Scheduled Time", toasttimeout);
|
||||
}
|
||||
});
|
||||
|
||||
gatescheduledTime.addEventListener("change",()=>{
|
||||
if (DateTimeContainsT(gatescheduledTime.value)){
|
||||
// data valid
|
||||
// ketika inputflight, gateestimatedTime dan gateactualTime akan sama dengan gatescheduledTime
|
||||
gateestimatedTime.value = gatescheduledTime.value;
|
||||
gateactualTime.value = gatescheduledTime.value;
|
||||
} else{
|
||||
ToastMessage("Invalid Gate Scheduled Time", toasttimeout);
|
||||
}
|
||||
});
|
||||
|
||||
let cancelbutton = document.getElementById("cancelbutton");
|
||||
cancelbutton.addEventListener("click",()=>{
|
||||
HandleFisTableForm(tablename);
|
||||
});
|
||||
let savebutton = document.getElementById("savebutton");
|
||||
savebutton.addEventListener("click",()=>{
|
||||
if (!radioAD_D.checked && !radioAD_A.checked){
|
||||
ToastMessage("Select Arrival or Departure", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!radioDI_D.checked && !radioDI_I.checked){
|
||||
ToastMessage("Select Domestic or International", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (airlineCode.value.length!==2){
|
||||
ToastMessage("Airline Code must be 2 characters", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
const regex_airlinecode = /^[A-Z]{2}$/;
|
||||
if (!regex_airlinecode.test(airlineCode.value)){
|
||||
ToastMessage("Airline Code must be 2 uppercase alphabets", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (flightNumber.value.length<1 || flightNumber.value.length>4){
|
||||
ToastMessage("Flight Number must be 1 to 4 characters", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
const regex_flightnumber = /^[0-9]{1,4}$/;
|
||||
if (!regex_flightnumber.test(flightNumber.value)){
|
||||
ToastMessage("Flight Number must be 1 to 4 digits", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (origin.value.length!==3){
|
||||
ToastMessage("Origin must be 3 characters", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
const regex_origin = /^[A-Z]{3}$/;
|
||||
if (!regex_origin.test(origin.value)){
|
||||
ToastMessage("Origin must be 3 uppercase alphabets", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (destination.value.length<3){
|
||||
ToastMessage("Destination must be 3 at least characters", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
const regex_destination = /^([A-Z]{3})(,[A-Z]{3})*$/;
|
||||
if (!regex_destination.test(destination.value)){
|
||||
ToastMessage("Destination must be 3 uppercase alphabets. Multi Destinations must be separated by comma", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
let destinations = destination.value.split(",");
|
||||
if (destinations.length>1){
|
||||
destinations.forEach(dest => {
|
||||
if (dest.trim().indexOf(origin.value)!==-1){
|
||||
ToastMessage("Destination must be different from Origin", toasttimeout);
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (gate.value.length<1){
|
||||
ToastMessage("Gate must be filled", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (terminal.value.length<1){
|
||||
ToastMessage("Terminal must be filled", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (baggageClaim.value.length<1){
|
||||
ToastMessage("Baggage Claim must be filled", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checkinCounter.value.length<1){
|
||||
ToastMessage("Checkin Counter must be filled", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (remark.value.length<1){
|
||||
ToastMessage("Remark must be filled", toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DateTimeContainsT(scheduledTime.value)){
|
||||
ToastMessage("Invalid Scheduled Time = "+scheduledTime.value, toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DateTimeContainsT(estimatedTime.value)){
|
||||
ToastMessage("Invalid Estimated Time = "+estimatedTime.value, toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DateTimeContainsT(actualTime.value)){
|
||||
ToastMessage("Invalid Actual Time = "+actualTime.value, toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DateTimeContainsT(gatescheduledTime.value)){
|
||||
ToastMessage("Invalid Gate Scheduled Time = "+gatescheduledTime.value, toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DateTimeContainsT(gateestimatedTime.value)){
|
||||
ToastMessage("Invalid Gate Estimated Time = "+gateestimatedTime.value, toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DateTimeContainsT(gateactualTime.value)){
|
||||
ToastMessage("Invalid Gate Actual Time = "+gateactualTime.value, toasttimeout);
|
||||
return;
|
||||
}
|
||||
|
||||
let newdata = new FisData();
|
||||
newdata.AirlineCode = airlineCode.value;
|
||||
newdata.FlightNumber = flightNumber.value;
|
||||
newdata.DA = radioAD_D.checked ? "D" : "A";
|
||||
newdata.DI = radioDI_D.checked ? "D" : "I";
|
||||
newdata.Origin = origin.value;
|
||||
newdata.Destination = destination.value;
|
||||
newdata.Gate = gate.value;
|
||||
newdata.Terminal = terminal.value;
|
||||
newdata.BaggageClaim = baggageClaim.value;
|
||||
newdata.CheckinCounter = checkinCounter.value;
|
||||
newdata.scheduledTime = DateTimeContainsT(scheduledTime.value) ? ConvertToSpaceFormat(scheduledTime.value) : scheduledTime.value;
|
||||
newdata.estimatedTime = DateTimeContainsT(estimatedTime.value) ? ConvertToSpaceFormat(estimatedTime.value) : estimatedTime.value;
|
||||
newdata.actualTime = DateTimeContainsT(actualTime.value) ? ConvertToSpaceFormat(actualTime.value) : actualTime.value;
|
||||
newdata.gateScheduledTime = DateTimeContainsT(gatescheduledTime.value) ? ConvertToSpaceFormat(gatescheduledTime.value) : gatescheduledTime.value;
|
||||
newdata.gateEstimatedTime = DateTimeContainsT(gateestimatedTime.value) ? ConvertToSpaceFormat(gateestimatedTime.value) : gateestimatedTime.value;
|
||||
newdata.gateActualTime = DateTimeContainsT(gateactualTime.value) ? ConvertToSpaceFormat(gateactualTime.value) : gateactualTime.value;
|
||||
newdata.Remark = remark.value;
|
||||
newdata.IsRead = "0";
|
||||
newdata.lastUpdated = DateToString(Date.now());
|
||||
insertrow(socket, tablename, newdata, (insertresult)=>{
|
||||
ToastMessage("Insert Result = "+insertresult, toasttimeout);
|
||||
// back to fisTable
|
||||
HandleFisTableForm(tablename);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if value is DateTime in format yyyy-MM-ddTHH:mm:ss
|
||||
* @param value value to check
|
||||
* @return {boolean} true if valid
|
||||
*/
|
||||
function DateTimeContainsT(value){
|
||||
if (value!=null && value.length>0){
|
||||
return /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/.test(value);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Date String from yyyy-MM-ddTHH:mm:ss to yyyy-MM-dd HH:mm:ss
|
||||
* @param value value to convert
|
||||
* @return {String|null} converted value
|
||||
*/
|
||||
function ConvertToSpaceFormat(value){
|
||||
if (value!=null && value.length>0){
|
||||
return value.replace("T"," ");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Date String from yyyy-MM-dd HH:mm:ss to yyyy-MM-ddTHH:mm:ss
|
||||
* @param value value to convert
|
||||
* @return {String|null} converted value
|
||||
*/
|
||||
function ConvertToTFormat(value){
|
||||
if (value!=null && value.length>0){
|
||||
return value.replace(" ","T");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Application Start from Here !
|
||||
*/
|
||||
document.addEventListener("DOMContentLoaded", ()=>{
|
||||
|
||||
|
||||
socket = io(":7001");
|
||||
|
||||
socket.on("connect",()=>{
|
||||
console.log("Connected to Server, socket id = "+socket.id);
|
||||
// Source : https://socket.io/docs/v4/connection-state-recovery
|
||||
// new on Socket.io v 4.6.0
|
||||
// if (socket.recovered){
|
||||
// // any event missed during the disconnection period will be received now
|
||||
// } else {
|
||||
// // new or unrecoverable session
|
||||
// }
|
||||
|
||||
// get and change connection status text and button
|
||||
let textstatus = document.getElementById("text_status");
|
||||
if (textstatus) textstatus.innerHTML = "Connected";
|
||||
let btnstatus = document.getElementById("status_connection");
|
||||
if (btnstatus) btnstatus.className = "btn-connected";
|
||||
|
||||
|
||||
});
|
||||
socket.on("connect_error", (err)=>console.log("Connection Error: "+err));
|
||||
|
||||
socket.on("disconnect",(reason,detail)=>{
|
||||
console.log("Disconnected from Server. Reason: "+reason)
|
||||
let textstatus = document.getElementById("text_status");
|
||||
if (textstatus) textstatus.innerHTML = "Disconnected";
|
||||
let btnstatus = document.getElementById("status_connection");
|
||||
if (btnstatus) btnstatus.className = "btn-disconnected";
|
||||
});
|
||||
|
||||
|
||||
socket.on("error", (err)=>console.log("Error: "+err));
|
||||
socket.on("message", (msg)=>console.log("Message: "+msg));
|
||||
let username = localStorage.getItem("username");
|
||||
if (username && username.length>0){
|
||||
HandleFisTableForm(null);
|
||||
} else HandleLoginForm();
|
||||
|
||||
})
|
||||
|
||||
function clear_fistablebody(){
|
||||
let fistablebody = document.getElementById("fistablebody");
|
||||
if (fistablebody) {
|
||||
fistablebody.datatag = null;
|
||||
fistablebody.innerHTML = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data tag identified at fistablebody
|
||||
* @return {String|null}
|
||||
*/
|
||||
function get_fistablebody_datatag(){
|
||||
let fistablebody = document.getElementById("fistablebody");
|
||||
if (fistablebody){
|
||||
return fistablebody.datatag;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill table body with data
|
||||
* @param {String} tag identifier for table body as indicator
|
||||
* @param {FisData[]}data
|
||||
*/
|
||||
function fill_fistablebody(tag, data){
|
||||
let fistablebody = document.getElementById("fistablebody");
|
||||
if (fistablebody){
|
||||
fistablebody.datatag = tag;
|
||||
data.forEach((row)=>{
|
||||
let tr = document.createElement("tr");
|
||||
tr.appendChild(create_td_label(null, row.id));
|
||||
tr.appendChild(create_td_label(null, row.AirlineCode));
|
||||
tr.appendChild(create_td_label(null, row.FlightNumber));
|
||||
tr.appendChild(create_td_label(null, row.DA));
|
||||
tr.appendChild(create_td_label(null, row.DI));
|
||||
tr.appendChild(create_td_label(null, row.Origin));
|
||||
tr.appendChild(create_td_label(null, row.Destination));
|
||||
tr.appendChild(create_td_label(null, row.Gate));
|
||||
tr.appendChild(create_td_label(null, row.Terminal));
|
||||
tr.appendChild(create_td_label(null, row.estimatedTime));
|
||||
tr.appendChild(create_td_label(null, row.Remark));
|
||||
tr.appendChild(create_td_edit_delete_buttons(()=>{
|
||||
HandleEditFlightForm(tag, row);
|
||||
},()=>{
|
||||
if (confirm(`Are you sure to delete this row=${row.id}, Airline=${row.AirlineCode}, FlightNumber=${row.FlightNumber} ?`)){
|
||||
deleterow(socket, tag, row.id, (deleterowresult)=>{
|
||||
if ("success"===deleterowresult){
|
||||
ToastMessage("Row deleted successfully", toasttimeout);
|
||||
// reget data to refresh table
|
||||
let btngetdata = document.getElementById("getdata");
|
||||
if (btngetdata) btngetdata.click();
|
||||
} else ToastMessage("Failed to delete row, result = "+deleterowresult, toasttimeout);
|
||||
});
|
||||
|
||||
}
|
||||
}));
|
||||
fistablebody.appendChild(tr);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create td element contains label inside
|
||||
* @param id not null, set the id of label
|
||||
* @param value value of label
|
||||
* @return {HTMLTableCellElement} created td element
|
||||
*/
|
||||
function create_td_label(id, value){
|
||||
const td = document.createElement("td");
|
||||
const lbl = document.createElement("label");
|
||||
lbl.innerHTML = value;
|
||||
if (id!=null && id.length>0) lbl.id = id;
|
||||
td.appendChild(lbl);
|
||||
return td;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create td element contains edit and delete buttons
|
||||
* @param {Function} cbedit callback called when edit button clicked
|
||||
* @param {Function} cbdelete callback called when delete button clicked
|
||||
* @return {HTMLTableCellElement} created td element
|
||||
*/
|
||||
function create_td_edit_delete_buttons(cbedit, cbdelete){
|
||||
const td = document.createElement("td");
|
||||
const div1 = document.createElement("div");
|
||||
div1.className = "row";
|
||||
|
||||
const div2a = document.createElement("div");
|
||||
div2a.className = "col";
|
||||
const btn1 = document.createElement("button");
|
||||
btn1.className = "btn btn-sm btn-primary";
|
||||
btn1.addEventListener("click", cbedit);
|
||||
const span1 = document.createElement("span");
|
||||
span1.className = "fa-solid fa-pencil";
|
||||
btn1.appendChild(span1);
|
||||
div2a.appendChild(btn1);
|
||||
|
||||
const div2b = document.createElement("div");
|
||||
div2b.className = "col";
|
||||
const btn2 = document.createElement("button");
|
||||
btn2.className = "btn btn-sm btn-danger";
|
||||
btn2.addEventListener("click", cbdelete);
|
||||
const span2 = document.createElement("span");
|
||||
span2.className = "fa-solid fa fa-trash";
|
||||
btn2.appendChild(span2);
|
||||
div2b.appendChild(btn2);
|
||||
|
||||
|
||||
div1.appendChild(div2a);
|
||||
div1.appendChild(div2b);
|
||||
|
||||
td.appendChild(div1);
|
||||
return td;
|
||||
}
|
||||
|
||||
/**
|
||||
* To get table names from server
|
||||
* @param {Function} finishcallback callback called when finish
|
||||
*/
|
||||
function regettablenames(finishcallback){
|
||||
let tableexisting = document.getElementById("tableexisting");
|
||||
tableexisting.options.length = 0;
|
||||
tablenames = [];
|
||||
|
||||
socket.emit("gettablenames", (reply)=>{
|
||||
if (reply!=null){
|
||||
if ("success"===reply.result){
|
||||
if (Array.isArray(reply.data) && reply.data.length>0){
|
||||
tablenames = reply.data.map(tn => ConvertDateFormat1(tn));
|
||||
let tableexisting = document.getElementById("tableexisting");
|
||||
tablenames.forEach(tablename => {
|
||||
let option = document.createElement("option");
|
||||
option.text = tablename;
|
||||
tableexisting.add(option);
|
||||
});
|
||||
tableexisting.selectedIndex = -1;
|
||||
|
||||
if (finishcallback) finishcallback();
|
||||
return;
|
||||
} else ToastMessage("gettablename result is not array or empty", toasttimeout);
|
||||
} else ToastMessage("Failed to get table names", toasttimeout);
|
||||
} else ToastMessage("No reply from gettablenames",toasttimeout);
|
||||
if (finishcallback) finishcallback();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* To handle button click for Create Table
|
||||
* @param {HTMLElement} btn Button for Create Table
|
||||
*/
|
||||
function CreateTableEventHandler(btn){
|
||||
btn?.addEventListener("click",()=>{
|
||||
// table untuk display FisData di sini
|
||||
let fistablebody = document.getElementById("fistablebody");
|
||||
// textarea untuk input tablename di sini , dalam format yyyy-MM-dd
|
||||
let tablename = document.getElementById("tablename");
|
||||
|
||||
if (valid_date_yyyymmdd(tablename?.value)){
|
||||
if (tablenames.includes(tablename.value)){
|
||||
ToastMessage("Table already exists", toasttimeout);
|
||||
return;
|
||||
}
|
||||
createtable(socket, tablename.value, (createresult)=>{
|
||||
if ("success"===createresult){
|
||||
regettablenames(null);
|
||||
ToastMessage("Table created successfully", toasttimeout);
|
||||
} else ToastMessage("Failed to create table, result = "+createresult, toasttimeout);
|
||||
});
|
||||
|
||||
} else ToastMessage("Valid tablename in format YYYY-MM-DD, from year 1970 to 2100", toasttimeout);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* To handle button click for Drop Table
|
||||
* @param {HTMLElement} btn Button untuk Drop Table
|
||||
*/
|
||||
function DropTableEventHandler(btn){
|
||||
btn?.addEventListener("click",()=>{
|
||||
// table untuk display FisData di sini
|
||||
let fistablebody = document.getElementById("fistablebody");
|
||||
// textarea untuk input tablename di sini , dalam format yyyy-MM-dd
|
||||
let tableexisting = document.getElementById("tableexisting");
|
||||
|
||||
if (tableexisting.options.length>0){
|
||||
if (valid_date_yyyymmdd(tableexisting?.value)){
|
||||
let tablename = tableexisting.value;
|
||||
if (confirm("Are you sure to drop table "+tablename+" ?")){
|
||||
droptable(socket, tablename, (dropresult)=>{
|
||||
if ("success"===dropresult){
|
||||
if (tablename===get_fistablebody_datatag()){
|
||||
// table yang di drop adalah table yang sedang ditampilkan
|
||||
clear_fistablebody();
|
||||
}
|
||||
regettablenames(null);
|
||||
ToastMessage("Table dropped successfully", toasttimeout);
|
||||
} else ToastMessage("Failed to drop table, result = "+dropresult, toasttimeout);
|
||||
});
|
||||
} else ToastMessage("Drop table cancelled",toasttimeout);
|
||||
} else ToastMessage("Valid tablename in format YYYY-MM-DD, from year 1970 to 2100", toasttimeout);
|
||||
} else ToastMessage("No table to drop", toasttimeout);
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* To handle button click for Get Data
|
||||
* @param {HTMLElement} btn Button untuk Get Data
|
||||
*/
|
||||
function GetDataEventHandler(btn){
|
||||
btn?.addEventListener("click",()=>{
|
||||
// table untuk display FisData di sini
|
||||
let fistablebody = document.getElementById("fistablebody");
|
||||
// textarea untuk input tablename di sini , dalam format yyyy-MM-dd
|
||||
let tableexisting = document.getElementById("tableexisting");
|
||||
|
||||
if (valid_date_yyyymmdd(tableexisting?.value)){
|
||||
let filter_airline = document.getElementById("filter_airline")?.value;
|
||||
let filter_flightnumber = document.getElementById("filter_flightnumber")?.value;
|
||||
let filter_da = document.getElementById("filter_da")?.value;
|
||||
let filter_di = document.getElementById("filter_di")?.value;
|
||||
getdata(socket, tableexisting.value,filter_airline,filter_flightnumber,filter_da,filter_di, (getdataresult)=>{
|
||||
if (["success","no data"].includes(getdataresult.result)){
|
||||
let tabletitle = document.getElementById("tabletitle");
|
||||
tabletitle.innerHTML = "Flight Schedule For "+tableexisting.value;
|
||||
clear_fistablebody()
|
||||
fill_fistablebody(tableexisting.value, getdataresult.data);
|
||||
} else ToastMessage("Failed to get data, result = "+getdataresult.result, toasttimeout);
|
||||
});
|
||||
|
||||
} else ToastMessage("Valid tablename in format YYYY-MM-DD, from year 1970 to 2100",toasttimeout);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function ExportToExcelEventHandler(btn){
|
||||
btn?.addEventListener("click",()=>{
|
||||
// table untuk display FisData di sini
|
||||
let fistablebody = document.getElementById("fistablebody");
|
||||
let tablename = fistablebody?.datatag;
|
||||
if (valid_date_yyyymmdd(tablename)){
|
||||
exporttoxls(socket, tablename, (exportresult)=>{
|
||||
if ("success"===exportresult.result){
|
||||
if (exportresult.size>0){
|
||||
if (exportresult.filename.length>0){
|
||||
|
||||
fetch(exportresult.filename)
|
||||
.then(response => {
|
||||
if (response.ok)
|
||||
return response.blob();
|
||||
else ToastMessage("Failed to fetch file "+exportresult.filename, toasttimeout);
|
||||
})
|
||||
.then(blob => {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.style.display = 'none';
|
||||
a.href = url;
|
||||
a.download = "export.xlsx";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
ToastMessage("Failed to fetch file "+exportresult.filename, toasttimeout);
|
||||
})
|
||||
|
||||
} else ToastMessage("No filename to export", toasttimeout);
|
||||
} else ToastMessage("No data to export", toasttimeout);
|
||||
} else ToastMessage("Failed to export data, result = "+exportresult.result, toasttimeout);
|
||||
});
|
||||
|
||||
|
||||
} else ToastMessage("Select a table to export to Excel", toasttimeout);
|
||||
})
|
||||
}
|
||||
|
||||
function ImportFromExcelEventHandler(btn){
|
||||
btn?.addEventListener("click",()=>{
|
||||
console.log("Import from Excel button clicked");
|
||||
// table untuk display FisData di sini
|
||||
let fistablebody = document.getElementById("fistablebody");
|
||||
let tablename = fistablebody?.datatag;
|
||||
if (valid_date_yyyymmdd(tablename)){
|
||||
let filename = document.getElementById("filename");
|
||||
if (filename!=null && filename.value.length>0){
|
||||
if (confirm("Are you sure to import data from "+filename.value+" to "+tablename+" ? Existing data will be replaced")){
|
||||
// importfromxls(socket, tablename, filename.valu, filename.files[0], (importresult)=>{
|
||||
// console.log("importresult = "+importresult);
|
||||
// if ("success"===importresult){
|
||||
// ToastMessage("Data imported successfully", toasttimeout);
|
||||
// // reget data to refresh table
|
||||
// let btngetdata = document.getElementById("getdata");
|
||||
// if (btngetdata) btngetdata.click();
|
||||
// } else ToastMessage("Failed to import data, result = "+importresult, toasttimeout);
|
||||
// });
|
||||
|
||||
let formdata = new FormData();
|
||||
formdata.append("file", filename.files[0]);
|
||||
formdata.append("tablename", tablename);
|
||||
fetch("/import",{
|
||||
method: "POST",
|
||||
body: formdata
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log(JSON.stringify(data));
|
||||
|
||||
if (data.result==="success"){
|
||||
ToastMessage("Data imported successfully", toasttimeout);
|
||||
let btngetdata = document.getElementById("getdata");
|
||||
if (btngetdata) btngetdata.click();
|
||||
|
||||
} else ToastMessage("Failed to import data, result = " + data.result, toasttimeout);
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error: "+error);
|
||||
ToastMessage("Failed to import data, error = "+error, toasttimeout);
|
||||
})
|
||||
} else ToastMessage("Import data cancelled", toasttimeout);
|
||||
} else ToastMessage("Select a file to import", toasttimeout);
|
||||
} else ToastMessage("Choose a table to import from Excel", toasttimeout);
|
||||
})
|
||||
}
|
||||
|
||||
function LogoutEventHandler(btn){
|
||||
btn?.addEventListener("click",()=>{
|
||||
if(confirm("Are you sure to logout ?")) {
|
||||
localStorage.removeItem("username");
|
||||
HandleLoginForm();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* To handle button click for Insert Data
|
||||
* @param {HTMLElement} btn Button to insert data
|
||||
*/
|
||||
function InsertDataEventHandler(btn){
|
||||
btn?.addEventListener("click",()=>{
|
||||
// table untuk display FisData di sini
|
||||
let fistablebody = document.getElementById("fistablebody");
|
||||
let tablename = fistablebody?.datatag;
|
||||
if (valid_date_yyyymmdd(tablename)){
|
||||
HandleInputFlightForm(tablename);
|
||||
} else ToastMessage("Select a table to insert data",toasttimeout);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert value in format yyyyMMdd to yyyy-MM-dd
|
||||
* @param {String} value value to convert
|
||||
* @returns {String} converted value
|
||||
*/
|
||||
function ConvertDateFormat1(value){
|
||||
if (validstring(value) && value.length===8){
|
||||
let year = value.substring(0,4);
|
||||
let month = value.substring(4,6);
|
||||
let day = value.substring(6,8);
|
||||
if (valid_day_month_year(+day,+month,+year)){
|
||||
return year+"-"+month+"-"+day;
|
||||
}
|
||||
}
|
||||
// kalau beda format, return as is
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given day, month, year is valid
|
||||
* @param {number} day
|
||||
* @param {number} month
|
||||
* @param {number} year
|
||||
* @return {boolean} true if valid
|
||||
*/
|
||||
function valid_day_month_year(day,month,year){
|
||||
if (day>0 && day<32){
|
||||
if (month>0 && month<13){
|
||||
if (year>=1970 && year<=2100){
|
||||
const DaysInMonth = new Date(year, month, day).getDate();
|
||||
return (day <= DaysInMonth);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Date to String in format yyyy-MM-dd
|
||||
* @param timestamp
|
||||
* @returns {string}
|
||||
*/
|
||||
function DateToString(timestamp){
|
||||
const date = new Date(timestamp);
|
||||
|
||||
// Get the individual components of the date
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0'); // Months are zero-based
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0');
|
||||
|
||||
// Format the date string
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
const regex_tablename = /^\d{4}-\d{2}-\d{2}$/
|
||||
|
||||
/**
|
||||
* Check if a value is valid date in format yyyy-MM-dd
|
||||
* @param {String} value
|
||||
* @returns true if format valid
|
||||
*/
|
||||
function valid_date_yyyymmdd(value){
|
||||
if (validstring(value)){
|
||||
if (regex_tablename.test(value)){
|
||||
const [year, month, day] = value.split("-").map(Number);
|
||||
return valid_day_month_year(day,month,year);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if a value is valid string and actually have content
|
||||
* @param {String} value
|
||||
* @returns true if valid
|
||||
*/
|
||||
function validstring(value){
|
||||
if (value!=null){
|
||||
if (value.length>0){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Show ToastMessage
|
||||
* @param {String} message message to display
|
||||
* @param {number} timeout timeout in ms
|
||||
*/
|
||||
function ToastMessage(message, timeout){
|
||||
let snackbar = document.getElementById("snackbar");
|
||||
|
||||
if (snackbar==null) return;
|
||||
if (timeout<1) return;
|
||||
if (message!=null && message.length>0){
|
||||
snackbar.innerHTML = message;
|
||||
snackbar.className = "show";
|
||||
setTimeout(function(){ snackbar.className = snackbar.className.replace("show", ""); }, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
7
src/resources/public/socket.io.min.js
vendored
Normal file
7
src/resources/public/socket.io.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
365
src/resources/public/style.css
Normal file
365
src/resources/public/style.css
Normal file
@@ -0,0 +1,365 @@
|
||||
html{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
body{
|
||||
background-color: lightgray;
|
||||
}
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
/* Rounded sliders */
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/*Tambahan Monik*/
|
||||
.class100{
|
||||
width: 100%;
|
||||
}
|
||||
.btn-insert{
|
||||
float: right;
|
||||
}
|
||||
.btn-pad-left{
|
||||
padding-left: 1em !important;
|
||||
}
|
||||
.btn-connected{
|
||||
background-color: lawngreen;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
}
|
||||
.btn-disconnected{
|
||||
background-color: red;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
border-radius: 10px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.font-dashboard{
|
||||
text-decoration: none;
|
||||
font-size: 1.6rem;
|
||||
/*padding-left: 0.3125rem;*/
|
||||
padding-top: 0.5125rem;
|
||||
padding-bottom: 0.5125rem;
|
||||
color: white;
|
||||
}
|
||||
.text-status{
|
||||
color: white;
|
||||
}
|
||||
|
||||
.row-status{
|
||||
margin-top:23px !important;
|
||||
}
|
||||
.table-header{
|
||||
background-color: #363333;
|
||||
color: white;
|
||||
}
|
||||
.card-flight{
|
||||
background-color: whitesmoke;
|
||||
border: none;
|
||||
/*border-radius: 10px;*/
|
||||
padding: 1em 2.5em 2.5em 2.5em !important;
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
|
||||
}
|
||||
.heading-black {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
/*marrgin-top: -8px;*/
|
||||
/*width: 35%;*/
|
||||
color: white;
|
||||
justify-content: space-around;
|
||||
background: #2f2f2f;
|
||||
padding-bottom: 5px;
|
||||
padding-top: 5px;
|
||||
border-radius: 0 10px 0 10px;
|
||||
}
|
||||
.card-header.heading-black {
|
||||
background-color: black;
|
||||
border-top-left-radius: 20px;
|
||||
border-top-right-radius: 20px;
|
||||
}
|
||||
.card-borderless{
|
||||
border: none;
|
||||
border-top-left-radius: 20px;
|
||||
}
|
||||
.pad-top{
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
/* The snackbar - position it at the bottom and in the middle of the screen */
|
||||
#snackbar {
|
||||
visibility: hidden; /* Hidden by default. Visible on click */
|
||||
min-width: 250px; /* Set a default minimum width */
|
||||
margin-left: -125px; /* Divide value of min-width by 2 */
|
||||
background-color: #333; /* Black background color */
|
||||
color: #fff; /* White text color */
|
||||
text-align: center; /* Centered text */
|
||||
border-radius: 2px; /* Rounded borders */
|
||||
padding: 16px; /* Padding */
|
||||
position: fixed; /* Sit on top of the screen */
|
||||
z-index: 1; /* Add a z-index if needed */
|
||||
left: 50%; /* Center the snackbar */
|
||||
bottom: 30px; /* 30px from the bottom */
|
||||
}
|
||||
|
||||
/* Show the snackbar when clicking on a button (class added with JavaScript) */
|
||||
#snackbar.show {
|
||||
visibility: visible; /* Show the snackbar */
|
||||
/* Add animation: Take 0.5 seconds to fade in and out the snackbar.
|
||||
However, delay the fade out process for 2.5 seconds */
|
||||
-webkit-animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||
animation: fadein 0.5s, fadeout 0.5s 2.5s;
|
||||
}
|
||||
|
||||
/* Animations to fade the snackbar in and out */
|
||||
@-webkit-keyframes fadein {
|
||||
from {bottom: 0; opacity: 0;}
|
||||
to {bottom: 30px; opacity: 1;}
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from {bottom: 0; opacity: 0;}
|
||||
to {bottom: 30px; opacity: 1;}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeout {
|
||||
from {bottom: 30px; opacity: 1;}
|
||||
to {bottom: 0; opacity: 0;}
|
||||
}
|
||||
|
||||
@keyframes fadeout {
|
||||
from {bottom: 30px; opacity: 1;}
|
||||
to {bottom: 0; opacity: 0;}
|
||||
}
|
||||
|
||||
/*Tambahan Login*/
|
||||
/*@import url('https://fonts.googleapis.com/css?family=Montserrat:400,500,600,700|Poppins:400,500&display=swap');*/
|
||||
*{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
user-select: none;
|
||||
}
|
||||
.bg-img{
|
||||
background-image: url('images/Airport33.png');
|
||||
/*background: url('images/background-login.jpg');*/
|
||||
/*background: url('../images/airport-terminal.jpg');*/
|
||||
/*background: conic-gradient(black, #343a40, darkgray, whitesmoke);*/
|
||||
/*background: linear-gradient(to bottom,black, darkgray, black);*/
|
||||
/*background-image: linear-gradient(to top, #00c6fb 0%, #005bea 100%);*/
|
||||
/*background-image: linear-gradient(to top, #7798E8 0%, #183B93 100%);*/
|
||||
height: 100vh;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
|
||||
}
|
||||
.bg-img:after{
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: rgb(40 67 73 / 40%);
|
||||
}
|
||||
.content{
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
border-radius: 15px;
|
||||
z-index: 999;
|
||||
text-align: center;
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 60px 32px;
|
||||
width: 370px;
|
||||
transform: translate(-50%,-50%);
|
||||
background: rgba(255,255,255,0.04);
|
||||
box-shadow: -1px 4px 28px 0 rgba(0,0,0,0.75);
|
||||
}
|
||||
.content header{
|
||||
color: white;
|
||||
font-size: 33px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 35px 0;
|
||||
font-family: 'Montserrat',sans-serif;
|
||||
}
|
||||
.field{
|
||||
position: relative;
|
||||
height: 45px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
background: rgba(255,255,255,0.94);
|
||||
/*border-radius: 4px;*/
|
||||
}
|
||||
.field span{
|
||||
color: #222;
|
||||
width: 40px;
|
||||
line-height: 45px;
|
||||
}
|
||||
.field input{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: #222;
|
||||
font-size: 16px;
|
||||
font-family: 'Poppins',sans-serif;
|
||||
}
|
||||
.space{
|
||||
margin-top: 16px;
|
||||
}
|
||||
.showw{
|
||||
position: absolute;
|
||||
right: 13px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
font-family: 'Montserrat',sans-serif;
|
||||
}
|
||||
.pass-key:valid ~ .show{
|
||||
display: block;
|
||||
}
|
||||
.pass{
|
||||
text-align: left;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.pass a{
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
font-family: 'Poppins',sans-serif;
|
||||
}
|
||||
.pass:hover a{
|
||||
text-decoration: underline;
|
||||
}
|
||||
.field input[type="submit"]{
|
||||
background: #3498db;
|
||||
border: 1px solid #2691d9;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
letter-spacing: 1px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
font-family: 'Montserrat',sans-serif;
|
||||
}
|
||||
.field input[type="submit"]:hover{
|
||||
background: #2691d9;
|
||||
}
|
||||
.login{
|
||||
color: white;
|
||||
margin: 20px 0;
|
||||
font-family: 'Poppins',sans-serif;
|
||||
}
|
||||
.input-rad{
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.padding-lock{
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.padding-btn{
|
||||
padding: 0;
|
||||
}
|
||||
.pad-left{
|
||||
padding-left: 30px;
|
||||
}
|
||||
.btn-logout{
|
||||
background-color: #afcef7;
|
||||
color: black;
|
||||
}
|
||||
.padding-icon{
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.button-logout{
|
||||
margin-top: -10px;
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
}
|
||||
.padding-head{
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
.btn-import{
|
||||
color: black;
|
||||
background-color: #e9ecef;
|
||||
border-color: #e9ecef;
|
||||
}
|
||||
|
||||
.form-pad-right{
|
||||
padding-right: 0 !important;
|
||||
padding-left: 0 !important;
|
||||
margin-left: 10px !important;;
|
||||
}
|
||||
.row.input-group.form-control.form-pad-right {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.btn-pad-top{
|
||||
margin-top: 17px !important;
|
||||
}
|
||||
.opt{
|
||||
margin: 5px;
|
||||
}
|
||||
.pad-left-right{
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.pad-option{
|
||||
padding: .375rem .75rem;
|
||||
border: 1px solid #ced4da;
|
||||
border-radius: .25rem;
|
||||
}
|
||||
34
src/resources/simplelogger.properties
Normal file
34
src/resources/simplelogger.properties
Normal file
@@ -0,0 +1,34 @@
|
||||
# SLF4J's SimpleLogger configuration file
|
||||
# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err.
|
||||
|
||||
# Default logging detail level for all instances of SimpleLogger.
|
||||
# Must be one of ("trace", "debug", "info", "warn", or "error").
|
||||
# If not specified, defaults to "info".
|
||||
org.slf4j.simpleLogger.defaultLogLevel=warn
|
||||
|
||||
# Logging detail level for a SimpleLogger instance named "xxxxx".
|
||||
# Must be one of ("trace", "debug", "info", "warn", or "error").
|
||||
# If not specified, the default logging detail level is used.
|
||||
#org.slf4j.simpleLogger.log.xxxxx=
|
||||
|
||||
# Set to true if you want the current date and time to be included in output messages.
|
||||
# Default is false, and will output the number of milliseconds elapsed since startup.
|
||||
#org.slf4j.simpleLogger.showDateTime=false
|
||||
|
||||
# The date and time format to be used in the output messages.
|
||||
# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat.
|
||||
# If the format is not specified or is invalid, the default format is used.
|
||||
# The default format is yyyy-MM-dd HH:mm:ss:SSS Z.
|
||||
#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z
|
||||
|
||||
# Set to true if you want to output the current thread name.
|
||||
# Defaults to true.
|
||||
#org.slf4j.simpleLogger.showThreadName=true
|
||||
|
||||
# Set to true if you want the Logger instance name to be included in output messages.
|
||||
# Defaults to true.
|
||||
#org.slf4j.simpleLogger.showLogName=true
|
||||
|
||||
# Set to true if you want the last component of the name to be included in output messages.
|
||||
# Defaults to false.
|
||||
#org.slf4j.simpleLogger.showShortLogName=false
|
||||
Reference in New Issue
Block a user