Skip to content

Commit 88e95b0

Browse files
committed
Add HTMX modal upload and cert table refresh
Enable HTMX-driven certificate uploads and partial UI updates for LoTW. Updated Lotw controller to detect HTMX requests in do_cert_upload, return inline success/error alerts for modal uploads, and added cert_table_refresh endpoint to return the cert table partial. Extracted the certificate table into a new view (lotw_views/cert_table.php) and replaced the inline table in index.php with a container that loads that partial. Added a Bootstrap modal view (upload_cert_modal.php) with an HTMX multipart form, drag-and-drop file UI, JS handlers to manage upload state and refresh the certificate list, and modal reset behavior. Minor refactors to flash/success handling to support both full-page and HTMX flows.
1 parent 7f7d2a6 commit 88e95b0

4 files changed

Lines changed: 388 additions & 127 deletions

File tree

application/controllers/Lotw.php

Lines changed: 46 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public function cert_upload() {
9393
|
9494
| do_cert_upload is called from cert_upload form submit and handles uploading
9595
| and processing of p12 files and storing the data into mysql
96+
| Now supports HTMX requests for modal-based uploads
9697
|
9798
*/
9899
public function do_cert_upload()
@@ -106,6 +107,9 @@ public function do_cert_upload()
106107
echo "You must install php OpenSSL for LoTW functions to work";
107108
}
108109

110+
// Check if this is an HTMX request
111+
$is_htmx = $this->input->get_request_header('HX-Request');
112+
109113
// create folder to store certs while processing
110114
if (!file_exists('./uploads/lotw/certs')) {
111115
mkdir('./uploads/lotw/certs', 0755, true);
@@ -119,17 +123,21 @@ public function do_cert_upload()
119123
if ( ! $this->upload->do_upload('userfile'))
120124
{
121125
// Upload of P12 Failed
122-
$error = array('error' => $this->upload->display_errors());
126+
$error = $this->upload->display_errors();
123127

124-
// Load DXCC Countrys List
125-
$data['dxcc_list'] = $this->dxcc->list();
128+
// If HTMX request, return just the error message
129+
if($is_htmx) {
130+
echo '<div class="alert alert-danger" role="alert">' . $error . '</div>';
131+
return;
132+
}
126133

127-
// Set Page Title
134+
// Otherwise load the full form page
135+
$data = array('error' => $error);
136+
$data['dxcc_list'] = $this->dxcc->list();
128137
$data['page_title'] = "Logbook of the World";
129138

130-
// Load Views
131139
$this->load->view('interface_assets/header', $data);
132-
$this->load->view('lotw_views/upload_cert', $error);
140+
$this->load->view('lotw_views/upload_cert', $data);
133141
$this->load->view('interface_assets/footer');
134142
}
135143
else
@@ -152,20 +160,28 @@ public function do_cert_upload()
152160
$this->LotwCert->store_certificate($this->session->userdata('user_id'), $info['issued_callsign'], $info['dxcc-id'], $info['validFrom'], $info['validTo_Date'], $info['qso-first-date'], $info['qso-end-date'], $info['pem_key'], $info['general_cert']);
153161

154162
// Cert success flash message
155-
$this->session->set_flashdata('Success', $info['issued_callsign'].' Certificate Imported.');
163+
$success_msg = $info['issued_callsign'].' Certificate Imported.';
156164
} else {
157165
// Certificate is in the system time to update
158166

159167
$this->LotwCert->update_certificate($this->session->userdata('user_id'), $info['issued_callsign'], $info['dxcc-id'], $info['validFrom'], $info['validTo_Date'], $info['qso-first-date'], $info['qso-end-date'], $info['pem_key'], $info['general_cert']);
160168

161169
// Cert success flash message
162-
$this->session->set_flashdata('Success', $info['issued_callsign'].' Certificate Updated.');
163-
170+
$success_msg = $info['issued_callsign'].' Certificate Updated.';
164171
}
165172

166173
// p12 certificate processed time to delete the file
167174
unlink($data['upload_data']['full_path']);
168175

176+
// If HTMX request, return just the success message
177+
if($is_htmx) {
178+
echo '<div class="alert alert-success" role="alert">' . $success_msg . '</div>';
179+
return;
180+
}
181+
182+
// Otherwise load the full page
183+
$this->session->set_flashdata('Success', $success_msg);
184+
169185
// Get Array of the logged in users LoTW certs.
170186
$data['lotw_cert_results'] = $this->LotwCert->lotw_certs($this->session->userdata('user_id'));
171187

@@ -176,11 +192,30 @@ public function do_cert_upload()
176192
$this->load->view('interface_assets/header', $data);
177193
$this->load->view('lotw_views/index');
178194
$this->load->view('interface_assets/footer');
195+
}
196+
}
197+
198+
/*
199+
|--------------------------------------------------------------------------
200+
| Function: cert_table_refresh
201+
|--------------------------------------------------------------------------
202+
|
203+
| Returns the certificate table for HTMX refresh after upload
204+
|
205+
*/
206+
public function cert_table_refresh() {
207+
$this->load->model('user_model');
208+
if(!$this->user_model->authorize(2)) { $this->session->set_flashdata('notice', 'You\'re not allowed to do that!'); redirect('dashboard'); }
179209

210+
// Load model
211+
$this->load->model('LotwCert');
180212

213+
// Get Array of the logged in users LoTW certs.
214+
$data['lotw_cert_results'] = $this->LotwCert->lotw_certs($this->session->userdata('user_id'));
181215

182-
}
183-
}
216+
// Load just the cert table partial
217+
$this->load->view('lotw_views/cert_table', $data);
218+
}
184219

185220
/*
186221
|--------------------------------------------------------------------------
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<?php if(isset($error)) { ?>
2+
<div class="alert alert-danger" role="alert">
3+
<?php echo $error; ?>
4+
</div>
5+
<?php } ?>
6+
7+
<?php if(isset($_SESSION['Success'])) { ?>
8+
<div class="alert alert-success" role="alert">
9+
<?php echo $_SESSION['Success']; ?>
10+
</div>
11+
<?php unset($_SESSION['Success']); ?>
12+
<?php } ?>
13+
14+
<?php if ($lotw_cert_results->num_rows() > 0) { ?>
15+
16+
<div class="table-responsive">
17+
<table class="table table-hover">
18+
<thead class="thead-light">
19+
<tr>
20+
<th scope="col"><?php echo lang('gen_hamradio_callsign'); ?></th>
21+
<th scope="col"><?php echo lang('gen_hamradio_dxcc'); ?></th>
22+
<th scope="col"><?php echo lang('lotw_qso_start_date'); ?></th>
23+
<th scope="col"><?php echo lang('lotw_qso_end_date'); ?></th>
24+
<th scope="col"><?php echo lang('lotw_date_created'); ?></th>
25+
<th scope="col"><?php echo lang('lotw_date_expires'); ?></th>
26+
<th scope="col"><?php echo lang('lotw_status'); ?></th>
27+
<th scope="col"><?php echo lang('lotw_options'); ?></th>
28+
</tr>
29+
</thead>
30+
31+
<tbody>
32+
33+
<?php foreach ($lotw_cert_results->result() as $row) { ?>
34+
<tr>
35+
<td><?php echo $row->callsign; ?></td>
36+
<td><?php echo $row->cert_dxcc == '' ? '- NONE -' : ucfirst($row->cert_dxcc); if ($row->cert_dxcc_end != NULL) { echo ' <span class="badge text-bg-danger">'.lang('gen_hamradio_deleted_dxcc').'</span>'; } ?></td>
37+
<td><?php
38+
if (isset($row->qso_start_date)) {
39+
$valid_qso_start = strtotime( $row->qso_start_date );
40+
$new_valid_qso_start = date($this->config->item('qso_date_format'), $valid_qso_start );
41+
echo $new_valid_qso_start;
42+
} else {
43+
echo "n/a";
44+
} ?>
45+
</td>
46+
<td><?php
47+
if (isset($row->qso_end_date)) {
48+
$valid_qso_end = strtotime( $row->qso_end_date );
49+
$new_valid_qso_end = date($this->config->item('qso_date_format'), $valid_qso_end );
50+
echo $new_valid_qso_end;
51+
} else {
52+
echo "n/a";
53+
} ?>
54+
</td>
55+
<td><?php
56+
$valid_from = strtotime( $row->date_created );
57+
$new_valid_from = date($this->config->item('qso_date_format'), $valid_from );
58+
echo $new_valid_from; ?>
59+
</td>
60+
<td>
61+
<?php
62+
$valid_to = strtotime( $row->date_expires );
63+
$new_valid_to = date($this->config->item('qso_date_format'), $valid_to );
64+
echo $new_valid_to; ?>
65+
</td>
66+
<td>
67+
<?php $current_date = date('Y-m-d H:i:s'); ?>
68+
<?php $warning_date = date('Y-m-d H:i:s', strtotime($row->date_expires.'-30 days')); ?>
69+
70+
<?php if ($row->archived) { ?>
71+
<span class="badge text-bg-secondary"><?php echo 'Archived'; ?></span>
72+
<?php } else { ?>
73+
<?php if ($current_date > $row->date_expires) { ?>
74+
<span class="badge text-bg-danger"><?php echo lang('lotw_expired'); ?></span>
75+
<?php } else if ($current_date <= $row->date_expires && $current_date > $warning_date) { ?>
76+
<span class="badge text-bg-warning"><?php echo lang('lotw_expiring'); ?></span>
77+
<?php } else { ?>
78+
<span class="badge text-bg-success"><?php echo lang('lotw_valid'); ?></span>
79+
<?php } ?>
80+
<?php } ?>
81+
82+
<?php if ($row->last_upload) {
83+
$last_upload = date($this->config->item('qso_date_format').' H:i:s', strtotime( $row->last_upload )); ?>
84+
<span class="badge text-bg-success"><?php echo $last_upload; ?></span>
85+
<?php } else { ?>
86+
<span class="badge text-bg-warning"><?php echo lang('lotw_not_synced'); ?></span>
87+
<?php } ?>
88+
</td>
89+
<td>
90+
<?php if ($row->archived) { ?>
91+
<a class="btn btn-outline-info btn-sm me-1" href="<?php echo site_url('lotw/toggle_archive_cert/'.$row->lotw_cert_id); ?>" role="button">
92+
<i class="fas fa-box-open"></i> Unarchive
93+
</a>
94+
<?php } else { ?>
95+
<a class="btn btn-outline-secondary btn-sm me-1" href="<?php echo site_url('lotw/toggle_archive_cert/'.$row->lotw_cert_id); ?>" role="button">
96+
<i class="fas fa-archive"></i> Archive
97+
</a>
98+
<?php } ?>
99+
<a class="btn btn-outline-danger btn-sm" href="<?php echo site_url('lotw/delete_cert/'.$row->lotw_cert_id); ?>" role="button"><i class="far fa-trash-alt"></i> <?php echo lang('lotw_btn_delete'); ?></a>
100+
</td>
101+
</tr>
102+
<?php } ?>
103+
104+
</tbody>
105+
</table>
106+
</div>
107+
108+
<?php } else { ?>
109+
<div class="alert alert-info" role="alert">
110+
<?php echo lang('lotw_no_certs_uploaded'); ?>
111+
</div>
112+
<?php } ?>

application/views/lotw_views/index.php

Lines changed: 10 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -6,129 +6,19 @@
66
<!-- Card Starts -->
77
<div class="card">
88
<div class="card-header">
9-
<a class="btn btn-outline-success btn-sm float-end" href="<?php echo site_url('/lotw/cert_upload'); ?>" role="button"><i class="fas fa-cloud-upload-alt"></i> <?php echo lang('lotw_btn_upload_certificate'); ?></a><i class="fab fa-expeditedssl"></i> <?php echo lang('lotw_title_available_cert'); ?>
9+
<button class="btn btn-outline-success btn-sm float-end" data-bs-toggle="modal" data-bs-target="#uploadCertModal" role="button"><i class="fas fa-cloud-upload-alt"></i> <?php echo lang('lotw_btn_upload_certificate'); ?></button><i class="fab fa-expeditedssl"></i> <?php echo lang('lotw_title_available_cert'); ?>
1010
</div>
1111

12-
<div class="lotw-cert-list">
13-
<?php if(isset($error)) { ?>
14-
<div class="alert alert-danger" role="alert">
15-
<?php echo $error; ?>
16-
</div>
17-
<?php } ?>
18-
19-
<?php if(isset($_SESSION['Success'])) { ?>
20-
<div class="alert alert-success" role="alert">
21-
<?php echo $_SESSION['Success']; ?>
22-
</div>
23-
<?php } ?>
24-
25-
<?php if ($lotw_cert_results->num_rows() > 0) { ?>
26-
27-
<div class="table-responsive">
28-
<table class="table table-hover">
29-
<thead class="thead-light">
30-
<tr>
31-
<th scope="col"><?php echo lang('gen_hamradio_callsign'); ?></th>
32-
<th scope="col"><?php echo lang('gen_hamradio_dxcc'); ?></th>
33-
<th scope="col"><?php echo lang('lotw_qso_start_date'); ?></th>
34-
<th scope="col"><?php echo lang('lotw_qso_end_date'); ?></th>
35-
<th scope="col"><?php echo lang('lotw_date_created'); ?></th>
36-
<th scope="col"><?php echo lang('lotw_date_expires'); ?></th>
37-
<th scope="col"><?php echo lang('lotw_status'); ?></th>
38-
<th scope="col"><?php echo lang('lotw_options'); ?></th>
39-
</tr>
40-
</thead>
41-
42-
<tbody>
43-
44-
<?php foreach ($lotw_cert_results->result() as $row) { ?>
45-
<tr>
46-
<td><?php echo $row->callsign; ?></td>
47-
<td><?php echo $row->cert_dxcc == '' ? '- NONE -' : ucfirst($row->cert_dxcc); if ($row->cert_dxcc_end != NULL) { echo ' <span class="badge text-bg-danger">'.lang('gen_hamradio_deleted_dxcc').'</span>'; } ?></td>
48-
<td><?php
49-
if (isset($row->qso_start_date)) {
50-
$valid_qso_start = strtotime( $row->qso_start_date );
51-
$new_valid_qso_start = date($this->config->item('qso_date_format'), $valid_qso_start );
52-
echo $new_valid_qso_start;
53-
} else {
54-
echo "n/a";
55-
} ?>
56-
</td>
57-
<td><?php
58-
if (isset($row->qso_end_date)) {
59-
$valid_qso_end = strtotime( $row->qso_end_date );
60-
$new_valid_qso_end = date($this->config->item('qso_date_format'), $valid_qso_end );
61-
echo $new_valid_qso_end;
62-
} else {
63-
echo "n/a";
64-
} ?>
65-
</td>
66-
<td><?php
67-
$valid_from = strtotime( $row->date_created );
68-
$new_valid_from = date($this->config->item('qso_date_format'), $valid_from );
69-
echo $new_valid_from; ?>
70-
</td>
71-
<td>
72-
<?php
73-
$valid_to = strtotime( $row->date_expires );
74-
$new_valid_to = date($this->config->item('qso_date_format'), $valid_to );
75-
echo $new_valid_to; ?>
76-
</td>
77-
<td>
78-
<?php $current_date = date('Y-m-d H:i:s'); ?>
79-
<?php $warning_date = date('Y-m-d H:i:s', strtotime($row->date_expires.'-30 days')); ?>
80-
81-
<?php if ($row->archived) { ?>
82-
<span class="badge text-bg-secondary"><?php echo 'Archived'; ?></span>
83-
<?php } else { ?>
84-
<?php if ($current_date > $row->date_expires) { ?>
85-
<span class="badge text-bg-danger"><?php echo lang('lotw_expired'); ?></span>
86-
<?php } else if ($current_date <= $row->date_expires && $current_date > $warning_date) { ?>
87-
<span class="badge text-bg-warning"><?php echo lang('lotw_expiring'); ?></span>
88-
<?php } else { ?>
89-
<span class="badge text-bg-success"><?php echo lang('lotw_valid'); ?></span>
90-
<?php } ?>
91-
<?php } ?>
92-
93-
<?php if ($row->last_upload) {
94-
$last_upload = date($this->config->item('qso_date_format').' H:i:s', strtotime( $row->last_upload )); ?>
95-
<span class="badge text-bg-success"><?php echo $last_upload; ?></span>
96-
<?php } else { ?>
97-
<span class="badge text-bg-warning"><?php echo lang('lotw_not_synced'); ?></span>
98-
<?php } ?>
99-
</td>
100-
<td>
101-
<?php if ($row->archived) { ?>
102-
<a class="btn btn-outline-info btn-sm me-1" href="<?php echo site_url('lotw/toggle_archive_cert/'.$row->lotw_cert_id); ?>" role="button">
103-
<i class="fas fa-box-open"></i> Unarchive
104-
</a>
105-
<?php } else { ?>
106-
<a class="btn btn-outline-secondary btn-sm me-1" href="<?php echo site_url('lotw/toggle_archive_cert/'.$row->lotw_cert_id); ?>" role="button">
107-
<i class="fas fa-archive"></i> Archive
108-
</a>
109-
<?php } ?>
110-
<a class="btn btn-outline-danger btn-sm" href="<?php echo site_url('lotw/delete_cert/'.$row->lotw_cert_id); ?>" role="button"><i class="far fa-trash-alt"></i> <?php echo lang('lotw_btn_delete'); ?></a>
111-
</td>
112-
</tr>
113-
<?php } ?>
114-
115-
</tbody>
116-
</table>
117-
</div>
118-
119-
<?php } else { ?>
120-
<div class="alert alert-info" role="alert">
121-
<?php echo lang('lotw_no_certs_uploaded'); ?>
122-
</div>
123-
<?php } ?>
124-
125-
</div>
12+
<div class="lotw-cert-list" id="lotw-cert-list">
13+
<?php $this->load->view('lotw_views/cert_table', array('lotw_cert_results' => $lotw_cert_results)); ?>
14+
</div>
12615
</div>
12716
<!-- Card Ends -->
12817

12918
<br>
13019

131-
<!-- Card Starts -->
20+
<!-- Information Card - Only show if certificates exist -->
21+
<?php if ($lotw_cert_results->num_rows() > 0) { ?>
13222
<div class="card">
13323
<div class="card-header">
13424
<?php echo lang('lotw_title_information'); ?>
@@ -146,9 +36,13 @@
14636
<div id="lotw_manual_results"></div>
14737
</div>
14838
</div>
39+
<?php } ?>
14940

15041
</div>
15142

43+
<!-- Load Certificate Upload Modal -->
44+
<?php $this->load->view('lotw_views/upload_cert_modal'); ?>
45+
15246
<script>
15347
document.addEventListener('DOMContentLoaded', function() {
15448
const syncBtn = document.getElementById('manual-sync-btn');

0 commit comments

Comments
 (0)