Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion press/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@
"press.saas.doctype.product_trial_request.product_trial_request.gather_daily_stats",
"press.press.doctype.agent_job.agent_job.agent_poll_count_stats_daily",
"press.press.doctype.site_backup.site_backup.delete_backups_for_archived_sites_after_retention",
"press.press.doctype.site.site.notify_sites_before_archival",
],
"hourly": [
"press.press.doctype.site.backups.cleanup_local",
Expand Down Expand Up @@ -279,7 +280,6 @@
"all": [
"press.auth.flush",
"press.press.doctype.site.sync.sync_setup_wizard_status",
"press.press.doctype.site.archive.archive_suspended_trial_sites",
"press.press.doctype.agent_job.agent_job.flush",
],
"cron": {
Expand Down
1 change: 1 addition & 0 deletions press/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,4 @@ press.press.doctype.proxy_server.patches.set_use_as_proxy_for_agent_and_metrics_
press.patches.v0_0_1.cancel_pending_version_upgrades_for_disabled_bench_groups
press.partner.doctype.partner_lead.patches.merge_engagement_stage_to_status
press.patches.v0_0_1.add_job_status_index_press_job_step
press.patches.v0_8_0.set_suspended_at_for_suspended_sites
23 changes: 23 additions & 0 deletions press/patches/v0_8_0/set_suspended_at_for_suspended_sites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import frappe


def execute():
suspended_sites = frappe.get_all("Site", filters={"status": "Suspended"}, pluck="name")

if not suspended_sites:
return

site_suspension_activity_records = frappe.get_all(
"Site Activity",
filters={
"action": "Suspend Site",
"site": ["in", suspended_sites],
},
fields=["site", "max(creation) as suspended_at"],
group_by="site",
)

for row in site_suspension_activity_records:
frappe.db.set_value("Site", row.site, "suspended_at", row.suspended_at, update_modified=False)

frappe.db.commit()
91 changes: 0 additions & 91 deletions press/press/doctype/site/archive.py

This file was deleted.

21 changes: 19 additions & 2 deletions press/press/doctype/site/site.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
"status_before_update",
"server",
"archive_failed",
"creation_failed",
"fatal_site_update",
"column_break_3",
"bench",
Expand All @@ -21,6 +20,10 @@
"admin_password",
"additional_system_user_created",
"backup_timeout",
"section_break_aokd",
"creation_failed",
"column_break_tfvn",
"suspended_at",
"config_tab",
"hide_config",
"host_name",
Expand Down Expand Up @@ -756,6 +759,20 @@
"fieldtype": "Link",
"label": "Fatal Site Update",
"options": "Site Update"
},
{
"fieldname": "section_break_aokd",
"fieldtype": "Section Break"
},
{
"fieldname": "column_break_tfvn",
"fieldtype": "Column Break"
},
{
"fieldname": "suspended_at",
"fieldtype": "Datetime",
"label": "Suspended At",
"read_only": 1
}
],
"links": [
Expand Down Expand Up @@ -840,7 +857,7 @@
"link_fieldname": "site"
}
],
"modified": "2026-02-09 16:05:29.084553",
"modified": "2026-04-12 12:56:22.557860",
"modified_by": "Administrator",
"module": "Press",
"name": "Site",
Expand Down
77 changes: 42 additions & 35 deletions press/press/doctype/site/site.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ class Site(Document, TagHelpers):
]
status_before_update: DF.Data | None
subdomain: DF.Data
suspended_at: DF.Datetime | None
tags: DF.Table[ResourceTag]
team: DF.Link
timezone: DF.Data | None
Expand Down Expand Up @@ -2773,6 +2774,7 @@ def activate(self):
def suspend(self, reason=None, skip_reload=False):
log_site_activity(self.name, "Suspend Site", reason)
self.status = "Suspended"
self.suspended_at = frappe.utils.now_datetime()
self.update_site_config({"maintenance_mode": 1})
self.update_site_status_on_proxy("suspended", skip_reload=skip_reload)
self.deactivate_app_subscriptions()
Expand Down Expand Up @@ -2812,6 +2814,7 @@ def reactivate_app_subscriptions(self):
def unsuspend(self, reason=None):
log_site_activity(self.name, "Unsuspend Site", reason)
self.status = "Active"
self.suspended_at = None
self.update_site_config({"maintenance_mode": 0})
self.update_site_status_on_proxy("activated")
self.reactivate_app_subscriptions()
Expand Down Expand Up @@ -5054,14 +5057,6 @@ def create_site_status_update_webhook_event(site: str):
create_webhook_event("Site Status Update", record, record.team)


class SiteToArchive(frappe._dict):
name: str
plan: str
team: str
bench: str
offsite_backups: DF.Check


def get_suspended_time(site: str):
return frappe.get_all(
"Site Activity",
Expand All @@ -5072,52 +5067,64 @@ def get_suspended_time(site: str):
)[0].creation


def archive_suspended_site(site_dict: SiteToArchive):
archive_after_days = ARCHIVE_AFTER_SUSPEND_DAYS
suspended_days = frappe.utils.date_diff(frappe.utils.today(), get_suspended_time(site_dict.name))

if frappe.db.get_value("Bench", site_dict.bench, "managed_database_service"):
return

if suspended_days <= archive_after_days:
if suspended_days == archive_after_days - NOTIFY_BEFORE_ARCHIVAL_DAYS:
notify_site_scheduled_for_archival(site_dict.name)
return

site = Site("Site", site_dict.name)
site.archive(reason="Archive suspended site")


def archive_suspended_sites():
archive_at_once = 5
archive_at_once = 6
archive_threshold = frappe.utils.add_to_date(frappe.utils.now(), days=-ARCHIVE_AFTER_SUSPEND_DAYS)

sites = frappe.qb.DocType("Site")
site_plans = frappe.qb.DocType("Site Plan")
SiteTable = frappe.qb.DocType("Site")

sites_to_drop = (
frappe.qb.from_(sites)
.join(site_plans)
.on(sites.plan == site_plans.name)
frappe.qb.from_(SiteTable)
.where(
(sites.status == "Suspended") & (sites.trial_end_date.isnull()) & (site_plans.is_trial_plan == 0)
(SiteTable.status == "Suspended")
& (SiteTable.suspended_at.isnotnull())
& (SiteTable.suspended_at <= archive_threshold)
)
.select(sites.name, sites.team, sites.plan, sites.bench, site_plans.offsite_backups)
.orderby(sites.creation, order=frappe.qb.asc)
.select(SiteTable.name, SiteTable.bench)
.limit(archive_at_once)
.run(as_dict=True)
)

for site_dict in sites_to_drop:
try:
archive_suspended_site(site_dict)
if frappe.db.get_value("Bench", site_dict.bench, "managed_database_service"):
continue

site = Site("Site", site_dict.name)
site.archive(reason="Archive suspended site")
frappe.db.commit()
except (frappe.QueryDeadlockError, frappe.QueryTimeoutError):
frappe.db.rollback()
except Exception:
frappe.log_error(title="Suspended Site Archive Error")
frappe.log_error(title="Suspended Site Archival Error")
frappe.db.rollback()


def notify_sites_before_archival():
notify_threshold = frappe.utils.add_to_date(
frappe.utils.now(), days=-(ARCHIVE_AFTER_SUSPEND_DAYS - NOTIFY_BEFORE_ARCHIVAL_DAYS)
)
archive_threshold = frappe.utils.add_to_date(frappe.utils.now(), days=-ARCHIVE_AFTER_SUSPEND_DAYS)

SiteTable = frappe.qb.DocType("Site")
sites_to_notify = (
frappe.qb.from_(SiteTable)
.where(
(SiteTable.status == "Suspended")
& (SiteTable.suspended_at.isnotnull())
& (SiteTable.suspended_at <= notify_threshold)
& (SiteTable.suspended_at > archive_threshold)
)
.select(SiteTable.name, SiteTable.bench)
.run(as_dict=True)
)

for site_dict in sites_to_notify:
if frappe.db.get_value("Bench", site_dict.bench, "managed_database_service"):
continue
notify_site_scheduled_for_archival(site_dict.name)


def notify_site_scheduled_for_archival(site_name: str):
try:
if frappe.db.exists(
Expand Down
Loading
Loading