Skip to content

Commit 2ec6377

Browse files
committed
fix: unintended multiple deployments
1 parent 5842d88 commit 2ec6377

File tree

4 files changed

+152
-106
lines changed

4 files changed

+152
-106
lines changed

admin/views/deploy-page.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
<div class="wrap">
2-
<h1>Site Deployment Manager</h1>
1+
<div class="wrap deploy-manager">
2+
<h1 style="margin-bottom: 12px;">Site Deployment Manager</h1>
33

4-
<div class="deploy-actions">
5-
<button id="trigger-deploy" class="button button-primary">
6-
Trigger Deployment
7-
</button>
8-
<div id="deploy-status"></div>
4+
<div class="deploy-section">
5+
<div class="deploy-card">
6+
<h2>Deploy Your Site</h2>
7+
<p>Click the button below to trigger a new deployment of your static site.</p>
8+
<button id="trigger-deploy" class="deploy-button">
9+
<span class="button-text">Deploy Site</span>
10+
<div class="loader"></div>
11+
</button>
12+
</div>
913
</div>
1014

1115
<div class="deploy-logs">

assets/js/deploy-manager.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1-
document.addEventListener('DOMContentLoaded', function() {
1+
document.addEventListener('DOMContentLoaded', function () {
22
const deployButton = document.getElementById('trigger-deploy');
3-
43
if (!deployButton) return;
54

6-
deployButton.addEventListener('click', async function() {
7-
// Disable button and show loader
5+
let isDeploying = false; // Ensures only one deployment at a time
6+
7+
8+
deployButton.addEventListener('click', async function (e) {
9+
10+
e.preventDefault();
11+
e.stopPropagation();
12+
13+
if (isDeploying) {
14+
return;
15+
}
16+
17+
isDeploying = true;
818
deployButton.classList.add('loading');
919
deployButton.disabled = true;
1020

@@ -15,7 +25,11 @@ document.addEventListener('DOMContentLoaded', function() {
1525

1626
const response = await fetch(deployManagerData.ajaxurl, {
1727
method: 'POST',
18-
body: formData
28+
body: formData,
29+
cache: 'no-cache',
30+
headers: {
31+
'Cache-Control': 'no-cache',
32+
}
1933
});
2034

2135
const data = await response.json();
@@ -33,6 +47,7 @@ document.addEventListener('DOMContentLoaded', function() {
3347
showNotification('error', 'Error connecting to the server');
3448
} finally {
3549
// Re-enable button and hide loader
50+
isDeploying = false;
3651
deployButton.classList.remove('loading');
3752
deployButton.disabled = false;
3853
}

includes/class-deploy-manager.php

Lines changed: 104 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,37 @@
33
// Prevent direct access
44
defined('ABSPATH') || exit;
55

6-
class Site_Deploy_Manager {
7-
6+
class Site_Deploy_Manager
7+
{
8+
89
private static $instance = null;
910

1011
/**
1112
* Constructor
1213
*/
13-
public function __construct() {
14-
add_action('admin_menu', array($this, 'deploy_manager_menu'));
14+
public function __construct()
15+
{
16+
add_action('admin_menu', array($this, 'setup_admin_menus'));
1517
add_action('admin_enqueue_scripts', array($this, 'deploy_manager_scripts'));
1618
add_action('wp_ajax_trigger_deployment', array($this, 'handle_deployment'));
1719
add_action('admin_init', array($this, 'register_settings'));
18-
add_action('admin_menu', array($this, 'add_settings_page'));
1920
}
2021

21-
/**
22-
* Initialize the plugin
23-
*/
24-
public function init() {
25-
register_activation_hook(__FILE__, array($this, 'deploy_manager_install'));
26-
}
2722

28-
public function add_settings_page() {
23+
public function setup_admin_menus()
24+
{
25+
// Add main menu
26+
add_menu_page(
27+
'Deploy Site',
28+
'Deploy',
29+
'manage_options',
30+
'deploy-manager',
31+
array($this, 'deploy_manager_page'),
32+
'dashicons-upload',
33+
100
34+
);
35+
36+
// Add settings submenu
2937
add_submenu_page(
3038
'deploy-manager',
3139
'Deploy Settings',
@@ -36,9 +44,10 @@ public function add_settings_page() {
3644
);
3745
}
3846

39-
public function register_settings() {
47+
public function register_settings()
48+
{
4049
register_setting('deploy_manager_settings', 'site_deploy_webhook_url');
41-
50+
4251
add_settings_section(
4352
'deploy_manager_main',
4453
'Deployment Settings',
@@ -55,13 +64,15 @@ public function register_settings() {
5564
);
5665
}
5766

58-
public function webhook_url_callback() {
67+
public function webhook_url_callback()
68+
{
5969
$webhook_url = get_option('site_deploy_webhook_url');
6070
echo '<input type="url" name="site_deploy_webhook_url" value="' . esc_attr($webhook_url) . '" class="regular-text">';
6171
}
6272

63-
public function render_settings_page() {
64-
?>
73+
public function render_settings_page()
74+
{
75+
?>
6576
<div class="wrap">
6677
<h1>Deploy Settings</h1>
6778
<form method="post" action="options.php">
@@ -72,48 +83,34 @@ public function render_settings_page() {
7283
?>
7384
</form>
7485
</div>
75-
<?php
76-
}
77-
78-
/**
79-
* Add admin menu item
80-
*/
81-
public function deploy_manager_menu() {
82-
add_menu_page(
83-
'Deploy Site',
84-
'Deploy',
85-
'manage_options',
86-
'deploy-manager',
87-
array($this, 'deploy_manager_page'),
88-
'dashicons-upload',
89-
100
90-
);
86+
<?php
9187
}
9288

9389
/**
9490
* Create database table on activation
9591
*/
96-
public function deploy_manager_install() {
92+
public function deploy_manager_install()
93+
{
9794
global $wpdb;
9895
$table_name = $wpdb->prefix . 'deployment_logs';
99-
96+
10097
// Check if table exists first
101-
if($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
98+
if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
10299
$charset_collate = $wpdb->get_charset_collate();
103-
100+
104101
$sql = "CREATE TABLE $table_name (
105102
id mediumint(9) NOT NULL AUTO_INCREMENT,
106103
deploy_time datetime DEFAULT CURRENT_TIMESTAMP,
107104
status varchar(20) NOT NULL,
108105
response_message text,
109106
PRIMARY KEY (id)
110107
) $charset_collate;";
111-
108+
112109
require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
113110
dbDelta($sql);
114-
111+
115112
// Verify table was created
116-
if($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
113+
if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
117114
error_log('Failed to create deployment logs table');
118115
}
119116
}
@@ -122,22 +119,25 @@ public function deploy_manager_install() {
122119
/**
123120
* Enqueue scripts and styles
124121
*/
125-
public function deploy_manager_scripts($hook) {
126-
if($hook != 'toplevel_page_deploy-manager') return;
127-
128-
wp_enqueue_script('deploy-manager-js',
129-
SITE_DEPLOY_PLUGIN_URL . 'assets/js/deploy-manager.js',
130-
array(),
131-
SITE_DEPLOY_VERSION,
122+
public function deploy_manager_scripts($hook)
123+
{
124+
if ($hook != 'toplevel_page_deploy-manager') return;
125+
126+
wp_enqueue_script(
127+
'deploy-manager-js',
128+
SITE_DEPLOY_PLUGIN_URL . 'assets/js/deploy-manager.js',
129+
array(),
130+
SITE_DEPLOY_VERSION,
132131
true
133132
);
134-
133+
135134
wp_localize_script('deploy-manager-js', 'deployManagerData', array(
136135
'ajaxurl' => admin_url('admin-ajax.php'),
137136
'nonce' => wp_create_nonce('deploy-manager-nonce')
138137
));
139-
140-
wp_enqueue_style('deploy-manager-css',
138+
139+
wp_enqueue_style(
140+
'deploy-manager-css',
141141
SITE_DEPLOY_PLUGIN_URL . 'assets/css/deploy-manager.css',
142142
array(),
143143
SITE_DEPLOY_VERSION
@@ -147,62 +147,79 @@ public function deploy_manager_scripts($hook) {
147147
/**
148148
* Handle the deployment AJAX request
149149
*/
150-
public function handle_deployment() {
151-
check_ajax_referer('deploy-manager-nonce', 'nonce');
152-
153-
if (!current_user_can('manage_options')) {
154-
wp_send_json_error('Unauthorized access');
155-
}
156-
157-
$webhook_url = get_option('site_deploy_webhook_url', '');
158-
159-
if (empty($webhook_url)) {
160-
wp_send_json_error('Webhook URL not configured');
150+
public function handle_deployment()
151+
{
152+
$lock_key = 'deploy_request_lock';
153+
154+
if (get_transient($lock_key)) {
155+
wp_send_json_error('Request already in progress');
156+
return;
161157
}
162-
163-
$response = wp_remote_post($webhook_url, array(
164-
'method' => 'POST',
165-
'timeout' => 45,
166-
'headers' => array('Content-Type' => 'application/json'),
167-
'body' => json_encode(array('trigger' => 'manual'))
168-
));
169-
170-
global $wpdb;
171-
$table_name = $wpdb->prefix . 'deployment_logs';
172-
173-
if (is_wp_error($response)) {
174-
$wpdb->insert($table_name, array(
175-
'status' => 'failed',
176-
'response_message' => $response->get_error_message()
177-
));
178-
wp_send_json_error($response->get_error_message());
179-
} else {
180-
$wpdb->insert($table_name, array(
181-
'status' => 'success',
182-
'response_message' => wp_remote_retrieve_body($response)
158+
159+
// Set 60-second lock
160+
set_transient($lock_key, true, 60);
161+
162+
try {
163+
check_ajax_referer('deploy-manager-nonce', 'nonce');
164+
165+
if (!current_user_can('manage_options')) {
166+
wp_send_json_error('Unauthorized access');
167+
}
168+
169+
$webhook_url = get_option('site_deploy_webhook_url', '');
170+
171+
if (empty($webhook_url)) {
172+
wp_send_json_error('Webhook URL not configured');
173+
}
174+
175+
$response = wp_remote_post($webhook_url, array(
176+
'method' => 'POST',
177+
'timeout' => 45,
178+
'headers' => array('Content-Type' => 'application/json'),
179+
'body' => json_encode(array('trigger' => 'manual'))
183180
));
184-
wp_send_json_success('Deployment triggered successfully');
181+
182+
global $wpdb;
183+
$table_name = $wpdb->prefix . 'deployment_logs';
184+
185+
if (is_wp_error($response)) {
186+
$wpdb->insert($table_name, array(
187+
'status' => 'failed',
188+
'response_message' => $response->get_error_message()
189+
));
190+
wp_send_json_error($response->get_error_message());
191+
} else {
192+
$wpdb->insert($table_name, array(
193+
'status' => 'success',
194+
'response_message' => wp_remote_retrieve_body($response)
195+
));
196+
wp_send_json_success('Deployment triggered successfully');
197+
}
198+
} finally {
199+
delete_transient($lock_key);
185200
}
186201
}
187202

188203
/**
189204
* Render the admin page
190205
*/
191-
public function deploy_manager_page() {
206+
public function deploy_manager_page()
207+
{
192208
global $wpdb;
193209
$table_name = $wpdb->prefix . 'deployment_logs';
194210
$logs = $wpdb->get_results("SELECT * FROM $table_name ORDER BY deploy_time DESC LIMIT 10");
195-
211+
196212
include SITE_DEPLOY_PLUGIN_DIR . 'admin/views/deploy-page.php';
197213
}
198214

199215
/**
200216
* Get singleton instance
201217
*/
202-
public static function get_instance() {
218+
public static function get_instance()
219+
{
203220
if (null === self::$instance) {
204221
self::$instance = new self();
205222
}
206223
return self::$instance;
207224
}
208-
}
225+
}

0 commit comments

Comments
 (0)