First Commit

This commit is contained in:
2025-02-05 15:02:37 +07:00
commit 4ec707134f
109 changed files with 68331 additions and 0 deletions

62
src/ConfigStructure.java Normal file
View 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;
}
}

View 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
View 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;
// }
}

View 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);
}
}

View 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
View 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
View 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;
}
}

View 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));
}
}

View 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
View 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;
}
}

View 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);
}
}

View 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
View 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();
}));
}
}

View 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);
}
}

View 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);
}
}

View 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;
}

View 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;
}

View 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);
}
}

View 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);
}
}

View File

@@ -0,0 +1,6 @@
package Web;
public class LoginStructure {
public String username;
public String password;
}

View 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);
}
}

View 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);
}
}

View 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);
}
}

View File

@@ -0,0 +1,8 @@
package Web;
public class ReplyExportXLSXStructure {
public String tablename;
public String filename;
public int size;
public String result;
}

View 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);
}
}

View 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);
}
}

View File

@@ -0,0 +1,7 @@
package Web;
public class ReplyLoginStructure {
public String command;
public LoginStructure value;
public String result;
}

View 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
View 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);
}
}

View File

@@ -0,0 +1,7 @@
package Web;
public class SocketIOConnections {
public String id;
public String remoteAddress;
public String role;
}

View 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
View 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();
}
}

View 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
View 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);
}
}
}

View File

@@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: MiniFIS

View 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;
}
}

View 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
}
*/
}

View 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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;
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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>

View 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">-->
<!-- &lt;!&ndash; tablename to create, edit, delete, get data &ndash;&gt;-->
<!-- <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">-->
<!-- &lt;!&ndash; untuk keperluan create table dengan nama tablename &ndash;&gt;-->
<!-- <button id="createtable" class="btn btn-primary class100">-->
<!-- Create FIS Table <i class="fa-solid fa-folder-plus"></i></button>-->
<!-- </div>-->
<!-- </div>-->
<!-- <br>-->
<!-- <br>-->
<!-- &lt;!&ndash; Table to display the fis data &ndash;&gt;-->
<!-- <div class="row">-->
<!-- <div class="col">-->
<!-- <h4 id="tabletitle">All Flight</h4>-->
<!-- </div>-->
<!-- <div class="col">-->
<!-- &lt;!&ndash; untuk keperluan insert data ke tablename &ndash;&gt;-->
<!-- <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> &nbsp 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> &nbsp 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> &nbsp 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> &nbsp 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> &nbsp 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> &nbsp 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 664 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View 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>

View 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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

10716
src/resources/public/js/jquery-3.7.1.js vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View 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>

View 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

File diff suppressed because one or more lines are too long

View 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;
}

View 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