mirror of
https://gh.llkk.cc/https://github.com/CaptainCore/captaincore-manager.git
synced 2025-10-03 14:04:44 +08:00
📦 NEW: Historical Captures feature
This commit is contained in:
parent
7c3b192af6
commit
9c0dc73b0f
5 changed files with 304 additions and 12 deletions
9
app/Captures.php
Normal file
9
app/Captures.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace CaptainCore;
|
||||
|
||||
class Captures extends DB {
|
||||
|
||||
static $primary_key = 'capture_id';
|
||||
|
||||
}
|
21
app/DB.php
21
app/DB.php
|
@ -159,11 +159,11 @@ class DB {
|
|||
}
|
||||
|
||||
// Perform CaptainCore database upgrades by running `CaptainCore\DB::upgrade();`
|
||||
public static function upgrade() {
|
||||
$required_version = 17;
|
||||
public static function upgrade( $force = false ) {
|
||||
$required_version = 18;
|
||||
$version = (int) get_site_option( 'captaincore_db_version' );
|
||||
|
||||
if ( $version >= $required_version ) {
|
||||
if ( $version >= $required_version and $force != true ) {
|
||||
echo "Not needed `captaincore_db_version` is v{$version} and required v{$required_version}.";
|
||||
}
|
||||
|
||||
|
@ -181,6 +181,18 @@ class DB {
|
|||
PRIMARY KEY (log_id)
|
||||
) $charset_collate;";
|
||||
|
||||
dbDelta($sql);
|
||||
|
||||
$sql = "CREATE TABLE `{$wpdb->base_prefix}captaincore_captures` (
|
||||
capture_id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
site_id bigint(20) UNSIGNED NOT NULL,
|
||||
environment_id bigint(20) UNSIGNED NOT NULL,
|
||||
created_at datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
git_commit varchar(100),
|
||||
pages longtext,
|
||||
PRIMARY KEY (capture_id)
|
||||
) $charset_collate;";
|
||||
|
||||
dbDelta($sql);
|
||||
|
||||
$sql = "CREATE TABLE `{$wpdb->base_prefix}captaincore_quicksaves` (
|
||||
|
@ -241,6 +253,7 @@ class DB {
|
|||
core varchar(10),
|
||||
subsite_count varchar(10),
|
||||
home_url varchar(255),
|
||||
capture_pages longtext,
|
||||
themes longtext,
|
||||
plugins longtext,
|
||||
users longtext,
|
||||
|
@ -291,7 +304,7 @@ class DB {
|
|||
|
||||
dbDelta($sql);
|
||||
|
||||
// Permission/relationships data stucture for CaptainCore: https://dbdiagram.io/d/5d7d409283427516dc0ba8b3
|
||||
// Permission/relationships data structure for CaptainCore: https://dbdiagram.io/d/5d7d409283427516dc0ba8b3
|
||||
$sql = "CREATE TABLE `{$wpdb->base_prefix}captaincore_accounts` (
|
||||
account_id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
name varchar(255),
|
||||
|
|
26
app/Site.php
26
app/Site.php
|
@ -162,6 +162,7 @@ class Site {
|
|||
'update_logs' => 'Loading',
|
||||
'quicksave_panel' => [],
|
||||
'quicksave_search' => '',
|
||||
'capture_pages' => json_decode ( $environments[0]->capture_pages ),
|
||||
'core' => $environments[0]->core,
|
||||
'home_url' => $environments[0]->home_url,
|
||||
'updates_enabled' => intval( $environments[0]->updates_enabled ),
|
||||
|
@ -255,6 +256,7 @@ class Site {
|
|||
'update_logs' => 'Loading',
|
||||
'quicksave_panel' => [],
|
||||
'quicksave_search' => '',
|
||||
'capture_pages' => json_decode ( $environments[1]->capture_pages ),
|
||||
'core' => $environments[1]->core,
|
||||
'home_url' => $environments[1]->home_url,
|
||||
'updates_enabled' => intval( $environments[1]->updates_enabled ),
|
||||
|
@ -618,4 +620,28 @@ class Site {
|
|||
|
||||
}
|
||||
|
||||
public function captures( $environment = "production" ) {
|
||||
|
||||
if ( $environment == "production" ) {
|
||||
$environment_id = get_field( 'environment_production_id', $this->site_id );
|
||||
}
|
||||
if ( $environment == "staging" ) {
|
||||
$environment_id = get_field( 'environment_staging_id', $this->site_id );
|
||||
}
|
||||
|
||||
$captures = new Captures();
|
||||
$results = $captures->where( [ "site_id" => $this->site_id, "environment_id" => $environment_id ] );
|
||||
|
||||
foreach ( $results as $result ) {
|
||||
$created_at_friendly = new \DateTime( $result->created_at );
|
||||
$created_at_friendly->setTimezone( new \DateTimeZone( get_option( 'gmt_offset' ) ) );
|
||||
$created_at_friendly = date_format( $created_at_friendly, 'D, M jS Y g:i a');
|
||||
$result->created_at_friendly = $created_at_friendly;
|
||||
$result->pages = json_decode( $result->pages );
|
||||
}
|
||||
|
||||
return $results;
|
||||
|
||||
}
|
||||
|
||||
}
|
114
captaincore.php
114
captaincore.php
|
@ -1591,6 +1591,74 @@ function captaincore_api_func( WP_REST_Request $request ) {
|
|||
}
|
||||
}
|
||||
|
||||
// Add capture
|
||||
if ( $command == 'new-capture' ) {
|
||||
|
||||
print_r( $data );
|
||||
|
||||
if ( $environment == "production" ) {
|
||||
$environment_id = get_field( 'environment_production_id', $site_id );
|
||||
}
|
||||
if ( $environment == "staging" ) {
|
||||
$environment_id = get_field( 'environment_staging_id', $site_id );
|
||||
}
|
||||
|
||||
$captures = new CaptainCore\Captures();
|
||||
$capture_lookup = $captures->where( [ "site_id" => $site_id, "environment_id" => $environment_id ] );
|
||||
if ( count( $capture_lookup ) > 0 ) {
|
||||
$current_capture_pages = json_decode( $capture_lookup[0]->pages );
|
||||
}
|
||||
|
||||
$git_commit_short = substr( $data->git_commit, 0, 7 );
|
||||
$image_ending = "_{$data->created_at}_{$git_commit_short}.jpg";
|
||||
$capture_pages = explode( ",", $data->capture_pages );
|
||||
$captured_pages = explode( ",", $data->captured_pages );
|
||||
$pages = [];
|
||||
foreach( $capture_pages as $page ) {
|
||||
$page_name = str_replace( "/", "#", $page );
|
||||
|
||||
// Add page with new screenshot
|
||||
if ( in_array( $page, $captured_pages ) ) {
|
||||
$pages[] = [
|
||||
"name" => $page,
|
||||
"image" => "{$page_name}{$image_ending}",
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Lookup current image from DB
|
||||
$current_image = "";
|
||||
foreach($current_capture_pages as $current_capture_page) {
|
||||
if ($page == $current_capture_page->name) {
|
||||
$current_image = $current_capture_page->image;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise add image to current screenshot
|
||||
$pages[] = [
|
||||
"name" => $page,
|
||||
"image" => $current_image,
|
||||
];
|
||||
}
|
||||
|
||||
// Format for mysql timestamp format. Changes "1530817828" to "2018-06-20 09:15:20"
|
||||
$epoch = $data->created_at;
|
||||
$created_at = new DateTime("@$epoch"); // convert UNIX timestamp to PHP DateTime
|
||||
$created_at = $created_at->format('Y-m-d H:i:s'); // output = 2017-01-01 00:00:00
|
||||
|
||||
$new_capture = array(
|
||||
'site_id' => $site_id,
|
||||
'environment_id' => $environment_id,
|
||||
'created_at' => $created_at,
|
||||
'git_commit' => $data->git_commit,
|
||||
'pages' => json_encode( $pages ),
|
||||
);
|
||||
|
||||
$capture = new CaptainCore\Captures();
|
||||
$capture->insert( $new_capture );
|
||||
}
|
||||
|
||||
// Imports update log
|
||||
if ( $command == 'import-quicksaves' ) {
|
||||
|
||||
|
@ -1825,6 +1893,13 @@ function captaincore_site_snapshot_download_func( $request ) {
|
|||
exit;
|
||||
}
|
||||
|
||||
function captaincore_site_captures_func( $request ) {
|
||||
$site_id = $request['id'];
|
||||
$environment = $request['environment'];
|
||||
$site = new CaptainCore\Site( $site_id );
|
||||
return $site->captures( $environment );
|
||||
}
|
||||
|
||||
function captaincore_site_quicksaves_func( $request ) {
|
||||
$site_id = $request['id'];
|
||||
|
||||
|
@ -2102,7 +2177,7 @@ function captaincore_register_rest_endpoints() {
|
|||
)
|
||||
);
|
||||
|
||||
// Custom endpoint for CaptainCore site
|
||||
// Custom endpoint for CaptainCore site/<id>/quicksaves
|
||||
register_rest_route(
|
||||
'captaincore/v1', '/site/(?P<id>[\d]+)/quicksaves', array(
|
||||
'methods' => 'GET',
|
||||
|
@ -2111,7 +2186,16 @@ function captaincore_register_rest_endpoints() {
|
|||
)
|
||||
);
|
||||
|
||||
// Custom endpoint for CaptainCore site
|
||||
// Custom endpoint for CaptainCore site/<id>/captures
|
||||
register_rest_route(
|
||||
'captaincore/v1', '/site/(?P<id>[\d]+)/(?P<environment>[a-zA-Z0-9-]+)/captures', array(
|
||||
'methods' => 'GET',
|
||||
'callback' => 'captaincore_site_captures_func',
|
||||
'show_in_index' => false
|
||||
)
|
||||
);
|
||||
|
||||
// Custom endpoint for CaptainCore site/<id>/snapshots
|
||||
register_rest_route(
|
||||
'captaincore/v1', '/site/(?P<id>[\d]+)/snapshots', array(
|
||||
'methods' => 'GET',
|
||||
|
@ -4043,6 +4127,32 @@ function captaincore_ajax_action_callback() {
|
|||
|
||||
}
|
||||
|
||||
if ( $cmd == 'updateCapturePages' ) {
|
||||
|
||||
$db_environments = new CaptainCore\Environments();
|
||||
$value_json = json_encode($value);
|
||||
|
||||
// Saves update settings for a site
|
||||
$environment_update = array(
|
||||
'capture_pages' => $value_json,
|
||||
);
|
||||
$environment_update['updated_at'] = date("Y-m-d H:i:s");
|
||||
|
||||
if ($environment == "Production") {
|
||||
$environment_id = get_field( 'environment_production_id', $post_id );
|
||||
$db_environments->update( $environment_update, [ "environment_id" => $environment_id ] );
|
||||
}
|
||||
if ($environment == "Staging") {
|
||||
$environment_id = get_field( 'environment_staging_id', $post_id );
|
||||
$db_environments->update( $environment_update, [ "environment_id" => $environment_id ] );
|
||||
}
|
||||
|
||||
// Remote Sync
|
||||
$remote_command = true;
|
||||
$command = "site update-field $site capture_pages $value_json";
|
||||
|
||||
}
|
||||
|
||||
if ( $cmd == 'fetchLink' ) {
|
||||
// Fetch snapshot details
|
||||
$db = new CaptainCore\Snapshots;
|
||||
|
|
|
@ -1614,17 +1614,89 @@ if ( $role_check ) {
|
|||
<v-container>
|
||||
<v-layout row wrap>
|
||||
<v-flex xs12 pa-2>
|
||||
<span>Will turn off search privacy and update development urls to the following live urls.</span><br /><br />
|
||||
<v-text-field label="Domain" prefix="https://" :value="dialog_launch.domain" @change.native="dialog_launch.domain = $event.target.value"></v-text-field>
|
||||
<v-btn @click="launchSite()">
|
||||
Launch Site
|
||||
</v-btn>
|
||||
<span>Will turn off search privacy and update development urls to the following live urls.</span><br /><br />
|
||||
<v-text-field label="Domain" prefix="https://" :value="dialog_launch.domain" @change.native="dialog_launch.domain = $event.target.value"></v-text-field>
|
||||
<v-btn @click="launchSite()">
|
||||
Launch Site
|
||||
</v-btn>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog
|
||||
v-model="dialog_captures.show"
|
||||
scrollable
|
||||
>
|
||||
<v-card tile>
|
||||
<v-toolbar flat dark color="primary">
|
||||
<v-btn icon dark @click.native="dialog_captures.show = false">
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title>Historical Captures of {{ dialog_captures.site.name }}</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-toolbar-items>
|
||||
<!--<v-select
|
||||
dense
|
||||
v-model="dialog_captures.mode"
|
||||
:items='[{"text":"Screenshot Mode","value":"screenshot","icon":"mdi-eye"},{"text":"Code Mode","value":"code","icon":"mdi-code-tags"}]'
|
||||
label=""
|
||||
class="mt-5"
|
||||
style="max-width:200px;"
|
||||
>-->
|
||||
<template v-slot:selection="data">
|
||||
<div class="v-list-item__title"><v-icon class="mr-2">{{ data.item.icon }}</v-icon> {{ data.item.text }}</div>
|
||||
</template>
|
||||
<template v-slot:item="data">
|
||||
<div class="v-list-item__title"><v-icon class="mr-2">{{ data.item.icon }}</v-icon> {{ data.item.text }}</div>
|
||||
</template>
|
||||
</v-select>
|
||||
</v-toolbar-items>
|
||||
</v-toolbar>
|
||||
<v-toolbar color="grey lighten-4" light flat v-if="dialog_captures.captures.length > 0">
|
||||
<div style="max-width:250px;" class="mx-1 mt-8">
|
||||
<v-select v-model="dialog_captures.capture" dense :items="dialog_captures.captures" item-text="created_at_friendly" item-value="capture_id" label="Taken On" return-object></v-select>
|
||||
</div>
|
||||
<div style="max-width:150px;" class="mx-1 mt-8">
|
||||
<v-select v-model="dialog_captures.selected_page" dense :items="dialog_captures.capture.pages" item-text="name" item-value="name" value="/" :label="`Contains ${dialog_captures.capture.pages.length} ${dialogCapturesPagesText}`" return-object></v-select>
|
||||
</div>
|
||||
<v-spacer></v-spacer>
|
||||
<v-toolbar-items>
|
||||
<v-tooltip top>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn text small @click="dialog_captures.show_configure = true" v-bind:class='{ "v-btn--active": dialog_bulk.show }' v-on="on"><small v-show="selectedSites > 0">({{ selectedSites }})</small><v-icon dark>mdi-settings</v-icon></v-btn>
|
||||
</template><span>Configure pages to capture</span>
|
||||
</v-tooltip>
|
||||
</v-toolbar-items>
|
||||
</v-toolbar>
|
||||
<v-card-text style="min-height:200px;">
|
||||
<v-card v-show="dialog_captures.show_configure" class="mt-5 mb-3" style="max-width:850px;margin:auto;">
|
||||
<v-toolbar color="grey lighten-4" dense light flat>
|
||||
<v-btn icon @click.native="closeCaptures()">
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title>Configured pages to capture.</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-card-text>
|
||||
<v-alert type="info">Should start with a <code>/</code>. Example use <code>/</code> for the homepage and <code>/contact</code> for the the contact page.</v-alert>
|
||||
<v-text-field v-for="item in dialog_captures.pages" label="Page URL" :value="item.page" @change.native="item.page = $event.target.value"></v-text-field>
|
||||
<p><v-btn text small icon color="primary" @click="addAdditionalCapturePage"><v-icon>mdi-plus-box</v-icon></v-btn></p>
|
||||
<p><v-btn color="primary" @click="updateCapturePages()">Save Pages</v-btn></p>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-container class="text-center" v-if="dialog_captures.captures.length > 0 && ! dialog_captures.loading">
|
||||
<img :src="`${dialog_captures.image_path}${dialog_captures.selected_page.image}` | safeUrl" style="max-width:100%;" class="elevation-5 mt-5">
|
||||
</v-container>
|
||||
<v-container v-show="dialog_captures.captures.length == 0 && ! dialog_captures.loading" class="mt-5">
|
||||
<v-alert type="info">There are no historical captures, yet.</v-alert>
|
||||
</v-container>
|
||||
<v-container v-show="dialog_captures.loading" class="mt-5">
|
||||
<v-progress-linear indeterminate rounded height="6" class="mb-3"></v-progress-linear>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<v-dialog
|
||||
v-model="dialog_toggle.show"
|
||||
transition="dialog-bottom-transition"
|
||||
|
@ -2612,7 +2684,7 @@ if ( $role_check ) {
|
|||
<v-layout body-1 px-6 class="row">
|
||||
<v-flex xs12 md6 class="py-2">
|
||||
<div class="block mt-6">
|
||||
<v-img :src="key.screenshot_large" max-width="400" aspect-ratio="1.6" class="elevation-5" v-show="key.screenshot_large" style="margin:auto;"></v-img>
|
||||
<a @click="showCaptures( site.id )"><v-img :src="key.screenshot_large" max-width="400" aspect-ratio="1.6" class="elevation-5" v-show="key.screenshot_large" style="margin:auto;"></v-img></a>
|
||||
</div>
|
||||
<v-list dense style="padding:0px;max-width:350px;margin: auto;" class="mt-6">
|
||||
<v-list-item :href="key.link" target="_blank" dense>
|
||||
|
@ -3807,12 +3879,14 @@ new Vue({
|
|||
drawer: null,
|
||||
billing_link: "<?php echo get_field( 'billing_link', 'option' ); ?>",
|
||||
home_link: "<?php echo home_url(); ?>",
|
||||
remote_upload_uri: "<?php echo get_field( 'remote_upload_uri', 'option' ); ?>",
|
||||
loading_page: true,
|
||||
expanded: [],
|
||||
accounts: [],
|
||||
account_tab: null,
|
||||
modules: { dns: <?php if ( defined( "CONSTELLIX_API_KEY" ) and defined( "CONSTELLIX_SECRET_KEY" ) ) { echo "true"; } else { echo "false"; } ?> },
|
||||
dialog_bulk: { show: false, tabs_management: "tab-Sites", environment_selected: "Production" },
|
||||
dialog_captures: { site: {}, pages: [{ page: ""}], capture: { pages: [] }, image_path:"", selected_page: "", captures: [], mode: "screenshot", loading: true, show: false, show_configure: false },
|
||||
dialog_delete_user: { show: false, site: {}, users: [], username: "", reassign: {} },
|
||||
dialog_apply_https_urls: { show: false, site_id: "", site_name: "", sites: [] },
|
||||
dialog_copy_site: { show: false, site: {}, options: [], destination: "" },
|
||||
|
@ -4071,6 +4145,9 @@ new Vue({
|
|||
}
|
||||
},
|
||||
filters: {
|
||||
safeUrl: function( url ) {
|
||||
return url.replace('#', '%23' )
|
||||
},
|
||||
timeago: function( timestamp ){
|
||||
return moment.utc( timestamp, "YYYY-MM-DD hh:mm:ss").fromNow();
|
||||
},
|
||||
|
@ -4180,6 +4257,16 @@ new Vue({
|
|||
return this.dialog_configure_defaults.record.default_recipes;
|
||||
}
|
||||
},
|
||||
dialogCapturesPagesText() {
|
||||
if ( typeof this.dialog_captures.capture.pages == 'undefined' ) {
|
||||
return ""
|
||||
}
|
||||
if ( this.dialog_captures.capture.pages.length == 1 ) {
|
||||
return "Page"
|
||||
} else {
|
||||
return "Pages"
|
||||
}
|
||||
},
|
||||
runningJobs() {
|
||||
return this.jobs.filter(job => job.status != 'done' && job.status != 'error' ).length;
|
||||
},
|
||||
|
@ -6047,6 +6134,53 @@ new Vue({
|
|||
.catch( error => console.log( error ) );
|
||||
|
||||
},
|
||||
showCaptures( site_id ) {
|
||||
site = this.sites.filter( site => site.id == site_id )[0];
|
||||
this.dialog_captures.site = site;
|
||||
environment = site.environments.filter( e => e.environment == site.environment_selected )[0];
|
||||
this.dialog_captures.pages = environment.capture_pages
|
||||
if ( environment.capture_pages == "" || environment.capture_pages == null ) {
|
||||
this.dialog_captures.pages = [{ page: "/" }]
|
||||
}
|
||||
this.dialog_captures.loading = true
|
||||
this.dialog_captures.show = true;
|
||||
this.dialog_captures.selected_page
|
||||
axios.get(
|
||||
`/wp-json/captaincore/v1/site/${site_id}/${site.environment_selected.toLowerCase()}/captures`, {
|
||||
headers: {'X-WP-Nonce':this.wp_nonce}
|
||||
})
|
||||
.then(response => {
|
||||
this.dialog_captures.image_path = this.remote_upload_uri + site.site + "_" + site.id + "/" + site.environment_selected.toLowerCase() + "/captures/"
|
||||
this.dialog_captures.captures = response.data
|
||||
if ( this.dialog_captures.captures.length > 0 ) {
|
||||
this.dialog_captures.capture = this.dialog_captures.captures[0]
|
||||
this.dialog_captures.selected_page = this.dialog_captures.capture.pages[0]
|
||||
}
|
||||
this.dialog_captures.loading = false
|
||||
});
|
||||
},
|
||||
closeCaptures() {
|
||||
this.dialog_captures = { site: {}, pages: [{ page: ""}], capture: { pages: [] }, image_path:"", selected_page: "", captures: [], mode: "screenshot", loading: true, show: false, show_configure: false };
|
||||
},
|
||||
addAdditionalCapturePage() {
|
||||
this.dialog_captures.pages.push({ page: "/" });
|
||||
},
|
||||
updateCapturePages() {
|
||||
var data = {
|
||||
action: 'captaincore_ajax',
|
||||
post_id: this.dialog_captures.site.id,
|
||||
command: 'updateCapturePages',
|
||||
environment: this.dialog_captures.site.environment_selected,
|
||||
value: this.dialog_captures.pages,
|
||||
};
|
||||
|
||||
axios.post( ajaxurl, Qs.stringify( data ) )
|
||||
.then( response => {
|
||||
this.dialog_captures.show = false;
|
||||
this.dialog_captures.pages = [];
|
||||
})
|
||||
.catch( error => console.log( error ) );
|
||||
},
|
||||
toggleSite( site_id ) {
|
||||
site = this.sites.filter( site => site.id == site_id )[0];
|
||||
this.dialog_toggle.show = true;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue