mirror of
https://github.com/tiennm99/BSK-All-in-One-Clinic-Management-System.git
synced 2026-10-11 03:12:53 +00:00
sync
This commit is contained in:
1 parent
9b498ac9e6
commit
a583b133bc
14 files changed
+403
-269
No files matched your search
@@ -49,7 +49,6 @@ public class ClientHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) {
|
||||
log.debug("Received message: {}", frame.text());
|
||||
var packet = PacketSerializer.GSON.fromJson(frame.text(), Packet.class);
|
||||
|
||||
if (packet != null) {
|
||||
|
||||
@@ -124,26 +124,11 @@ public class DashboardPage extends JPanel {
|
||||
loadDashboardData();
|
||||
startRealTimeClock();
|
||||
|
||||
addAncestorListener(new AncestorListener() {
|
||||
@Override
|
||||
public void ancestorAdded(AncestorEvent event) {
|
||||
ClientHandler.addResponseListener(GetCheckUpQueueUpdateResponse.class, checkUpQueueUpdateListener);
|
||||
ClientHandler.addResponseListener(TodayPatientCountResponse.class, todayPatientCountListener);
|
||||
ClientHandler.addResponseListener(RecheckCountResponse.class, recheckCountListener);
|
||||
log.info("DashboardPage listeners registered. Requesting initial data.");
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetCheckUpQueueUpdateRequest());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorRemoved(AncestorEvent event) {
|
||||
ClientHandler.deleteListener(GetCheckUpQueueUpdateResponse.class, checkUpQueueUpdateListener);
|
||||
ClientHandler.deleteListener(TodayPatientCountResponse.class, todayPatientCountListener);
|
||||
log.info("DashboardPage listeners removed.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void ancestorMoved(AncestorEvent event) { /* Do nothing */ }
|
||||
});
|
||||
ClientHandler.addResponseListener(GetCheckUpQueueUpdateResponse.class, checkUpQueueUpdateListener);
|
||||
ClientHandler.addResponseListener(TodayPatientCountResponse.class, todayPatientCountListener);
|
||||
ClientHandler.addResponseListener(RecheckCountResponse.class, recheckCountListener);
|
||||
log.info("DashboardPage listeners registered. Requesting initial data.");
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), new GetCheckUpQueueUpdateRequest());
|
||||
}
|
||||
|
||||
private void startRealTimeClock() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import BsK.common.packet.req.GetMedInfoRequest;
|
||||
import BsK.common.packet.res.GetMedInfoResponse;
|
||||
import BsK.common.util.network.NetworkUtil;
|
||||
import BsK.common.util.text.TextUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
@@ -24,6 +25,7 @@ import java.util.List;
|
||||
* A JPanel dedicated to managing medicine inventory (CRUD operations).
|
||||
* It handles its own UI, data fetching, and event handling.
|
||||
*/
|
||||
@Slf4j
|
||||
public class MedicineManagementPanel extends JPanel {
|
||||
|
||||
// --- UI Components ---
|
||||
@@ -348,15 +350,15 @@ public class MedicineManagementPanel extends JPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. Construct the special 'preferedNote' string
|
||||
String preferedNote = morningSpinner.getValue().toString() + ","
|
||||
// 4. Construct the special 'preferredNote' string
|
||||
String preferredNote = morningSpinner.getValue().toString() + ","
|
||||
+ noonSpinner.getValue().toString() + ","
|
||||
+ eveningSpinner.getValue().toString() + ","
|
||||
+ noteField.getText().trim();
|
||||
|
||||
// 5. Create the request packet and send it to the server
|
||||
// IMPORTANT: Assumes your AddMedicineRequest packet is updated to include 'supplement'
|
||||
AddMedicineRequest request = new AddMedicineRequest(name, company, description, unit, price, preferedNote, isSupplement, isDeleted);
|
||||
AddMedicineRequest request = new AddMedicineRequest(name, company, description, unit, price, preferredNote, isSupplement, isDeleted);
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request);
|
||||
|
||||
// 6. Provide feedback and reset the form
|
||||
@@ -410,15 +412,15 @@ public class MedicineManagementPanel extends JPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Construct the 'preferedNote' string
|
||||
String preferedNote = morningSpinner.getValue().toString() + ","
|
||||
// 5. Construct the 'preferredNote' string
|
||||
String preferredNote = morningSpinner.getValue().toString() + ","
|
||||
+ noonSpinner.getValue().toString() + ","
|
||||
+ eveningSpinner.getValue().toString() + ","
|
||||
+ noteField.getText().trim();
|
||||
|
||||
// 6. Create the request packet and send it
|
||||
// IMPORTANT: Assumes your EditMedicineRequest packet is updated to include 'supplement'
|
||||
EditMedicineRequest request = new EditMedicineRequest(id, name, company, description, unit, price, preferedNote, isSupplement, isDeleted);
|
||||
EditMedicineRequest request = new EditMedicineRequest(id, name, company, description, unit, price, preferredNote, isSupplement, isDeleted);
|
||||
NetworkUtil.sendPacket(ClientHandler.ctx.channel(), request);
|
||||
|
||||
// 7. Provide user feedback and clear the form
|
||||
@@ -441,7 +443,8 @@ public class MedicineManagementPanel extends JPanel {
|
||||
TitledBorder.LEFT, TitledBorder.TOP, new Font("Arial", Font.BOLD, 14)));
|
||||
|
||||
// --- Table Setup ---
|
||||
String[] medicineColumns = {"ID", "Tên thuốc", "Đơn vị", "Giá", "Bổ sung","Chú thích", "Trạng thái"};
|
||||
// --- BUG FIX: Corrected column names to match the data being added ---
|
||||
String[] medicineColumns = {"ID", "Tên thuốc", "Bổ sung", "Trạng thái"};
|
||||
medicineTableModel = new DefaultTableModel(medicineColumns, 0) {
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) { return false; }
|
||||
@@ -511,6 +514,32 @@ public class MedicineManagementPanel extends JPanel {
|
||||
chkIsSupplement.setSelected("1".equals(med.getSupplement()));
|
||||
chkIsDeleted.setSelected("1".equals(med.getDeleted()));
|
||||
|
||||
// --- PARSE PREFERRED NOTE ---
|
||||
String preferredNote = med.getPreferredNote();
|
||||
if (preferredNote != null && !preferredNote.trim().isEmpty()) {
|
||||
String[] parts = preferredNote.split(",", 4);
|
||||
try {
|
||||
morningSpinner.setValue(parts.length > 0 ? Integer.parseInt(parts[0].trim()) : 0);
|
||||
noonSpinner.setValue(parts.length > 1 ? Integer.parseInt(parts[1].trim()) : 0);
|
||||
eveningSpinner.setValue(parts.length > 2 ? Integer.parseInt(parts[2].trim()) : 0);
|
||||
noteField.setText(parts.length > 3 ? parts[3].trim() : "");
|
||||
} catch (NumberFormatException e) {
|
||||
log.error("Could not parse preferred note: {}", preferredNote, e);
|
||||
// Reset to default values in case of parsing error
|
||||
morningSpinner.setValue(0);
|
||||
noonSpinner.setValue(0);
|
||||
eveningSpinner.setValue(0);
|
||||
noteField.setText("");
|
||||
}
|
||||
} else {
|
||||
// No preferred note, set default values
|
||||
morningSpinner.setValue(0);
|
||||
noonSpinner.setValue(0);
|
||||
eveningSpinner.setValue(0);
|
||||
noteField.setText("");
|
||||
}
|
||||
// --- END PARSING ---
|
||||
|
||||
// Update button states
|
||||
btnAdd.setEnabled(false);
|
||||
btnEdit.setEnabled(true);
|
||||
@@ -525,6 +554,14 @@ public class MedicineManagementPanel extends JPanel {
|
||||
medicineDescriptionField.setText("");
|
||||
medicineUnitField.setText("");
|
||||
medicinePriceField.setText("");
|
||||
|
||||
// --- ADDED RESET LOGIC ---
|
||||
morningSpinner.setValue(0);
|
||||
noonSpinner.setValue(0);
|
||||
eveningSpinner.setValue(0);
|
||||
noteField.setText("");
|
||||
// --- END ADD ---
|
||||
|
||||
chkIsSupplement.setSelected(false);
|
||||
chkIsDeleted.setSelected(false);
|
||||
medicineTable.clearSelection();
|
||||
@@ -555,10 +592,7 @@ public class MedicineManagementPanel extends JPanel {
|
||||
medicineTableModel.addRow(new Object[]{
|
||||
med.getId(),
|
||||
med.getName(),
|
||||
med.getUnit(),
|
||||
med.getSellingPrice(),
|
||||
supplementStatus,
|
||||
med.getPreferredNote(),
|
||||
deletedStatus
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
package BsK.client.ui.component.common.MedicineDialog;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
|
||||
import BsK.client.network.handler.ClientHandler;
|
||||
import BsK.client.network.handler.ResponseListener;
|
||||
import BsK.common.entity.Medicine;
|
||||
@@ -17,25 +11,25 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.TableColumnModel;
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyAdapter;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.*;
|
||||
import java.text.NumberFormat; // --- ADD THIS IMPORT ---
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.awt.event.ActionEvent;
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.KeyStroke;
|
||||
import java.util.Locale; // --- ADD THIS IMPORT ---
|
||||
|
||||
@Slf4j
|
||||
public class MedicineDialog extends JDialog {
|
||||
private JTextField medicineNameField;
|
||||
private JTextField medicineCompanyField;
|
||||
private JTextArea medicineDescriptionField;
|
||||
private JCheckBox chkIsSupplement; // The new checkbox
|
||||
private JSpinner quantitySpinner;
|
||||
private JComboBox<String> UnitComboBox;
|
||||
private JTextField priceField;
|
||||
@@ -69,10 +63,10 @@ public class MedicineDialog extends JDialog {
|
||||
void getMedInfoHandler(GetMedInfoResponse response) {
|
||||
logger.info("Received medicine data");
|
||||
medicineData = response.getMedInfo();
|
||||
|
||||
|
||||
// Get Medicine objects from the response
|
||||
medicines = response.getMedicines();
|
||||
|
||||
|
||||
// We don't need to populate a main table anymore, just have the data ready.
|
||||
}
|
||||
|
||||
@@ -87,7 +81,7 @@ public class MedicineDialog extends JDialog {
|
||||
this.originalPrescription = new String[0][0];
|
||||
init(parent);
|
||||
}
|
||||
|
||||
|
||||
public MedicineDialog(final Frame parent, String[][] existingPrescription) {
|
||||
super(parent, "Đơn thuốc", true);
|
||||
this.medicinePrescription = existingPrescription != null ? existingPrescription : new String[0][0];
|
||||
@@ -105,17 +99,17 @@ public class MedicineDialog extends JDialog {
|
||||
setSize(1200, 700);
|
||||
setLocationRelativeTo(parent);
|
||||
setResizable(true);
|
||||
|
||||
|
||||
// UI Fonts and Dimensions
|
||||
Font labelFont = new Font("Arial", Font.BOLD, 15);
|
||||
Font textFont = new Font("Arial", Font.PLAIN, 15);
|
||||
Font titleFont = new Font("Arial", Font.BOLD, 16);
|
||||
Dimension textFieldSize = new Dimension(100, 30);
|
||||
|
||||
|
||||
// Ensure modal behavior and proper parent relationship
|
||||
setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
|
||||
setAlwaysOnTop(false); // Don't force always on top, let modal behavior handle this
|
||||
|
||||
|
||||
// Add window listener to handle minimize/restore events with parent
|
||||
addWindowListener(new WindowAdapter() {
|
||||
@Override
|
||||
@@ -124,7 +118,7 @@ public class MedicineDialog extends JDialog {
|
||||
parent.setState(Frame.ICONIFIED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void windowDeiconified(WindowEvent e) {
|
||||
if (parent != null && parent.getState() == Frame.ICONIFIED) {
|
||||
@@ -263,6 +257,16 @@ public class MedicineDialog extends JDialog {
|
||||
descriptionScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
|
||||
medicineInfoPanel.add(descriptionScrollPane, gbc);
|
||||
gbc.weightx = 0.0;
|
||||
|
||||
gbc.gridy++;
|
||||
gbc.gridx = 0;
|
||||
gbc.gridwidth = 2;
|
||||
chkIsSupplement = new JCheckBox("Thực phẩm bổ sung");
|
||||
chkIsSupplement.setFont(labelFont);
|
||||
chkIsSupplement.setEnabled(false);
|
||||
chkIsSupplement.setOpaque(false);
|
||||
medicineInfoPanel.add(chkIsSupplement, gbc);
|
||||
gbc.gridwidth = 1;
|
||||
|
||||
gbc.gridy = 0;
|
||||
gbc.gridx = 0;
|
||||
@@ -723,18 +727,21 @@ public class MedicineDialog extends JDialog {
|
||||
UnitComboBox.setSelectedItem(medicine.getUnit());
|
||||
priceField.setText(medicine.getSellingPrice());
|
||||
|
||||
// Update the new checkbox
|
||||
chkIsSupplement.setSelected("1".equals(medicine.getSupplement()));
|
||||
|
||||
quantitySpinner.setValue(1);
|
||||
|
||||
String preferenceNote = medicine.getPreferredNote();
|
||||
if (preferenceNote != null && !preferenceNote.trim().isEmpty()) {
|
||||
String[] parts = preferenceNote.split(",", 4);
|
||||
String preferredNote = medicine.getPreferredNote();
|
||||
if (preferredNote != null && !preferredNote.trim().isEmpty()) {
|
||||
String[] parts = preferredNote.split(",", 4);
|
||||
try {
|
||||
morningSpinner.setValue(parts.length > 0 ? Integer.parseInt(parts[0].trim()) : 0);
|
||||
noonSpinner.setValue(parts.length > 1 ? Integer.parseInt(parts[1].trim()) : 0);
|
||||
eveningSpinner.setValue(parts.length > 2 ? Integer.parseInt(parts[2].trim()) : 0);
|
||||
noteField.setText(parts.length > 3 ? parts[3].trim() : "");
|
||||
} catch (NumberFormatException e) {
|
||||
logger.error("Could not parse preference note: {}", preferenceNote, e);
|
||||
logger.error("Could not parse preferred note: {}", preferredNote, e);
|
||||
// Reset to default values in case of parsing error
|
||||
morningSpinner.setValue(0);
|
||||
noonSpinner.setValue(0);
|
||||
@@ -742,7 +749,7 @@ public class MedicineDialog extends JDialog {
|
||||
noteField.setText("");
|
||||
}
|
||||
} else {
|
||||
// No preference note, set default values
|
||||
// No preferred note, set default values
|
||||
morningSpinner.setValue(0);
|
||||
noonSpinner.setValue(0);
|
||||
eveningSpinner.setValue(0);
|
||||
@@ -760,6 +767,7 @@ public class MedicineDialog extends JDialog {
|
||||
UnitComboBox.setSelectedIndex(0);
|
||||
priceField.setText("");
|
||||
totalField.setText("");
|
||||
chkIsSupplement.setSelected(false); // Reset the checkbox
|
||||
quantitySpinner.setValue(1);
|
||||
morningSpinner.setValue(0);
|
||||
noonSpinner.setValue(0);
|
||||
@@ -770,9 +778,19 @@ public class MedicineDialog extends JDialog {
|
||||
private void updateTotalField() {
|
||||
try {
|
||||
int quantity = (Integer) quantitySpinner.getValue();
|
||||
if (quantity < 0) { quantitySpinner.setValue(0); quantity = 0; }
|
||||
if (quantity < 0) {
|
||||
quantitySpinner.setValue(0);
|
||||
quantity = 0;
|
||||
}
|
||||
double price = priceField.getText().isEmpty() ? 0 : Double.parseDouble(priceField.getText());
|
||||
totalField.setText(String.format("%.0f", quantity * price));
|
||||
double totalValue = quantity * price;
|
||||
|
||||
// Create formatter for Vietnamese locale
|
||||
NumberFormat formatter = NumberFormat.getInstance(new Locale("vi", "VN"));
|
||||
|
||||
// Format the total and set the text
|
||||
totalField.setText(formatter.format(totalValue));
|
||||
|
||||
} catch (NumberFormatException e) {
|
||||
totalField.setText("Giá trị không hợp lệ");
|
||||
logger.error("Error parsing price for total calculation: " + priceField.getText(), e);
|
||||
|
||||
@@ -12,7 +12,7 @@ public class AddMedicineRequest implements Packet{
|
||||
private String description;
|
||||
private String unit;
|
||||
private Double price;
|
||||
private String preferedNote;
|
||||
private String preferredNote;
|
||||
private Boolean supplement;
|
||||
private Boolean deleted;
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class EditMedicineRequest implements Packet {
|
||||
private String description;
|
||||
private String unit;
|
||||
private Double price;
|
||||
private String preferedNote;
|
||||
private String preferredNote;
|
||||
private Boolean supplement;
|
||||
private Boolean deleted;
|
||||
}
|
||||
@@ -330,5 +330,31 @@ public class Server {
|
||||
parentGroup.shutdownGracefully();
|
||||
childGroup.shutdownGracefully();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the backup of the main database file to Google Drive.
|
||||
* This is intended to be called from the ServerDashboard.
|
||||
* @throws IOException if the file cannot be found or the upload fails.
|
||||
*/
|
||||
public static void backupDatabaseToDrive() throws IOException {
|
||||
ServerDashboard dashboard = ServerDashboard.getInstance();
|
||||
|
||||
if (!isGoogleDriveConnected() || googleDriveService == null) {
|
||||
dashboard.addLog("❌ Cannot backup database: Google Drive is not connected.");
|
||||
throw new IOException("Google Drive service is not available.");
|
||||
}
|
||||
|
||||
// Define the path to the database
|
||||
String dbPath = "src/main/resources/database/BSK.db";
|
||||
java.io.File dbFile = new java.io.File(dbPath);
|
||||
|
||||
if (!dbFile.exists()) {
|
||||
dashboard.addLog("❌ Cannot backup database: File not found at " + dbPath);
|
||||
throw new IOException("Database file not found: " + dbPath);
|
||||
}
|
||||
|
||||
// Call the service to perform the backup
|
||||
googleDriveService.backupDatabaseFile(dbFile);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
package BsK.server;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.TitledBorder;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.text.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Vector;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryMXBean;
|
||||
import java.lang.management.OperatingSystemMXBean;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Vector;
|
||||
import BsK.server.network.entity.ClientConnection;
|
||||
import BsK.server.network.manager.SessionManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -36,6 +36,9 @@ public class ServerDashboard extends JFrame {
|
||||
private JTable networkTable;
|
||||
private DefaultTableModel networkTableModel;
|
||||
|
||||
// --- NEW ---
|
||||
private JButton backupDbButton;
|
||||
|
||||
public static ServerDashboard getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new ServerDashboard();
|
||||
@@ -49,97 +52,158 @@ public class ServerDashboard extends JFrame {
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setLocationRelativeTo(null);
|
||||
|
||||
// Create main panel with border layout
|
||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
// Main container panel
|
||||
JPanel mainPanel = new JPanel(new BorderLayout(10, 10));
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
|
||||
// Status Panel (North)
|
||||
JPanel topPanel = new JPanel(new GridLayout(3, 1, 0, 5));
|
||||
|
||||
// Server Status Panel
|
||||
JPanel statusPanel = new JPanel(new GridLayout(1, 4, 10, 0));
|
||||
statusPanel.setBorder(BorderFactory.createTitledBorder("Server Status"));
|
||||
// Add main components
|
||||
mainPanel.add(createHeaderPanel(), BorderLayout.NORTH);
|
||||
mainPanel.add(createMainContentPanel(), BorderLayout.CENTER);
|
||||
|
||||
add(mainPanel);
|
||||
|
||||
// Start timers
|
||||
startSystemStatsTimer();
|
||||
startNetworkStatsTimer();
|
||||
}
|
||||
|
||||
private JPanel createHeaderPanel() {
|
||||
JPanel headerPanel = new JPanel(new GridBagLayout());
|
||||
GridBagConstraints gbc = new GridBagConstraints();
|
||||
gbc.fill = GridBagConstraints.BOTH;
|
||||
gbc.insets = new Insets(0, 5, 0, 5);
|
||||
gbc.weighty = 1.0;
|
||||
|
||||
// Server Status Panel
|
||||
JPanel statusPanel = new JPanel(new GridLayout(1, 3, 10, 0));
|
||||
statusPanel.setBorder(BorderFactory.createTitledBorder("Server Status"));
|
||||
statusLabel = new JLabel("Status: Starting...", SwingConstants.CENTER);
|
||||
portLabel = new JLabel("Port: --", SwingConstants.CENTER);
|
||||
clientsLabel = new JLabel("Connected Clients: 0", SwingConstants.CENTER);
|
||||
googleDriveLabel = new JLabel("Google Drive: Checking...", SwingConstants.CENTER);
|
||||
|
||||
statusPanel.add(statusLabel);
|
||||
statusPanel.add(portLabel);
|
||||
statusPanel.add(clientsLabel);
|
||||
statusPanel.add(googleDriveLabel);
|
||||
gbc.gridx = 0;
|
||||
gbc.weightx = 0.35; // MODIFIED: Adjusted weight
|
||||
headerPanel.add(statusPanel, gbc);
|
||||
|
||||
// System Info Panel
|
||||
JPanel systemPanel = new JPanel(new GridLayout(1, 4, 10, 0));
|
||||
JPanel systemPanel = new JPanel(new GridLayout(1, 3, 10, 0));
|
||||
systemPanel.setBorder(BorderFactory.createTitledBorder("System Information"));
|
||||
|
||||
memoryLabel = new JLabel("Memory Usage: --", SwingConstants.CENTER);
|
||||
cpuLabel = new JLabel("CPU Usage: --", SwingConstants.CENTER);
|
||||
JLabel osLabel = new JLabel("OS: " + System.getProperty("os.name"), SwingConstants.CENTER);
|
||||
|
||||
// Google Drive settings button
|
||||
googleDriveButton = new JButton("Retry Drive");
|
||||
googleDriveButton.addActionListener(e -> retryGoogleDriveConnection());
|
||||
googleDriveButton.setToolTipText("Retry Google Drive connection");
|
||||
|
||||
systemPanel.add(memoryLabel);
|
||||
systemPanel.add(cpuLabel);
|
||||
systemPanel.add(osLabel);
|
||||
systemPanel.add(googleDriveButton);
|
||||
gbc.gridx = 1;
|
||||
gbc.weightx = 0.35; // MODIFIED: Adjusted weight
|
||||
headerPanel.add(systemPanel, gbc);
|
||||
|
||||
// Google Drive Settings Panel
|
||||
JPanel driveSettingsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
driveSettingsPanel.setBorder(BorderFactory.createTitledBorder("Google Drive Settings"));
|
||||
// Google Drive Panel
|
||||
JPanel drivePanel = new JPanel(new BorderLayout(10, 0));
|
||||
drivePanel.setBorder(BorderFactory.createTitledBorder("Google Drive"));
|
||||
googleDriveLabel = new JLabel("Status: Checking...", SwingConstants.CENTER);
|
||||
googleDriveButton = new JButton("Retry");
|
||||
googleDriveButton.addActionListener(e -> retryGoogleDriveConnection());
|
||||
drivePanel.add(googleDriveLabel, BorderLayout.CENTER);
|
||||
drivePanel.add(googleDriveButton, BorderLayout.EAST);
|
||||
gbc.gridx = 2;
|
||||
gbc.weightx = 0.2; // MODIFIED: Adjusted weight
|
||||
headerPanel.add(drivePanel, gbc);
|
||||
|
||||
JLabel folderNameLabel = new JLabel("Root Folder Name:");
|
||||
JTextField driveRootFolderField = new JTextField(getCurrentDriveRootFolder(), 20);
|
||||
JButton applyDriveSettingsButton = new JButton("Apply");
|
||||
applyDriveSettingsButton.addActionListener(e -> applyDriveSettings(driveRootFolderField.getText().trim()));
|
||||
|
||||
driveSettingsPanel.add(folderNameLabel);
|
||||
driveSettingsPanel.add(driveRootFolderField);
|
||||
driveSettingsPanel.add(applyDriveSettingsButton);
|
||||
// --- NEW: Actions Panel ---
|
||||
JPanel actionsPanel = new JPanel(new BorderLayout());
|
||||
actionsPanel.setBorder(BorderFactory.createTitledBorder("Actions"));
|
||||
backupDbButton = new JButton("Backup DB to Drive");
|
||||
backupDbButton.setToolTipText("Upload a timestamped copy of the database to Google Drive.");
|
||||
backupDbButton.addActionListener(e -> performDatabaseBackup());
|
||||
actionsPanel.add(backupDbButton, BorderLayout.CENTER);
|
||||
gbc.gridx = 3;
|
||||
gbc.weightx = 0.1; // MODIFIED: Adjusted weight
|
||||
headerPanel.add(actionsPanel, gbc);
|
||||
|
||||
topPanel.add(statusPanel);
|
||||
topPanel.add(systemPanel);
|
||||
topPanel.add(driveSettingsPanel);
|
||||
|
||||
// Create split pane for logs and network info
|
||||
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
|
||||
splitPane.setResizeWeight(0.1); // Give more space to the top component
|
||||
return headerPanel;
|
||||
}
|
||||
|
||||
// --- NEW: Method to handle the backup action ---
|
||||
private void performDatabaseBackup() {
|
||||
// Disable button to prevent multiple clicks
|
||||
backupDbButton.setEnabled(false);
|
||||
addLog("▶️ Starting database backup to Google Drive...");
|
||||
|
||||
// Network Information Panel
|
||||
// Run the backup in a background thread to not freeze the UI
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Call the static method in the Server class
|
||||
BsK.server.Server.backupDatabaseToDrive();
|
||||
|
||||
// Update UI on success
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
addLog("✅ Database backup completed successfully.");
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Database backup was successfully uploaded to Google Drive.",
|
||||
"Backup Successful",
|
||||
JOptionPane.INFORMATION_MESSAGE);
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Database backup failed", e);
|
||||
// Update UI on failure
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
addLog("❌ Database backup failed: " + e.getMessage());
|
||||
JOptionPane.showMessageDialog(this,
|
||||
"Failed to backup the database.\nError: " + e.getMessage() + "\n\nCheck logs for more details.",
|
||||
"Backup Failed",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
});
|
||||
} finally {
|
||||
// Always re-enable the button on the UI thread
|
||||
SwingUtilities.invokeLater(() -> backupDbButton.setEnabled(true));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private JSplitPane createMainContentPanel() {
|
||||
// ... (This method remains unchanged)
|
||||
// --- Log Panel ---
|
||||
JPanel logPanel = new JPanel(new BorderLayout());
|
||||
logPanel.setBorder(BorderFactory.createTitledBorder("Server Logs"));
|
||||
logArea = new JTextPane();
|
||||
logArea.setEditable(false);
|
||||
logArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
|
||||
JScrollPane logScrollPane = new JScrollPane(logArea);
|
||||
logPanel.add(createLogControlsPanel(), BorderLayout.NORTH);
|
||||
logPanel.add(logScrollPane, BorderLayout.CENTER);
|
||||
|
||||
// --- Network Panel ---
|
||||
JPanel networkPanel = new JPanel(new BorderLayout());
|
||||
networkPanel.setBorder(BorderFactory.createTitledBorder("Network Information"));
|
||||
|
||||
// Create table model with columns
|
||||
String[] columnNames = {"Session ID", "IP Address", "Port", "Role", "Connected Time", "Last Activity", "Bytes Sent", "Bytes Received"};
|
||||
String[] columnNames = {"Session ID", "IP Address", "Port", "Role", "Connected Time", "Last Activity"};
|
||||
networkTableModel = new DefaultTableModel(columnNames, 0) {
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
return false;
|
||||
}
|
||||
public boolean isCellEditable(int row, int column) { return false; }
|
||||
};
|
||||
|
||||
networkTable = new JTable(networkTableModel);
|
||||
networkTable.setFillsViewportHeight(true);
|
||||
JScrollPane networkScrollPane = new JScrollPane(networkTable);
|
||||
networkPanel.add(networkScrollPane, BorderLayout.CENTER);
|
||||
networkPanel.add(new JScrollPane(networkTable), BorderLayout.CENTER);
|
||||
|
||||
// Log Controls Panel
|
||||
JPanel logControlsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
// --- Split Pane ---
|
||||
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, logPanel, networkPanel);
|
||||
splitPane.setResizeWeight(0.80); // Give 80% of space to the logs
|
||||
|
||||
// Search field
|
||||
return splitPane;
|
||||
}
|
||||
|
||||
private JPanel createLogControlsPanel() {
|
||||
// ... (This method remains unchanged)
|
||||
JPanel logControlsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
|
||||
searchField = new JTextField(20);
|
||||
JButton searchButton = new JButton("Search");
|
||||
searchButton.addActionListener(e -> searchInLogs());
|
||||
|
||||
// Clear logs button
|
||||
JButton clearButton = new JButton("Clear Logs");
|
||||
clearButton.addActionListener(e -> clearLogs());
|
||||
|
||||
// Auto-scroll toggle
|
||||
autoScrollToggle = new JToggleButton("Auto-scroll", true);
|
||||
autoScrollToggle.addActionListener(e -> isAutoScrollEnabled = autoScrollToggle.isSelected());
|
||||
|
||||
@@ -150,52 +214,25 @@ public class ServerDashboard extends JFrame {
|
||||
logControlsPanel.add(clearButton);
|
||||
logControlsPanel.add(Box.createHorizontalStrut(20));
|
||||
logControlsPanel.add(autoScrollToggle);
|
||||
|
||||
// Log Area Panel
|
||||
JPanel logPanel = new JPanel(new BorderLayout());
|
||||
logPanel.setBorder(BorderFactory.createTitledBorder("Server Logs"));
|
||||
|
||||
logArea = new JTextPane();
|
||||
logArea.setEditable(false);
|
||||
logArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
|
||||
JScrollPane logScrollPane = new JScrollPane(logArea);
|
||||
|
||||
logPanel.add(logControlsPanel, BorderLayout.NORTH);
|
||||
logPanel.add(logScrollPane, BorderLayout.CENTER);
|
||||
|
||||
// Add components to split pane
|
||||
splitPane.setTopComponent(logPanel);
|
||||
splitPane.setBottomComponent(networkPanel);
|
||||
|
||||
// Add components to main panel
|
||||
mainPanel.add(topPanel, BorderLayout.NORTH);
|
||||
mainPanel.add(splitPane, BorderLayout.CENTER);
|
||||
|
||||
add(mainPanel);
|
||||
|
||||
// Start system stats update timer
|
||||
startSystemStatsTimer();
|
||||
// Start network stats update timer
|
||||
startNetworkStatsTimer();
|
||||
return logControlsPanel;
|
||||
}
|
||||
|
||||
private void startSystemStatsTimer() {
|
||||
// ... (This method remains unchanged)
|
||||
MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
|
||||
OperatingSystemMXBean osBean = ManagementFactory.getOperatingSystemMXBean();
|
||||
|
||||
statsTimer = new Timer(2000, e -> {
|
||||
// Update memory usage
|
||||
long usedMemory = memoryBean.getHeapMemoryUsage().getUsed() / (1024 * 1024);
|
||||
long maxMemory = memoryBean.getHeapMemoryUsage().getMax() / (1024 * 1024);
|
||||
SwingUtilities.invokeLater(() ->
|
||||
memoryLabel.setText(String.format("Memory Usage: %d MB / %d MB", usedMemory, maxMemory))
|
||||
SwingUtilities.invokeLater(() ->
|
||||
memoryLabel.setText(String.format("Memory: %d MB / %d MB", usedMemory, maxMemory))
|
||||
);
|
||||
|
||||
// Update CPU usage if available
|
||||
if (osBean instanceof com.sun.management.OperatingSystemMXBean) {
|
||||
double cpuLoad = ((com.sun.management.OperatingSystemMXBean) osBean).getSystemCpuLoad() * 100;
|
||||
SwingUtilities.invokeLater(() ->
|
||||
cpuLabel.setText(String.format("CPU Usage: %.1f%%", cpuLoad))
|
||||
SwingUtilities.invokeLater(() ->
|
||||
cpuLabel.setText(String.format("CPU: %.1f%%", cpuLoad))
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -203,16 +240,15 @@ public class ServerDashboard extends JFrame {
|
||||
}
|
||||
|
||||
private void startNetworkStatsTimer() {
|
||||
// ... (This method remains unchanged)
|
||||
networkStatsTimer = new Timer(1000, e -> refreshNetworkTable());
|
||||
networkStatsTimer.start();
|
||||
}
|
||||
|
||||
public void refreshNetworkTable() {
|
||||
// ... (This method remains unchanged)
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
// Clear existing rows
|
||||
networkTableModel.setRowCount(0);
|
||||
|
||||
// Add current connections
|
||||
for (ClientConnection conn : SessionManager.getAllConnections()) {
|
||||
Vector<Object> row = new Vector<>();
|
||||
row.add(conn.getSessionId());
|
||||
@@ -221,21 +257,13 @@ public class ServerDashboard extends JFrame {
|
||||
row.add(conn.getUserRole());
|
||||
row.add(conn.getConnectionDuration());
|
||||
row.add(conn.getLastActivityDuration());
|
||||
row.add(formatBytes(conn.getBytesSent()));
|
||||
row.add(formatBytes(conn.getBytesReceived()));
|
||||
networkTableModel.addRow(row);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String formatBytes(long bytes) {
|
||||
if (bytes < 1024) return bytes + " B";
|
||||
if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
|
||||
if (bytes < 1024 * 1024 * 1024) return String.format("%.1f MB", bytes / (1024.0 * 1024));
|
||||
return String.format("%.1f GB", bytes / (1024.0 * 1024 * 1024));
|
||||
}
|
||||
|
||||
private void searchInLogs() {
|
||||
// ... (This method remains unchanged)
|
||||
String searchText = searchField.getText().toLowerCase();
|
||||
if (searchText.isEmpty()) {
|
||||
return;
|
||||
@@ -248,9 +276,9 @@ public class ServerDashboard extends JFrame {
|
||||
int index = content.toLowerCase().indexOf(searchText);
|
||||
while (index >= 0) {
|
||||
logArea.getHighlighter().addHighlight(
|
||||
index,
|
||||
index + searchText.length(),
|
||||
new DefaultHighlighter.DefaultHighlightPainter(Color.YELLOW)
|
||||
index,
|
||||
index + searchText.length(),
|
||||
new DefaultHighlighter.DefaultHighlightPainter(Color.YELLOW)
|
||||
);
|
||||
index = content.toLowerCase().indexOf(searchText, index + 1);
|
||||
}
|
||||
@@ -260,6 +288,7 @@ public class ServerDashboard extends JFrame {
|
||||
}
|
||||
|
||||
private void clearLogs() {
|
||||
// ... (This method remains unchanged)
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
try {
|
||||
logArea.getDocument().remove(0, logArea.getDocument().getLength());
|
||||
@@ -271,6 +300,7 @@ public class ServerDashboard extends JFrame {
|
||||
}
|
||||
|
||||
public void updateStatus(String status, Color color) {
|
||||
// ... (This method remains unchanged)
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
statusLabel.setText("Status: " + status);
|
||||
statusLabel.setForeground(color);
|
||||
@@ -278,47 +308,48 @@ public class ServerDashboard extends JFrame {
|
||||
}
|
||||
|
||||
public void updatePort(int port) {
|
||||
SwingUtilities.invokeLater(() ->
|
||||
// ... (This method remains unchanged)
|
||||
SwingUtilities.invokeLater(() ->
|
||||
portLabel.setText("Port: " + port)
|
||||
);
|
||||
}
|
||||
|
||||
public static void incrementClients() {
|
||||
// ... (This method remains unchanged)
|
||||
connectedClients++;
|
||||
updateClientCount();
|
||||
}
|
||||
|
||||
public static void decrementClients() {
|
||||
connectedClients = Math.max(0, connectedClients - 1); // Ensure we don't go below 0
|
||||
// ... (This method remains unchanged)
|
||||
connectedClients = Math.max(0, connectedClients - 1);
|
||||
updateClientCount();
|
||||
}
|
||||
|
||||
private static void updateClientCount() {
|
||||
// ... (This method remains unchanged)
|
||||
if (instance != null) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
instance.clientsLabel.setText("Connected Clients: " + connectedClients);
|
||||
// Also update the network table since client count changed
|
||||
instance.refreshNetworkTable();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void addLog(String message) {
|
||||
// ... (This method remains unchanged)
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
try {
|
||||
Document doc = logArea.getDocument();
|
||||
String timestamp = timeFormat.format(new Date());
|
||||
String formattedMessage = String.format("[%s] %s%n", timestamp, message);
|
||||
|
||||
// Create style for new text
|
||||
|
||||
Style style = logArea.addStyle("Log Style", null);
|
||||
StyleConstants.setFontFamily(style, "Monospace");
|
||||
StyleConstants.setFontSize(style, 12);
|
||||
|
||||
// Insert the text at the end
|
||||
|
||||
doc.insertString(doc.getLength(), formattedMessage, style);
|
||||
|
||||
// Scroll to bottom if auto-scroll is enabled
|
||||
|
||||
if (isAutoScrollEnabled) {
|
||||
logArea.setCaretPosition(doc.getLength());
|
||||
}
|
||||
@@ -329,96 +360,60 @@ public class ServerDashboard extends JFrame {
|
||||
}
|
||||
|
||||
public void updateGoogleDriveStatus(boolean connected, String statusMessage) {
|
||||
// ... (This method remains unchanged)
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (connected) {
|
||||
googleDriveLabel.setText("Google Drive: ✅ " + statusMessage);
|
||||
googleDriveLabel.setForeground(Color.GREEN);
|
||||
googleDriveButton.setText("Test Drive");
|
||||
googleDriveLabel.setText("✅ " + statusMessage);
|
||||
googleDriveLabel.setForeground(new Color(34, 139, 34)); // Forest Green
|
||||
googleDriveButton.setText("Test");
|
||||
googleDriveButton.setToolTipText("Test Google Drive connection");
|
||||
} else {
|
||||
googleDriveLabel.setText("Google Drive: ❌ " + statusMessage);
|
||||
googleDriveLabel.setText("❌ " + statusMessage);
|
||||
googleDriveLabel.setForeground(Color.RED);
|
||||
googleDriveButton.setText("Retry Drive");
|
||||
googleDriveButton.setText("Retry");
|
||||
googleDriveButton.setToolTipText("Retry Google Drive connection");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void retryGoogleDriveConnection() {
|
||||
// ... (This method remains unchanged)
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
googleDriveLabel.setText("Google Drive: 🔄 Connecting...");
|
||||
googleDriveLabel.setText("🔄 Connecting...");
|
||||
googleDriveLabel.setForeground(Color.ORANGE);
|
||||
googleDriveButton.setEnabled(false);
|
||||
addLog("Retrying Google Drive connection...");
|
||||
addLog("Testing Google Drive connection...");
|
||||
});
|
||||
|
||||
// Run connection attempt in background thread
|
||||
// Run connection attempt in a background thread
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// This method should attempt the connection and update the server's internal state.
|
||||
BsK.server.Server.retryGoogleDriveConnection();
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
googleDriveButton.setEnabled(true);
|
||||
if (BsK.server.Server.isGoogleDriveConnected()) {
|
||||
addLog("Google Drive connection successful ✅");
|
||||
} else {
|
||||
addLog("Google Drive connection failed ❌");
|
||||
}
|
||||
});
|
||||
|
||||
// After the attempt, get the result from the server's state
|
||||
boolean isConnected = BsK.server.Server.isGoogleDriveConnected();
|
||||
String message = isConnected ? "Connection test successful" : "Connection test failed";
|
||||
|
||||
// *** THIS IS THE FIX ***
|
||||
// Call the standard UI update method with the result.
|
||||
updateGoogleDriveStatus(isConnected, message);
|
||||
|
||||
} catch (Exception e) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
googleDriveButton.setEnabled(true);
|
||||
addLog("Google Drive connection error: " + e.getMessage());
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
log.error("Google Drive connection test failed with an exception", e);
|
||||
// Also update the UI in case of an exception
|
||||
updateGoogleDriveStatus(false, "Connection error");
|
||||
addLog("Google Drive connection error: " + e.getMessage());
|
||||
} finally {
|
||||
// ALWAYS re-enable the button on the UI thread
|
||||
SwingUtilities.invokeLater(() -> googleDriveButton.setEnabled(true));
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private String getCurrentDriveRootFolder() {
|
||||
// Get current root folder name from Server or return default
|
||||
try {
|
||||
if (BsK.server.Server.getGoogleDriveService() != null) {
|
||||
return BsK.server.Server.getGoogleDriveRootFolderName();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Could not get current drive root folder name", e);
|
||||
}
|
||||
return "BSK_Clinic_Patient_Files"; // Default value
|
||||
}
|
||||
|
||||
private void applyDriveSettings(String newRootFolderName) {
|
||||
if (newRootFolderName == null || newRootFolderName.trim().isEmpty()) {
|
||||
addLog("❌ Root folder name cannot be empty");
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanitize folder name
|
||||
String sanitizedName = newRootFolderName.replaceAll("[^a-zA-Z0-9._-]", "_");
|
||||
if (!sanitizedName.equals(newRootFolderName)) {
|
||||
addLog("⚠️ Folder name sanitized to: " + sanitizedName);
|
||||
}
|
||||
|
||||
addLog("🔄 Updating Google Drive root folder to: " + sanitizedName);
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
// Update the root folder name in server
|
||||
BsK.server.Server.updateGoogleDriveRootFolder(sanitizedName);
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
addLog("✅ Google Drive root folder updated successfully");
|
||||
addLog("💡 New patient folders will be created under: " + sanitizedName);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
addLog("❌ Failed to update Google Drive root folder: " + e.getMessage());
|
||||
});
|
||||
log.error("Error updating Google Drive root folder", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
@Override
|
||||
public void dispose() {
|
||||
// ... (This method remains unchanged)
|
||||
if (statsTimer != null) {
|
||||
statsTimer.stop();
|
||||
}
|
||||
@@ -427,4 +422,4 @@ public class ServerDashboard extends JFrame {
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,4 +51,11 @@ public class User {
|
||||
public boolean isAuthenticated() {
|
||||
return role != Role.GUEST;
|
||||
}
|
||||
|
||||
public void resetAuthentication() {
|
||||
this.role = Role.GUEST;
|
||||
this.userId = -1; // Or some other indicator of not being logged in
|
||||
SessionManager.updateUserRole(channel.id().asLongText(), this.role.toString(), this.userId);
|
||||
log.info("User session {} has been de-authenticated.", sessionId);
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,6 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame frame) {
|
||||
log.debug("Received message: {}", frame.text());
|
||||
// Update last activity time for the client
|
||||
var connectedUser = SessionManager.getUserByChannel(ctx.channel().id().asLongText());
|
||||
if (connectedUser != null) {
|
||||
@@ -91,19 +90,17 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
// Tạo user trong database hoặc check exist
|
||||
boolean isUserExist = false;
|
||||
} else if (packet instanceof LogoutRequest) {
|
||||
var logoutUser = SessionManager.getUserByChannel(ctx.channel().id().asLongText());
|
||||
var logoutUser = SessionManager.getUserByChannel(ctx.channel().id().asLongText());
|
||||
if (logoutUser != null) {
|
||||
log.info("User {} (Session {}) requested logout.", logoutUser.getUserId(), logoutUser.getSessionId());
|
||||
SessionManager.onUserDisconnect(ctx.channel());
|
||||
// Optionally, you could send a LogoutResponse back to the client if needed,
|
||||
// but since the client navigates away, it might not be processed.
|
||||
logoutUser.resetAuthentication();
|
||||
} else {
|
||||
log.warn("Received LogoutRequest from a channel with no active user session: {}", ctx.channel().id().asLongText());
|
||||
}
|
||||
} else {
|
||||
// Check if user is authenticated
|
||||
var currentUser = SessionManager.getUserByChannel(ctx.channel().id().asLongText());
|
||||
if (!currentUser.isAuthenticated()) {
|
||||
if (currentUser == null || !currentUser.isAuthenticated()) {
|
||||
log.warn("Received packet from unauthenticated user: {}", packet);
|
||||
return;
|
||||
}
|
||||
@@ -1461,14 +1458,14 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
}
|
||||
log.info("Received AddMedRequest");
|
||||
try {
|
||||
String sql = "INSERT INTO Medicine (med_name, med_company, med_description, med_unit, med_selling_price, preference_note, supplement, deleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
String sql = "INSERT INTO Medicine (med_name, med_company, med_description, med_unit, med_selling_price, preferred_note, supplement, deleted) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
PreparedStatement stmt = Server.connection.prepareStatement(sql);
|
||||
stmt.setString(1, addMedicineRequest.getName());
|
||||
stmt.setString(2, addMedicineRequest.getCompany());
|
||||
stmt.setString(3, addMedicineRequest.getDescription());
|
||||
stmt.setString(4, addMedicineRequest.getUnit());
|
||||
stmt.setDouble(5, addMedicineRequest.getPrice());
|
||||
stmt.setString(6, addMedicineRequest.getPreferedNote());
|
||||
stmt.setString(6, addMedicineRequest.getPreferredNote());
|
||||
stmt.setBoolean(7, addMedicineRequest.getSupplement());
|
||||
stmt.setBoolean(8, addMedicineRequest.getDeleted());
|
||||
stmt.executeUpdate();
|
||||
@@ -1486,14 +1483,14 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
}
|
||||
log.info("Received EditMedRequest");
|
||||
try {
|
||||
String sql = "UPDATE Medicine SET med_name = ?, med_company = ?, med_description = ?, med_unit = ?, med_selling_price = ?, preference_note = ?, supplement = ?, deleted = ? WHERE med_id = ?";
|
||||
String sql = "UPDATE Medicine SET med_name = ?, med_company = ?, med_description = ?, med_unit = ?, med_selling_price = ?, preferred_note = ?, supplement = ?, deleted = ? WHERE med_id = ?";
|
||||
PreparedStatement stmt = Server.connection.prepareStatement(sql);
|
||||
stmt.setString(1, editMedicineRequest.getName());
|
||||
stmt.setString(2, editMedicineRequest.getCompany());
|
||||
stmt.setString(3, editMedicineRequest.getDescription());
|
||||
stmt.setString(4, editMedicineRequest.getUnit());
|
||||
stmt.setDouble(5, editMedicineRequest.getPrice());
|
||||
stmt.setString(6, editMedicineRequest.getPreferedNote());
|
||||
stmt.setString(6, editMedicineRequest.getPreferredNote());
|
||||
stmt.setBoolean(7, editMedicineRequest.getSupplement());
|
||||
stmt.setBoolean(8, editMedicineRequest.getDeleted());
|
||||
stmt.setString(9, editMedicineRequest.getId());
|
||||
@@ -1669,7 +1666,7 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
private void getMedInfo(int sessionId) {
|
||||
try {
|
||||
ResultSet rs = statement.executeQuery(
|
||||
"select med_id, med_name, med_company, med_description, med_unit, med_selling_price, preference_note, supplement, deleted\n" +
|
||||
"select med_id, med_name, med_company, med_description, med_unit, med_selling_price, preferred_note, supplement, deleted\n" +
|
||||
" from Medicine"
|
||||
);
|
||||
|
||||
@@ -1684,12 +1681,12 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
String medDescription = rs.getString("med_description");
|
||||
String medUnit = rs.getString("med_unit");
|
||||
String medSellingPrice = rs.getString("med_selling_price");
|
||||
String preferenceNote = rs.getString("preference_note");
|
||||
String preferredNote = rs.getString("preferred_note");
|
||||
String supplement = rs.getString("supplement");
|
||||
String deleted = rs.getString("deleted");
|
||||
|
||||
String result = String.join("|",medId, medName, medCompany, medDescription, medUnit,
|
||||
medSellingPrice, preferenceNote != null ? preferenceNote : "", supplement != null ? supplement : "0", deleted);
|
||||
medSellingPrice, preferredNote != null ? preferredNote : "", supplement != null ? supplement : "0", deleted);
|
||||
resultList.add(result);
|
||||
}
|
||||
|
||||
@@ -1768,6 +1765,9 @@ public class ServerHandler extends SimpleChannelInboundHandler<TextWebSocketFram
|
||||
ServerDashboard.getInstance().addLog("Client timed out: Session " + user.getSessionId());
|
||||
}
|
||||
SessionManager.onUserDisconnect(ctx.channel());
|
||||
if (user != null) {
|
||||
user.resetAuthentication();
|
||||
}
|
||||
ctx.channel().close();
|
||||
} catch (Exception e) {
|
||||
ServerDashboard.getInstance().addLog("Error during client timeout: " + e.getMessage());
|
||||
|
||||
@@ -46,13 +46,19 @@ public class SessionManager {
|
||||
|
||||
public static int onUserLogin(Channel channel) {
|
||||
int sessionId = SESSION_ID.incrementAndGet();
|
||||
var user = new User(channel, sessionId);
|
||||
String channelId = channel.id().asLongText();
|
||||
var user = CHANNEL_TO_USER_MAP.get(channelId);
|
||||
if(user == null) {
|
||||
user = new User(channel, sessionId);
|
||||
CHANNEL_TO_USER_MAP.put(channelId, user);
|
||||
} else {
|
||||
user.setSessionId(sessionId);
|
||||
user.setChannel(channel);
|
||||
}
|
||||
var connection = new ClientConnection(channel, sessionId);
|
||||
|
||||
String channelId = channel.id().asLongText();
|
||||
SESSION_ID_MAP.put(sessionId, channelId);
|
||||
CHANNEL_SESSION_ID_MAP.put(channelId, sessionId);
|
||||
CHANNEL_TO_USER_MAP.put(channelId, user);
|
||||
CHANNEL_TO_CONNECTION_MAP.put(channelId, connection);
|
||||
|
||||
return sessionId;
|
||||
@@ -60,10 +66,12 @@ public class SessionManager {
|
||||
|
||||
public static void onUserDisconnect(Channel channel) {
|
||||
String channelId = channel.id().asLongText();
|
||||
int sessionId = CHANNEL_SESSION_ID_MAP.remove(channelId);
|
||||
SESSION_ID_MAP.remove(sessionId);
|
||||
CHANNEL_TO_USER_MAP.remove(channelId);
|
||||
CHANNEL_TO_CONNECTION_MAP.remove(channelId);
|
||||
var user = CHANNEL_TO_USER_MAP.get(channelId);
|
||||
if(user != null && user.getUserId() != -1) {
|
||||
int oldSessionId = CHANNEL_SESSION_ID_MAP.remove(channelId);
|
||||
SESSION_ID_MAP.remove(oldSessionId);
|
||||
CHANNEL_TO_CONNECTION_MAP.remove(channelId);
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateUserRole(String channelId, String role, int userId) {
|
||||
|
||||
@@ -464,4 +464,63 @@ public class GoogleDriveServiceOAuth {
|
||||
|
||||
return result.getFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a folder by name within a specific parent folder, or creates it if it doesn't exist.
|
||||
* @param parentFolderId The ID of the parent folder.
|
||||
* @param folderName The name of the folder to find or create.
|
||||
* @return The ID of the found or newly created folder.
|
||||
* @throws IOException if a Google Drive API error occurs.
|
||||
*/
|
||||
private String findOrCreateFolder(String parentFolderId, String folderName) throws IOException {
|
||||
// Search for existing folder
|
||||
String query = String.format("name = '%s' and mimeType='application/vnd.google-apps.folder' and '%s' in parents and trashed=false",
|
||||
folderName.replace("'", "\\'"),
|
||||
parentFolderId);
|
||||
|
||||
FileList result = driveService.files().list()
|
||||
.setQ(query)
|
||||
.setSpaces("drive")
|
||||
.setFields("files(id)")
|
||||
.execute();
|
||||
|
||||
if (!result.getFiles().isEmpty()) {
|
||||
String existingFolderId = result.getFiles().get(0).getId();
|
||||
log.info("Found existing folder '{}' (ID: {}) inside parent {}", folderName, existingFolderId, parentFolderId);
|
||||
return existingFolderId;
|
||||
}
|
||||
|
||||
// Create folder if it doesn't exist
|
||||
log.info("Creating new folder '{}' under parent {}", folderName, parentFolderId);
|
||||
return createFolderUnderParent(parentFolderId, folderName);
|
||||
}
|
||||
|
||||
|
||||
// Add this primary backup method inside the GoogleDriveServiceOAuth class
|
||||
/**
|
||||
* Backs up the specified database file to a dedicated backup folder in Google Drive.
|
||||
* The backup filename will be timestamped.
|
||||
* @param dbFile The local database file to upload.
|
||||
* @return The ID of the uploaded file in Google Drive.
|
||||
* @throws IOException if the upload fails.
|
||||
*/
|
||||
public String backupDatabaseFile(java.io.File dbFile) throws IOException {
|
||||
// 1. Define the dedicated, private backup folder name
|
||||
String backupFolderName = "_Database_Backups"; // Using underscore to sort it to the top
|
||||
|
||||
// 2. Get or create the backup folder under the main application root folder
|
||||
String backupFolderId = findOrCreateFolder(this.rootFolderId, backupFolderName);
|
||||
log.info("Using database backup folder: {} (ID: {})", backupFolderName, backupFolderId);
|
||||
|
||||
// 3. Create a unique, timestamped filename for the backup
|
||||
String timestamp = new java.text.SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(new java.util.Date());
|
||||
String backupFileName = "BSK_Backup_" + timestamp + ".db";
|
||||
|
||||
// 4. Upload the file
|
||||
log.info("Uploading database backup as '{}' to Google Drive...", backupFileName);
|
||||
String fileId = uploadFileToFolder(backupFolderId, dbFile, backupFileName);
|
||||
log.info("✅ Successfully uploaded database backup. File ID: {}", fileId);
|
||||
|
||||
return fileId;
|
||||
}
|
||||
}
|
||||
@@ -28,4 +28,7 @@ google.drive.root.folder=BSK_Clinic_Patient_Files
|
||||
storage.image_db_path=img_db
|
||||
|
||||
# The base directory for checkup media resources (currently unused but defined for future use).
|
||||
storage.checkup_media_base_dir=src/main/resources/image/checkup_media
|
||||
storage.checkup_media_base_dir=src/main/resources/image/checkup_media
|
||||
|
||||
|
||||
app.auto_change_status_to_finished=false
|
||||
Binary file not shown.
Reference in new issue
Block a user