-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlogBookData.java
More file actions
1310 lines (1052 loc) · 46.9 KB
/
Copy pathlogBookData.java
File metadata and controls
1310 lines (1052 loc) · 46.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.stream.Stream;
class DataPlace {
boolean isTestingPhase = Boolean.parseBoolean(ConfigLoader.config.getProperty("testing.phase","false"));
private JFrame jf;
private JPanel view;
private JPanel mainContent;
static JTextField dateFrom, dateTo;
static JComboBox<String> startTime, endTime;
static JCheckBox everything;
private JCheckBox matchD, matchT;
static JButton exportButton;
private JPanel datePanel;
// Store the generated combos to retrieve values for filtering
static List<JComboBox<String>> dynamicCombos = new ArrayList<>();
private static String table = ConfigLoader.config.getProperty("LOCAL_TABLE");
static boolean matchSlotConditon = false;
private static String cloudUrl = ConfigLoader.config.getProperty("project.url");
private static String cloudKey = ConfigLoader.config.getProperty("anon.key");
private static String cloudAdminKeyValue = ConfigLoader.config.getProperty("server.header");
private final static String JDBC_URL_local = ConfigLoader.getLocalDBUrl();
// Store categories and their options
static Map<String, List<String>> configMap = HelperFunctions.loadConfigMap(JDBC_URL_local);
DataPlace() {
jf = new JFrame("Data Zone");
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setSize(900, 500);
jf.setLocationRelativeTo(null);
createInitialView();
jf.setVisible(true);
}
private static String getLatestLocalTimestamp(Connection localConn,String in_out) {
String sql = String.format("SELECT MAX(%s) FROM %s", in_out, ConfigLoader.config.getProperty("LOCAL_TABLE"));
try (Statement stmt = localConn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
if (rs.next() && rs.getString(1) != null) {
return rs.getString(1);
}
} catch (SQLException e) {
e.printStackTrace();
}
return "1970-01-01 00:00:00"; // Fallback: Fetch everything if local is empty
}
static boolean syncDatabases() {
if (table.equals("temp")) return false;
String localTable = ConfigLoader.config.getProperty("LOCAL_TABLE");
try (Connection localConn = DriverManager.getConnection(JDBC_URL_local)) {
// 1. Setup local table if not exist to match cloud structure
OptionsManager.createRecordsTableLocal(localConn);
String lastLoginLocalTime = getLatestLocalTimestamp(localConn, "login_time");
String lastLogoutLocalTime = getLatestLocalTimestamp(localConn, "logout_time");
String jsonData = String.format( "{\"lastLoginTimestamp\":\"%s\", \"lastLogoutTimestamp\":\"%s\"}",
lastLoginLocalTime,
lastLogoutLocalTime
);
Object result = CloudAPI.callEdgeFunction("fetch-new-data", jsonData);
if (result instanceof List) {
// This is the specific type cast
@SuppressWarnings("unchecked")
List<Map<String, Object>> cloudRecords = (List<Map<String, Object>>) result;
localConn.setAutoCommit(false);
String insertSql = "INSERT OR REPLACE INTO " + localTable +
" (session_id, login_time, logout_time, usn, name, details) VALUES (?, ?, ?, ?, ?, ?)";
try (PreparedStatement localStmt = localConn.prepareStatement(insertSql)) {
for (Map<String, Object> record : cloudRecords) {
localStmt.setString(1, (String) record.get("session_id"));
localStmt.setString(2, (String) record.get("login_time"));
localStmt.setString(3, (String) record.get("logout_time"));
localStmt.setString(4, (String) record.get("usn"));
localStmt.setString(5, (String) record.get("name"));
localStmt.setString(6, (String) record.get("details")); // Just move the entire string => parse the string to behave like json
localStmt.addBatch();
}
localStmt.executeBatch();
}
localConn.commit();
localConn.setAutoCommit(true);
System.out.println("✓ Mirror Sync Complete. New/Updated Entries = "+ cloudRecords.size());
if (cloudRecords.isEmpty()) {
HelperFunctions.showSyncStatusDialog(
"Database is already up to date.",
JOptionPane.INFORMATION_MESSAGE
);
} else {
HelperFunctions.showSyncStatusDialog(
"Successfully synced " + cloudRecords.size() + " new/updated entries.",
JOptionPane.INFORMATION_MESSAGE
);
}
return true;
} else {
System.err.println("Unexpected response type from Cloud: " + (result != null ? result.getClass().getName() : "null"));
return false;
}
} catch (SQLException e) {
e.printStackTrace();
HelperFunctions.showSyncStatusDialog(
"Database Error: " + e.getMessage(),
JOptionPane.ERROR_MESSAGE
);
return false;
}
}
public List<SessionGroup> getDatafromDataBase(String table, JPanel mainContent, Map<String, String> currentFilters) {
// configure which database to connect
Connection connection = null;
if (table.equals("temp")) {
connection = logBookData.manager.connection;
} else {
try {
connection = DriverManager.getConnection(JDBC_URL_local);
} catch (SQLException e) {
System.out.println("Connection to sqlite failed: " + e.getMessage());
}
}
// each entry is stored in records (EVERYTHING)
List<SessionGroup> groups = new ArrayList<>();
try {
// Create a StringBuilder to construct the query
StringBuilder showQueryBuilder = new StringBuilder("SELECT * FROM " + table + " WHERE 1=1");
List<String> params = new ArrayList<>();
// Conditionally append WHERE clauses
if (currentFilters != null) {
for (Map.Entry<String, String> filter : currentFilters.entrySet()) {
String category = filter.getKey();
String value = filter.getValue();
// Check if user selected something other than the default header (e.g., "Subjects")
if (!value.equalsIgnoreCase(category)) {
showQueryBuilder.append(" AND json_extract(details, '$.")
.append(category)
.append("') = ?");
params.add(value);
}
}
}
// Add the ORDER BY clause
showQueryBuilder.append(" ORDER BY USN");
String showQuery = showQueryBuilder.toString();
try (PreparedStatement preparedStatement = connection.prepareStatement(showQuery)) {
// Loop through the collected parameters and set them
for (int i = 0; i < params.size(); i++) {
preparedStatement.setString(i + 1, params.get(i));
}
ResultSet resultSet = preparedStatement.executeQuery();
// each entry is stored in records (EVERYTHING)
List<SessionRecord> records = new ArrayList<>();
while (resultSet.next()) {
String jsonDetails = resultSet.getString("details");
// Convert the JSON string into a Map of ALL available attributes
Map<String, String> attrMap = HelperFunctions.parseJsonToMap(jsonDetails);
records.add(new SessionRecord(
resultSet.getString("login_time"),
resultSet.getString("logout_time"),
resultSet.getString("usn"),
resultSet.getString("name"),
attrMap,
resultSet.getString("session_id")
));
}
System.out.println("Data Retrived/Reloaded");
if (!table.equals("temp")) {
connection.close();
}
// Group the records
groups = SessionGrouper.groupSessions(records);
return groups;
}
} catch (SQLException e) {
System.out.println("Database not connected");
System.out.println(e.getMessage());
return groups;
}
}
void showData(String table, JPanel mainContent, Map<String, String> currentFilters) {
// remove previously present content
if (mainContent.getComponentCount() > 1)
mainContent.remove(1);
// Grouped records
List<SessionGroup> groups = getDatafromDataBase(table, mainContent, currentFilters);
JScrollPane scroll = new JScrollPane(new TimeGroupPanel(groups));
// Add the content
mainContent.add(scroll, BorderLayout.CENTER);
mainContent.revalidate();
mainContent.repaint();
// Add action Listener to "Export Button"
ActionListener[] listeners = exportButton.getActionListeners();
// Remove each listener
for (ActionListener listener : listeners) {
exportButton.removeActionListener(listener);
}
DataPlace.exportButton.addActionListener(e -> {
JFrame frame = new JFrame();
JDialog dialog = new JDialog(frame, "Select Groups to Export", true);
dialog.setLayout(new BorderLayout());
JPanel listPanel = new JPanel();
listPanel.setLayout(new BoxLayout(listPanel, BoxLayout.Y_AXIS));
List<JCheckBox> checkBoxes = new ArrayList<>();
Integer num = 0;
for (SessionGroup group : groups) {
JCheckBox cb = new JCheckBox((++num).toString());
checkBoxes.add(cb);
listPanel.add(cb);
}
JScrollPane scrollPane = new JScrollPane(listPanel);
dialog.add(scrollPane, BorderLayout.CENTER);
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
JLabel selectedLabel = new JLabel("0 selected");
JButton selectAllBtn = new JButton("Select All");
JButton exportBtn = new JButton("Export");
String[] pdf_csv = { "PDF", "CSV" };
JComboBox<String> format = new JComboBox<>(pdf_csv);
selectAllBtn.addActionListener(ev -> {
boolean isCancel = selectAllBtn.getText().equals("Cancel");
selectAllBtn.setText(isCancel ? "Select All" : "Cancel");
for (JCheckBox cb : checkBoxes)
cb.setSelected(!isCancel);
long count = checkBoxes.stream().filter(AbstractButton::isSelected).count();
selectedLabel.setText(count + " selected");
});
for (JCheckBox cb : checkBoxes) {
cb.addActionListener(ev -> {
long count = checkBoxes.stream().filter(AbstractButton::isSelected).count();
selectedLabel.setText(count + " selected");
});
}
exportBtn.addActionListener(ev -> {
ArrayList<SessionGroup> selectedGroups = new ArrayList<>();
for (int i = 0; i < checkBoxes.size(); i++) {
if (checkBoxes.get(i).isSelected()) {
selectedGroups.add(groups.get(i));
}
}
String selected = (String) format.getSelectedItem();
new ExportCsvPdf(selected,selectedGroups);
});
bottomPanel.add(selectedLabel);
bottomPanel.add(selectAllBtn);
bottomPanel.add(format);
bottomPanel.add(exportBtn);
dialog.add(bottomPanel, BorderLayout.SOUTH);
dialog.setSize(400, 280);
dialog.setLocationRelativeTo(mainContent);
dialog.setVisible(true);
});
}
void createInitialView() {
try {
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (Exception e) {
// Use default if Nimbus is not available
System.out.println("Nimbus not there");
}
everything = new JCheckBox("All");
matchD = new JCheckBox("Match Date");
matchT = new JCheckBox("Match Time");
String date = new java.text.SimpleDateFormat("yyyy-MM-dd, E").format(Calendar.getInstance().getTime());
dateFrom = new JTextField(date, 12);
dateFrom.setEditable(false);
dateTo = new JTextField(date, 12);
dateTo.setEditable(false);
// Add action listeners for dialog components
ActionListener dialogActionListener = e -> {
if (e.getSource() == everything) {
dateFrom.setEnabled(!everything.isSelected());
dateTo.setEnabled(!everything.isSelected());
matchD.setEnabled(!everything.isSelected());
}
if (e.getSource() == matchD) {
dateTo.setVisible(!matchD.isSelected());
datePanel.getComponent(2).setVisible(dateTo.isVisible());
}
if (e.getSource() == matchT) {
matchSlotConditon = !matchSlotConditon;
}
// Trigger data refresh
showData(table, mainContent, HelperFunctions.getCurrentFilters());
};
everything.addActionListener(dialogActionListener);
matchD.addActionListener(dialogActionListener);
matchT.addActionListener(dialogActionListener);
dateFrom.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (dateFrom.isEnabled()) {
DatePicker datePicker = new DatePicker(jf, dateFrom);
datePicker.showPicker();
dialogActionListener.actionPerformed(new ActionEvent(dateFrom, 0, ""));
}
}
});
dateTo.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (dateTo.isEnabled()) {
DatePicker datePicker = new DatePicker(jf, dateTo);
datePicker.showPicker();
dialogActionListener.actionPerformed(new ActionEvent(dateTo, 0, ""));
}
}
});
view = new JPanel(new BorderLayout());
// view.setBackground(new Color(24, 25, 26));
view.setBorder(BorderFactory.createEmptyBorder(50, 50, 50, 50));
// Top Panel (North) using BorderLayout for title (Center) and settings (East)
JPanel topPanel = new JPanel(new BorderLayout());
topPanel.setOpaque(false);
// Heading Panel at the Top (North) - Keep it in the CENTER of topPanel
JPanel headingPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
headingPanel.setOpaque(false);
JLabel title = new JLabel("Log book");
title.setFont(new Font("Arial", Font.BOLD, 48));
title.setForeground(Color.WHITE);
headingPanel.add(title);
// Settings Button Panel (East)
JPanel settingsPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
settingsPanel.setOpaque(false);
// Settings Button ⚙️
JButton settingsButton = new JButton("\u2699");
settingsButton.setFont(new Font("Arial", Font.PLAIN, 18));
settingsButton.setForeground(Color.WHITE);
settingsButton.setBackground(new Color(44, 44, 46)); // Dark background
settingsButton.setFocusPainted(false);
settingsButton.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10)); // Padding
settingsButton.addActionListener(e -> {
System.out.println("Settings button clicked!");
JDialog settingsDialog = new JDialog(jf, "Settings", true);
Callable<JPanel> CloudDBConfig = () -> {
// main panel with padding
JPanel mainPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.WEST;
gbc.insets = new Insets(5, 10, 5, 10); // padding
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1.0;
JLabel cloudDatabaseLabel = new JLabel("Cloud Database Configuration");
JLabel cloudProjectURL = new JLabel("Project URL:"); cloudProjectURL.setVisible(false);
JLabel cloudProjectKey = new JLabel("Publishable/Anon Key:"); cloudProjectKey.setVisible(false);
JLabel cloudAdminKey = new JLabel("Admin Key:"); cloudAdminKey.setVisible(false);
gbc.gridx = 0;
gbc.gridy = 0;
mainPanel.add(cloudDatabaseLabel, gbc);
JPanel sensitivePanel = new JPanel(new GridBagLayout());
GridBagConstraints gbcs = new GridBagConstraints();
gbcs.anchor = GridBagConstraints.WEST;
gbcs.fill = GridBagConstraints.HORIZONTAL;
gbcs.weightx = 1.0;
boolean isVerified = Boolean.parseBoolean(ConfigLoader.config.getProperty("CLOUD_DB_VERIFIED", "false"));
JLabel verifiedorNot = (isVerified) ? new JLabel("Verfied"): new JLabel("Verification Failed\nRe-configure application with right credentials");
verifiedorNot.setFont(new Font("Arial", Font.BOLD, 14));
if (isVerified) verifiedorNot.setForeground(new Color(52, 199, 89)); // Green
else verifiedorNot.setForeground(Color.RED);
if (cloudUrl != null && !cloudUrl.isEmpty()) {
JTextField cloudUrlField = new JTextField(cloudUrl, 25);
cloudUrlField.setEditable(false);
JTextField cloudKeyField = new JTextField(cloudKey,25); cloudKeyField.setVisible(false);
int iX = 0, iY=0;
gbcs.gridx = iX;
gbcs.gridy=iY++; sensitivePanel.add(cloudProjectURL,gbcs);
gbcs.gridy=iY++; sensitivePanel.add(cloudUrlField,gbcs);
gbcs.gridy=iY++; sensitivePanel.add(cloudProjectKey,gbcs);
gbcs.gridy = iY++; sensitivePanel.add(cloudKeyField,gbcs);
} else {
verifiedorNot.setVisible(false);
JButton addCloudDatabaseButton = new JButton("Add Cloud Database Info");
addCloudDatabaseButton.addActionListener(ee -> {
// --- Inner Dialog (Add Info) ---
JDialog addInfoDialog = new JDialog(jf, "Add Cloud Database Info", true);
addInfoDialog.setSize(350, 300);
addInfoDialog.setLocationRelativeTo(settingsDialog);
addInfoDialog.setLayout(new GridBagLayout());
GridBagConstraints innerGbc = new GridBagConstraints();
innerGbc.fill = GridBagConstraints.HORIZONTAL;
innerGbc.insets = new Insets(5, 10, 5, 10);
innerGbc.weightx = 1.0;
JTextField cloudUrlField = new JTextField(20);
JTextField cloudKeyField = new JTextField(20);
JPasswordField cloudAdminKeyField = new JPasswordField(20);
JButton saveButton = new JButton("Verify and Save");
innerGbc.gridx = 0; innerGbc.gridy = 0; addInfoDialog.add(new JLabel("<html>Enter Project URL<font color='red'>*</font></html>"), innerGbc);
innerGbc.gridy++; addInfoDialog.add(cloudUrlField, innerGbc);
innerGbc.gridy++; addInfoDialog.add(new JLabel("<html>Enter Publishable/Anon Key<font color='red'>*</font></html>"), innerGbc);
innerGbc.gridy++; addInfoDialog.add(cloudKeyField, innerGbc);
innerGbc.gridy++; addInfoDialog.add(new JLabel("<html>Enter Admin Key<font color='red'>*</font></html>"),innerGbc);
innerGbc.gridy++; addInfoDialog.add(cloudAdminKeyField, innerGbc);
innerGbc.gridy++; addInfoDialog.add(saveButton, innerGbc);
saveButton.addActionListener(eee -> {
cloudUrl = cloudUrlField.getText().trim();
cloudKey = cloudKeyField.getText().trim();
cloudAdminKeyValue = new String(cloudAdminKeyField.getPassword()).trim();
if (cloudUrl.isEmpty() || cloudKey.isEmpty() || cloudAdminKeyValue.isEmpty() ) {
JOptionPane.showMessageDialog(addInfoDialog, "Cannot be blank.", "Invalid Input", JOptionPane.ERROR_MESSAGE);
return;
}
boolean verification = CloudDataBaseInfo.verification(cloudUrl,cloudKey, cloudAdminKeyValue);
ConfigLoader.saveCloudDbConfig(cloudUrl, cloudKey, cloudAdminKeyValue, verification );
if(!verification) {
verifiedorNot.setText("Verification Failed. Re-configure application with right credentials");
verifiedorNot.setForeground(Color.RED);
} else {
//
verifiedorNot.setText("Verifying & Setting up Cloud...");
verifiedorNot.setForeground(Color.ORANGE);
// Background task
new Thread(() -> {
try {
CloudDataBaseInfo.createTables();
SwingUtilities.invokeLater(() -> {
verifiedorNot.setText("Verified"); // Change verified status
verifiedorNot.setForeground(new Color(52, 199, 89)); // Green
});
} catch (Exception ex) {
// If it fails, pop an error back on the UI thread
SwingUtilities.invokeLater(() -> {
verifiedorNot.setText("Verification Failed"); // Change verified status
verifiedorNot.setForeground(new Color(52, 199, 89)); // Green
});
HelperFunctions.showSyncStatusDialog(
"Cloud table creation failed, Re-configure the application",
JOptionPane.WARNING_MESSAGE
);
ex.printStackTrace();
}
}).start();
}
addInfoDialog.dispose();
settingsDialog.dispose();
settingsButton.putClientProperty("targetTab", 2);
settingsButton.doClick(); // to repaint Settings Dialog
});
addInfoDialog.setVisible(true);
});
sensitivePanel.add(addCloudDatabaseButton);
}
gbc.gridy = 1; // Put sensitive panel on the next row visually
mainPanel.add(sensitivePanel, gbc);
gbc.gridy = 2;
gbc.anchor = GridBagConstraints.CENTER; // Center the component within its cell
gbc.fill = GridBagConstraints.NONE;
mainPanel.add(verifiedorNot,gbc);
return mainPanel;
};
Callable<JPanel> AutoDeleteConfig = () -> {
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 10, 5, 10);
gbc.fill = GridBagConstraints.HORIZONTAL;
int iX = 0, iY = 0; // for gbc
gbc.weightx = 1.0;
if (Boolean.parseBoolean(ConfigLoader.config.getProperty("testing.skip.delete", "false"))) {
JLabel warningLabel = new JLabel("\u26A0 Testing Mode: Deletions are disabled");
warningLabel.setForeground(Color.RED);
warningLabel.setFont(new Font("Arial", Font.BOLD, 12));
gbc.gridy = iY++;
mainPanel.add(warningLabel, gbc);
}
boolean autoSavedState = Boolean.parseBoolean(ConfigLoader.config.getProperty("auto.save"));
JToggleButton enableAutoSave = new JToggleButton("Enable Auto Save"); enableAutoSave.setSelected(autoSavedState);
// --- AutoSave Label ---
JLabel autoSaveLabel = new JLabel("Auto Save Directory");
gbc.gridx = iX;
gbc.gridy = iY++;
gbc.weighty = 0.0;
mainPanel.add(autoSaveLabel, gbc);
// --- Path to save file ---
JTextArea autoSaveDir = new JTextArea();
autoSaveDir.setEditable(false);
autoSaveDir.setLineWrap(true);
autoSaveDir.setWrapStyleWord(true);
autoSaveDir.setText(ConfigLoader.config.getProperty("auto.save.records.directory"));
JScrollPane scrollPane = new JScrollPane(autoSaveDir);
gbc.gridx = 0;
gbc.gridy = iY++;
gbc.weighty = 1.0;
mainPanel.add(scrollPane, gbc);
//Mouse Listener to Set AutoSave Directory
autoSaveDir.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (!autoSaveDir.isEnabled()) return;
System.out.println("Text Area Clicked");
JFileChooser fileChooser = new JFileChooser();
// Configure the file chooser to select directories only
fileChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
// Set a default starting directory as current working directory
fileChooser.setCurrentDirectory(new File(ConfigLoader.config.getProperty("auto.save.records.directory")));
int result = fileChooser.showSaveDialog(autoSaveDir);
if (result == JFileChooser.APPROVE_OPTION) {
File selectedDirectory = fileChooser.getSelectedFile();
String absolutePath = selectedDirectory.getAbsolutePath();
String oldPathString = ConfigLoader.config.getProperty("auto.save.records.directory");
Path oldPath = Paths.get(oldPathString);
Path newPathDir = Paths.get(absolutePath);
if (! (oldPath.equals(newPathDir) || !Files.isDirectory(oldPath)) ) {
try {
// Iterate through all files in the old directory
try (Stream<Path> files = Files.list(oldPath)) {
files.forEach(source -> {
// Construct the destination path
Path destination = newPathDir.resolve(source.getFileName());
try {
// Move the file. ATOMIC_MOVE ensures the file is moved in one operation,
// and REPLACE_EXISTING prevents errors if a file with the same name exists.
Files.move(source, destination, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ee) {
// Handle failure for individual file move (e.g., permission denied)
System.err.println("Failed to move file " + source.getFileName() + ": " + ee.getMessage());
}
});
}
//delete the previous directory
Files.delete(oldPath);
System.out.println("Previous autoSave directory");
} catch (IOException ee) {
// Handle critical errors like inability to create the new directory or list files
System.err.println("Critical error during directory transfer: " + ee.getMessage());
}
}
ConfigLoader.setAutoSaveDirectory(absolutePath);
autoSaveDir.setText(absolutePath);
JOptionPane.showMessageDialog(jf,
"AutoSave directory set to: \n" + absolutePath + "\n(Exported and deleted deleted file in: " + oldPathString + " )",
"Location Selected",
JOptionPane.INFORMATION_MESSAGE);
} else if (result == JFileChooser.CANCEL_OPTION) {
// User cancelled the dialog
System.out.println("Folder selection cancelled by user.");
}
settingsDialog.pack();
}
});
// --- Recent Cleanup ---
JLabel cleanUpL = new JLabel("Recent Cleanup (Local DB): " + ConfigLoader.config.getProperty("local.auto.delete.last.run.date"));
gbc.gridy = iY++; mainPanel.add(cleanUpL,gbc);
JLabel cleanUpC = new JLabel("Recent Cleanup (Cloud DB): " + ConfigLoader.config.getProperty("cloud.auto.delete.last.run.date"));
gbc.gridy = iY++; mainPanel.add(cleanUpC,gbc);
// --- Time Duration to delete (Local) ---
JPanel timeDuration = new JPanel(new GridLayout(2, 2));
JLabel timeDurationLocalLabel = new JLabel("Duration of cleanup (Local DB): "); timeDuration.add(timeDurationLocalLabel);
JComboBox timeDurationLocal = new JComboBox<>(new String[] {"1 Month", "3 Months", "6 Months", "9 Months", "12 Months"}); timeDuration.add(timeDurationLocal);
// --- Time Duration to delete (Cloud) ---
JLabel timeDurationCloudLabel = new JLabel("Duration of cleanup (Cloud DB): "); timeDuration.add(timeDurationCloudLabel);
JComboBox timeDurationCloud = new JComboBox<>(new String[] {"1 Week", "2 Weeks", "3 Weeks", "4 Weeks"}); timeDuration.add(timeDurationCloud);
gbc.gridy = iY++; mainPanel.add(timeDuration,gbc);
gbc.gridy = iY++; mainPanel.add(enableAutoSave,gbc);
enableAutoSave.addActionListener(ee -> {
boolean enabled = enableAutoSave.isSelected();
String text = (enabled) ? "Disable Auto Save" : "Enable Auto Save";
enableAutoSave.setText(text);
timeDurationCloud.setEnabled(enabled);
timeDurationLocal.setEnabled(enabled);
cleanUpC.setVisible(enabled);
cleanUpL.setVisible(enabled);
autoSaveDir.setEnabled(enabled);
//save updated property
ConfigLoader.setAutoSaveFeature("auto.save", String.valueOf(enabled));
if (enabled) {
String AUTOSAVE_DIR_PATH = ConfigLoader.config.getProperty("auto.save.records.directory");
if (AUTOSAVE_DIR_PATH.equals("autoSaved_Session_Records"))
AUTOSAVE_DIR_PATH = Paths.get(System.getProperty("user.home"), "autoSaved_Session_Records").toString();
if (!Paths.get(AUTOSAVE_DIR_PATH).toFile().getAbsoluteFile().exists()) {
Paths.get(AUTOSAVE_DIR_PATH).toFile().getAbsoluteFile().mkdirs();
}
ConfigLoader.setAutoSaveDirectory(AUTOSAVE_DIR_PATH);
} else {
String AUTOSAVE_DIR_PATH = ConfigLoader.config.getProperty("auto.save.records.directory");
if (AUTOSAVE_DIR_PATH.equals("autoSaved_Session_Records"))
AUTOSAVE_DIR_PATH = Paths.get(System.getProperty("user.home"), "autoSaved_Session_Records").toString();
if (Paths.get(AUTOSAVE_DIR_PATH).toFile().getAbsoluteFile().exists()) {
Paths.get(AUTOSAVE_DIR_PATH).toFile().getAbsoluteFile().delete();
}
}
autoSaveDir.setText(!enabled ? "(Auto Save Disabled)" : ConfigLoader.config.getProperty("auto.save.records.directory"));
settingsDialog.pack();
});
boolean enabled = enableAutoSave.isSelected();
if (enabled) {
String AUTOSAVE_DIR_PATH = ConfigLoader.config.getProperty("auto.save.records.directory");
if (AUTOSAVE_DIR_PATH.equals("autoSaved_Session_Records"))
AUTOSAVE_DIR_PATH = Paths.get(System.getProperty("user.home"), "autoSaved_Session_Records").toString();
if (!Paths.get(AUTOSAVE_DIR_PATH).toFile().getParentFile().exists()) {
Paths.get(AUTOSAVE_DIR_PATH).toFile().getParentFile().mkdirs();
}
ConfigLoader.setAutoSaveDirectory(AUTOSAVE_DIR_PATH);
} else {
String AUTOSAVE_DIR_PATH = ConfigLoader.config.getProperty("auto.save.records.directory");
if (AUTOSAVE_DIR_PATH.equals("autoSaved_Session_Records"))
AUTOSAVE_DIR_PATH = Paths.get(System.getProperty("user.home"), "autoSaved_Session_Records").toString();
if (Paths.get(AUTOSAVE_DIR_PATH).toFile().getAbsoluteFile().exists()) {
Paths.get(AUTOSAVE_DIR_PATH).toFile().getAbsoluteFile().delete();
}
}
String text = (enabled) ? "Disable Auto Save" : "Enable Auto Save";
enableAutoSave.setText(text);
// Explicitly set the state of dependent components
timeDurationCloud.setEnabled(enabled);
timeDurationLocal.setEnabled(enabled);
cleanUpC.setVisible(enabled);
cleanUpL.setVisible(enabled);
// Update the directory field
autoSaveDir.setText(!enabled ? "(Auto Save Disabled)" : ConfigLoader.config.getProperty("auto.save.records.directory"));
autoSaveDir.setEnabled(enabled);
// Calculate index to set
timeDurationLocal.setSelectedIndex(Integer.parseInt(ConfigLoader.config.getProperty("local.auto.delete.duration")) / 3);
timeDurationCloud.setSelectedIndex(Integer.parseInt(ConfigLoader.config.getProperty("cloud.auto.delete.duration")) - 1);
//Action Listeners
timeDurationLocal.addActionListener(ee -> {
String selectedDuration = (String) timeDurationLocal.getSelectedItem();
String[] parts = selectedDuration.split(" ");
int duration = Integer.parseInt(parts[0]);
ConfigLoader.setAutoDeleteDuration("local.auto.delete.duration", String.valueOf(duration));
System.out.println("(LOCAL DB) Auto Delete Duration set to: " + duration);
});
timeDurationCloud.addActionListener(ee -> {
String selectedDuration = (String) timeDurationCloud.getSelectedItem();
String[] parts = selectedDuration.split(" ");
int duration = Integer.parseInt(parts[0]);
ConfigLoader.setAutoDeleteDuration("cloud.auto.delete.duration", String.valueOf(duration));
System.out.println("(CLOUD DB) Auto Delete Duration set to: " + duration);
});
return mainPanel;
};
Callable<JPanel> ConfigurationFolder = () -> {
JPanel mainPanel = new JPanel(new GridBagLayout());
final Runnable[] refreshConfigurationFolder = new Runnable[1];
// As Callable can only be called once, create a helper method (runnable) that can be called anytime to refresh the UI
refreshConfigurationFolder[0] = () -> {
mainPanel.removeAll(); // Clear existing buttons
GridBagConstraints gbc = new GridBagConstraints();
gbc.insets = new Insets(5, 10, 5, 10);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.gridx = 0;
gbc.gridy = 0;
// --- SECTION: Edit Options ---
JLabel label = new JLabel("\u2699 Edit Application References");
label.setFont(new Font("SansSerif", Font.BOLD, 14));
mainPanel.add(label, gbc);
// Refresh Button to sync cloud => application
gbc.gridx++;
JButton refresh = new JButton("\u27F3");
refresh.addActionListener(ee ->
HelperFunctions.performSyncWithProgress(
null,
()->{ CloudDataBaseInfo.fetchConfigurationFromCloud(); },
()->{}
)
);
mainPanel.add(refresh);
gbc.gridx--;
//
// Dynamic Options to manage
gbc.gridy++;
JButton manageCatsBtn = new JButton("+ Add / Remove Categories");
manageCatsBtn.setFont(new Font("SansSerif", Font.ITALIC, 12));
// This helper would handle adding/deleting keys from the config table
manageCatsBtn.addActionListener(ee -> HelperFunctions.showCategoryManagerDialog(mainPanel,refreshConfigurationFolder[0]));
mainPanel.add(manageCatsBtn, gbc);
Set<String> categories = DataPlace.configMap.keySet();
for (String cat : categories) {
gbc.gridy++;
JButton btn = new JButton("Manage " + cat);
btn.addActionListener(ee -> HelperFunctions.showEditDialog(cat, mainPanel));
mainPanel.add(btn, gbc);
}
// --- SECTION: Danger Zone ---
gbc.gridy++;
gbc.insets = new Insets(30, 10, 10, 10);
JButton deleteConfigurations = new JButton("\uD83D\uDDD1 Delete Configuration");
deleteConfigurations.setBackground(new Color(220, 53, 69));
deleteConfigurations.setForeground(Color.WHITE);
deleteConfigurations.setFocusPainted(false);
deleteConfigurations.setFont(new Font("SansSerif", Font.BOLD, 12));
// Makes the background color visible on MacOS and some Windows themes
deleteConfigurations.setContentAreaFilled(true);
deleteConfigurations.setOpaque(true);
deleteConfigurations.setBorderPainted(false);
mainPanel.add(deleteConfigurations, gbc);
deleteConfigurations.addActionListener(ee -> {
// Checkbox for cloud deletion
JCheckBox deleteCloudCB = new JCheckBox("Wipe Cloud Database (Delete all tables)");
deleteCloudCB.setForeground(java.awt.Color.RED);
deleteCloudCB.setFocusPainted(false);
// Layout
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.add(new JLabel("CRITICAL ACTION: This will delete local configurations."));
panel.add(new JLabel("The application will terminate and must be re-configured."));
panel.add(Box.createVerticalStrut(15));
panel.add(deleteCloudCB);
panel.add(Box.createVerticalStrut(5));
panel.add(new JLabel("<html><i>Warning: Deleting the cloud is irreversible.</i></html>"));
// Confirm Dialog
int response = JOptionPane.showConfirmDialog(
mainPanel,
panel,
"Confirm Critical Deletion",
JOptionPane.YES_NO_OPTION,
JOptionPane.WARNING_MESSAGE
);
// Deletion
if (response == JOptionPane.YES_OPTION) {
try {
// Perform Local Cleanup
System.out.println("Deleting local data...");
Path CONFIG_DIR_PATH = Paths.get(System.getProperty("user.home"), ".DigiLogBook");
ConfigLoader.deleteConfigFolder(CONFIG_DIR_PATH);
// Check if Cloud Wipe was requested
if (deleteCloudCB.isSelected() && Boolean.parseBoolean(ConfigLoader.config.getProperty("CLOUD_DB_VERIFIED", "false"))) {
System.out.println("Initiating Cloud Wipe...");
CloudAPI.callEdgeFunction("teardown-db", "{}");
}
JOptionPane.showMessageDialog(null, "System reset successful. Terminating.");
// Terminate the app
System.out.println("Terminating Application...");
System.exit(0);
} catch (Exception ex) {
JOptionPane.showMessageDialog(null, "Error during cleanup: " + ex.getMessage());
ex.printStackTrace();
}
}
});
mainPanel.revalidate();
mainPanel.repaint();
settingsDialog.pack();
};
refreshConfigurationFolder[0].run();
return mainPanel;
};
JTabbedPane ooptions = new JTabbedPane() {
@Override
public Dimension getPreferredSize() {
Component selected = getSelectedComponent();
if (selected != null) {
Dimension d = selected.getPreferredSize();
int tabHeaderHeight = getUI().getTabRunCount(this) * 32; // Approx header height
// Calculate required size based on content + headers
int reqWidth = d.width + 20;
int reqHeight = d.height + tabHeaderHeight;
// Return the LARGER of (Required Size) vs (Minimum Size)
return new Dimension(
Math.max(reqWidth, 400),
Math.max(reqHeight, 180)
);
}
return super.getPreferredSize();
}
};
try {
ooptions.addTab("Configuration Details", ConfigurationFolder.call());
ooptions.addTab("Auto Save/Delete", AutoDeleteConfig.call());
ooptions.addTab("Cloud Database", CloudDBConfig.call());
} catch (Exception e1) {
System.err.println("ERROR: While Adding Tabs to Settings Dialog");
e1.printStackTrace();
}
ooptions.addChangeListener(ee -> {
settingsDialog.revalidate();
settingsDialog.pack();
settingsDialog.setLocationRelativeTo(jf);
});
// Add main panel to the center of the settings dialog
settingsDialog.add(ooptions, BorderLayout.CENTER);
// Trigger ChangeListener for first time
ooptions.setSelectedIndex(1);
ooptions.setSelectedIndex(0);
Object target = settingsButton.getClientProperty("targetTab");
if (target instanceof Integer) {
ooptions.setSelectedIndex((Integer) target);
// Clear the property so it doesn't open that tab every time thereafter
settingsButton.putClientProperty("targetTab", null);
}
settingsDialog.setVisible(true);
});
settingsPanel.add(settingsButton);
// Add components to the topPanel
topPanel.add(headingPanel, BorderLayout.CENTER);
topPanel.add(settingsPanel, BorderLayout.EAST);
// Add the combined topPanel to the main view
view.add(topPanel, BorderLayout.NORTH);
// Content Panel in the Center
JPanel contentPanel = new JPanel(new GridBagLayout());
contentPanel.setOpaque(false);
// Buttons Panel with Descriptions
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 50, 0));
buttonPanel.setOpaque(false);