Jump to content

ICT:Dashboard autopurge architecture: Difference between revisions

From Costa Sano MediaWiki
Created page with "= ICT: Dashboard Auto‑Purge Architecture = This page documents why dashboards in this wiki require an automatic purge after saving a Page Forms form, why several intuitive approaches fail, and why the final solution works reliably. It is written for future administrators and maintainers of the Costa Sano Research infrastructure. == 1. The Problem == Dashboards such as '''Dashboard:Place''' display data stored in Cargo tables. After saving a form, the updated Car..."
 
No edit summary
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
= ICT: Dashboard Auto‑Purge Architecture =
= ICT: Dashboard Auto‑Purge Architecture =


This page documents why dashboards in this wiki require an automatic purge after saving a Page Forms form, why several intuitive approaches fail, and why the final solution works reliably
This page documents why dashboards in this wiki require an automatic purge after saving a Page Forms form, why several intuitive approaches fail, and why the final solution works reliably.
It is written for future administrators and maintainers of the Costa Sano Research infrastructure.


== 1. The Problem ==
== 1. The Problem ==
Line 14: Line 13:
* Reverse proxies may cache responses   
* Reverse proxies may cache responses   
* Page Forms redirects do not trigger a purge   
* Page Forms redirects do not trigger a purge   
Users therefore see outdated dashboard data unless the page is manually purged.


A reliable, automatic purge mechanism is required.
A reliable, automatic purge mechanism is required.
Line 26: Line 23:


'''Conclusion:''' HTML refresh cannot be used.
'''Conclusion:''' HTML refresh cannot be used.
---


=== 2.2. Redirecting to <code>?action=purge</code> ===
=== 2.2. Redirecting to <code>?action=purge</code> ===
A redirect to:
A redirect to <code>?action=purge</code> always triggers MediaWiki’s confirmation dialog
 
<code>?action=purge</code>
 
always triggers MediaWiki’s confirmation dialog:
 
> Do you want to purge this page?
 
This dialog cannot be suppressed via GET requests.
This dialog cannot be suppressed via GET requests.


'''Conclusion:''' GET‑based purge is never silent.
'''Conclusion:''' GET‑based purge is never silent.
---


=== 2.3. Page Forms limitations ===
=== 2.3. Page Forms limitations ===
Page Forms does not provide:
Page Forms does not provide hooks or callbacks that run after saving.  
 
It cannot trigger a purge.
* after‑save hooks
* post‑redirect callbacks   
* purge triggers 
* button overrides 
 
There is no way to attach purge logic to the Save button.
 
'''Conclusion:''' Page Forms cannot trigger a purge.


---
'''Conclusion:''' Page Forms cannot be used for purge logic.


=== 2.4. JavaScript redirect loops ===
=== 2.4. JavaScript redirect loops ===
If JavaScript redirects to <code>?action=purge</code>:
Redirecting to <code>?action=purge</code> causes:


# The purge dialog appears  
# Purge dialog   
# After confirmation, the page reloads  
# Page reload  
# The JS sees no marker indicating purge completion 
# JavaScript fires again   
# JS redirects again   
# Loop  
# Infinite loop  


'''Conclusion:''' Redirect‑based purge is unstable.
'''Conclusion:''' Redirect‑based purge is unstable.
---


=== 2.5. Query parameters being stripped ===
=== 2.5. Query parameters being stripped ===
Reverse proxies, skins, and canonical URL rewrites may strip unknown parameters such as:
Reverse proxies, skins, and canonical URL rewrites may strip unknown parameters such as <code>?purged=1</code>
 
This prevents loop‑detection.
<code>?purged=1</code>
 
This causes loops because the JS cannot detect that the purge already happened.


'''Conclusion:''' Only parameters MediaWiki preserves can be used as markers.
'''Conclusion:''' Only parameters MediaWiki preserves can be used.


== 3. Requirements for a Working Solution ==
== 3. Requirements for a Working Solution ==
Line 84: Line 56:
A correct solution must:
A correct solution must:


* purge silently (no dialog)  
* purge silently   
* avoid loops   
* avoid loops   
* work for all dashboards   
* work for all dashboards   
* survive reverse proxies and canonicalization  
* survive reverse proxies   
* be centralized   
* be centralized   
* be successor‑friendly   
* be successor‑friendly   
This leads to the final architecture.


== 4. The Final, Working Solution ==
== 4. The Final, Working Solution ==


=== 4.1. Purge via the MediaWiki API ===
=== 4.1. Purge via the MediaWiki API ===
MediaWiki performs a silent purge only when:
Silent purge requires:
 
* the request is a POST 
* it includes a valid CSRF token 
* it uses the API module 
 
This avoids the confirmation dialog entirely.


---
* POST request 
* CSRF token 
* API module 


=== 4.2. Use a preserved query parameter ===
=== 4.2. Use a preserved query parameter ===
Line 111: Line 77:


<code>?mw_purged=1</code>
<code>?mw_purged=1</code>
to mark that the purge has already occurred.
---


=== 4.3. Centralize logic in <code>MediaWiki:Common.js</code> ===
=== 4.3. Centralize logic in <code>MediaWiki:Common.js</code> ===
This ensures:
One place, all dashboards.
 
* all dashboards benefit 
* no per‑page hacks 
* predictable behaviour 


== 5. The Working Code (Common.js) ==
== 5. The Working Code (Common.js) ==
Line 163: Line 121:
* API POST + CSRF token   
* API POST + CSRF token   
* No confirmation dialog   
* No confirmation dialog   
* No user interaction 


=== No loops ===
=== No loops ===
* <code>mw_purged=1</code> is preserved by MediaWiki  
* <code>mw_purged=1</code> is preserved   
* JS sees the marker and does not purge again  
* JS sees the marker and stops  


=== Works for all dashboards ===
=== Works for all dashboards ===
* Uses <code>wgPageName</code>   
* Uses <code>wgPageName</code>   
* No hardcoded page names 


=== Infrastructure‑safe ===
=== Infrastructure‑safe ===
Line 177: Line 133:
* Survives canonical URL rewrites   
* Survives canonical URL rewrites   
* Survives short URLs   
* Survives short URLs   
* Survives caching layers 


=== Successor‑friendly ===
=== Successor‑friendly ===
* One central rule   
* One central rule   
* No per‑page code  
* No per‑page hacks  
* Easy to understand and maintain  
 
== 7. Architecture Diagram ==
 
The following ASCII diagram shows the complete flow from saving a form to the final dashboard refresh.
 
<pre>
                +-----------------------+
                |  User saves form    |
                |  (Page Forms)        |
                +-----------+-----------+
                            |
                            v
                +-----------------------+
                | Page Forms redirect  |
                | to Dashboard:XYZ      |
                +-----------+-----------+
                            |
                            v
                +-----------------------+
                | Dashboard loads      |
                | Common.js executes    |
                +-----------+-----------+
                            |
                            | JS checks:
                            |  - Is this Dashboard:* ?
                            |  - Is mw_purged=1 absent?
                            v
                +-----------------------+
                | Request CSRF token    |
                | via API (GET)        |
                +-----------+-----------+
                            |
                            v
                +-----------------------+
                | Silent purge via API |
                | (POST + token)        |
                +-----------+-----------+
                            |
                            v
                +-----------------------+
                | Reload dashboard with |
                | ?mw_purged=1 marker  |
                +-----------+-----------+
                            |
                            v
                +-----------------------+
                | Dashboard loads fresh |
                | JS sees mw_purged=1  |
                | -> No purge triggered |
                +-----------------------+
</pre>


== 7. Summary ==
== 8. Summary ==


The final solution works because it respects all of MediaWiki’s constraints:
The final solution works because it respects all of MediaWiki’s constraints:
Line 195: Line 200:
* Only <code>mw_*</code> parameters survive reliably   
* Only <code>mw_*</code> parameters survive reliably   


By combining these insights, we achieve a stable, silent, automatic purge mechanism for all dashboards.
This produces a stable, silent, automatic purge mechanism for all dashboards.
 
== 9. Failure Mode Diagrams ==
 
The following diagrams illustrate each attempted solution, why it seemed plausible, and why it ultimately failed. 
These are included so future administrators can immediately understand the architectural traps and avoid repeating them.
 
=== 9.1. Failure Mode 1 — HTML <meta> Refresh (Sanitized) ===
 
<pre>
+---------------------------+
| Dashboard page contains  |
| <meta http-equiv="refresh">
+-------------+-------------+
              |
              v
+---------------------------+
| MediaWiki sanitizes HTML  |
| <meta> is escaped        |
| (displayed as text)      |
+-------------+-------------+
              |
              v
+---------------------------+
| No refresh occurs        |
| Dashboard stays stale    |
+---------------------------+
</pre>
 
'''Reason for failure:''' 
MediaWiki strips or escapes <meta> tags for security. They never execute.
 
---
 
=== 9.2. Failure Mode 2 — Redirect to ?action=purge (Confirmation Dialog) ===
 
<pre>
+---------------------------+
| JS redirects to          |
| Dashboard:XYZ?action=purge
+-------------+-------------+
              |
              v
+---------------------------+
| MediaWiki shows dialog:  |
| "Do you want to purge?"  |
+-------------+-------------+
              |
              v
+---------------------------+
| User must click OK        |
| (not silent)              |
+---------------------------+
</pre>
 
'''Reason for failure:''' 
GET-based purge always triggers a confirmation dialog.
 
---
 
=== 9.3. Failure Mode 3 — JS Redirect Loop ===
 
<pre>
+---------------------------+
| Dashboard loads          |
| JS sees "not purged"      |
+-------------+-------------+
              |
              v
+---------------------------+
| JS redirects to purge    |
+-------------+-------------+
              |
              v
+---------------------------+
| Purge dialog appears      |
| User clicks OK            |
+-------------+-------------+
              |
              v
+---------------------------+
| Dashboard reloads        |
| URL has no marker        |
| JS thinks "not purged"    |
+-------------+-------------+
              |
              v
+---------------------------+
| JS redirects again        |
| (infinite loop)          |
+---------------------------+
</pre>
 
'''Reason for failure:''' 
The dashboard reloads without a marker, so JS purges again.
 
---
 
=== 9.4. Failure Mode 4 — Query Parameter Stripping ===
 
<pre>
+---------------------------+
| JS reloads dashboard with |
| ?purged=1                |
+-------------+-------------+
              |
              v
+---------------------------+
| Reverse proxy / skin      |
| strips unknown parameter  |
| (purged=1 removed)        |
+-------------+-------------+
              |
              v
+---------------------------+
| Dashboard loads as        |
| Dashboard:XYZ            |
| (no marker)              |
+-------------+-------------+
              |
              v
+---------------------------+
| JS purges again          |
| (loop)                    |
+---------------------------+
</pre>
 
'''Reason for failure:''' 
Unknown parameters like <code>purged=1</code> are removed by canonical URL rewriting.
 
---
 
=== 9.5. Failure Mode 5 — API Purge Without CSRF Token ===
 
<pre>
+---------------------------+
| JS calls API purge        |
| without CSRF token        |
+-------------+-------------+
              |
              v
+---------------------------+
| MediaWiki rejects purge  |
| "badtoken" or "permission"|
+-------------+-------------+
              |
              v
+---------------------------+
| Dashboard reloads        |
| still stale              |
+-------------+-------------+
</pre>
 
'''Reason for failure:''' 
MediaWiki requires a CSRF token for all write operations, including purge.
 
---
 
=== 9.6. Failure Mode 6 — API Purge Works, But Loop Continues ===
 
<pre>
+---------------------------+
| JS purges via API        |
| (correct)                |
+-------------+-------------+
              |
              v
+---------------------------+
| JS reloads dashboard with |
| ?purged=1                |
+-------------+-------------+
              |
              v
+---------------------------+
| Skin / proxy strips      |
| ?purged=1                |
+-------------+-------------+
              |
              v
+---------------------------+
| Dashboard loads without  |
| marker                    |
+-------------+-------------+
              |
              v
+---------------------------+
| JS purges again          |
| (loop)                    |
+---------------------------+
</pre>
 
'''Reason for failure:''' 
Even with correct API purge, the loop persists if the marker is stripped.
 
---
 
=== 9.7. Final Working Architecture (for comparison) ===
 
<pre>
+---------------------------+
| Dashboard loads          |
| JS executes              |
+-------------+-------------+
              |
              | JS checks:
              |  - Is Dashboard:* ?
              |  - Is mw_purged=1 absent?
              v
+---------------------------+
| Request CSRF token        |
| via API (GET)            |
+-------------+-------------+
              |
              v
+---------------------------+
| Silent purge via API      |
| (POST + token)            |
+-------------+-------------+
              |
              v
+---------------------------+
| Reload dashboard with    |
| ?mw_purged=1              |
+-------------+-------------+
              |
              v
+---------------------------+
| Dashboard loads fresh    |
| JS sees mw_purged=1      |
| -> No purge triggered    |
+---------------------------+
</pre>
 
'''Reason for success:''' 
<code>mw_purged=1</code> is preserved by MediaWiki, and API POST purge is silent.

Latest revision as of 21:40, 14 February 2026

ICT: Dashboard Auto‑Purge Architecture

This page documents why dashboards in this wiki require an automatic purge after saving a Page Forms form, why several intuitive approaches fail, and why the final solution works reliably.

1. The Problem

Dashboards such as Dashboard:Place display data stored in Cargo tables. After saving a form, the updated Cargo data does not appear immediately because:

  • Cargo updates are asynchronous
  • MediaWiki caches parser output
  • APCu/object cache may serve stale HTML
  • Reverse proxies may cache responses
  • Page Forms redirects do not trigger a purge

A reliable, automatic purge mechanism is required.

2. Why Simple Solutions Do Not Work

2.1. HTML <meta> refresh

MediaWiki sanitizes <meta> tags, even inside #tag:html. The tag is displayed as text and never executed.

Conclusion: HTML refresh cannot be used.

2.2. Redirecting to ?action=purge

A redirect to ?action=purge always triggers MediaWiki’s confirmation dialog. This dialog cannot be suppressed via GET requests.

Conclusion: GET‑based purge is never silent.

2.3. Page Forms limitations

Page Forms does not provide hooks or callbacks that run after saving. It cannot trigger a purge.

Conclusion: Page Forms cannot be used for purge logic.

2.4. JavaScript redirect loops

Redirecting to ?action=purge causes:

  1. Purge dialog
  2. Page reload
  3. JavaScript fires again
  4. Loop

Conclusion: Redirect‑based purge is unstable.

2.5. Query parameters being stripped

Reverse proxies, skins, and canonical URL rewrites may strip unknown parameters such as ?purged=1. This prevents loop‑detection.

Conclusion: Only parameters MediaWiki preserves can be used.

3. Requirements for a Working Solution

A correct solution must:

  • purge silently
  • avoid loops
  • work for all dashboards
  • survive reverse proxies
  • be centralized
  • be successor‑friendly

4. The Final, Working Solution

4.1. Purge via the MediaWiki API

Silent purge requires:

  • POST request
  • CSRF token
  • API module

4.2. Use a preserved query parameter

MediaWiki never strips parameters beginning with mw_. We use:

?mw_purged=1

4.3. Centralize logic in MediaWiki:Common.js

One place, all dashboards.

5. The Working Code (Common.js)

$(function () {
    const title = mw.config.get('wgPageName');

    if (title && title.startsWith('Dashboard:')) {

        // Only purge once
        if (!location.search.includes('mw_purged=1')) {

            new mw.Api().get({
                action: 'query',
                meta: 'tokens',
                type: 'csrf'
            }).done(function (data) {

                const token = data.query.tokens.csrftoken;

                new mw.Api().post({
                    action: 'purge',
                    titles: title,
                    token: token
                }).always(function () {

                    // Reload the dashboard normally, marked as purged
                    const url = mw.util.getUrl(title, { mw_purged: 1 });
                    location.replace(url);
                });
            });
        }
    }
});

6. Why This Works

Silent purge

  • API POST + CSRF token
  • No confirmation dialog

No loops

  • mw_purged=1 is preserved
  • JS sees the marker and stops

Works for all dashboards

  • Uses wgPageName

Infrastructure‑safe

  • Survives reverse proxies
  • Survives canonical URL rewrites
  • Survives short URLs

Successor‑friendly

  • One central rule
  • No per‑page hacks

7. Architecture Diagram

The following ASCII diagram shows the complete flow from saving a form to the final dashboard refresh.

                +-----------------------+
                |   User saves form     |
                |   (Page Forms)        |
                +-----------+-----------+
                            |
                            v
                +-----------------------+
                | Page Forms redirect   |
                | to Dashboard:XYZ      |
                +-----------+-----------+
                            |
                            v
                +-----------------------+
                | Dashboard loads       |
                | Common.js executes    |
                +-----------+-----------+
                            |
                            | JS checks:
                            |   - Is this Dashboard:* ?
                            |   - Is mw_purged=1 absent?
                            v
                +-----------------------+
                | Request CSRF token    |
                | via API (GET)         |
                +-----------+-----------+
                            |
                            v
                +-----------------------+
                | Silent purge via API  |
                | (POST + token)        |
                +-----------+-----------+
                            |
                            v
                +-----------------------+
                | Reload dashboard with |
                | ?mw_purged=1 marker   |
                +-----------+-----------+
                            |
                            v
                +-----------------------+
                | Dashboard loads fresh |
                | JS sees mw_purged=1   |
                | -> No purge triggered |
                +-----------------------+

8. Summary

The final solution works because it respects all of MediaWiki’s constraints:

  • Purge must be done via API POST
  • CSRF token is required
  • GET purge always shows a dialog
  • Page Forms cannot trigger purge
  • Unknown query parameters may be stripped
  • Only mw_* parameters survive reliably

This produces a stable, silent, automatic purge mechanism for all dashboards.

9. Failure Mode Diagrams

The following diagrams illustrate each attempted solution, why it seemed plausible, and why it ultimately failed. These are included so future administrators can immediately understand the architectural traps and avoid repeating them.

9.1. Failure Mode 1 — HTML <meta> Refresh (Sanitized)

+---------------------------+
| Dashboard page contains   |
| <meta http-equiv="refresh"> 
+-------------+-------------+
              |
              v
+---------------------------+
| MediaWiki sanitizes HTML  |
| <meta> is escaped         |
| (displayed as text)       |
+-------------+-------------+
              |
              v
+---------------------------+
| No refresh occurs         |
| Dashboard stays stale     |
+---------------------------+

Reason for failure: MediaWiki strips or escapes <meta> tags for security. They never execute.

---

9.2. Failure Mode 2 — Redirect to ?action=purge (Confirmation Dialog)

+---------------------------+
| JS redirects to           |
| Dashboard:XYZ?action=purge
+-------------+-------------+
              |
              v
+---------------------------+
| MediaWiki shows dialog:   |
| "Do you want to purge?"   |
+-------------+-------------+
              |
              v
+---------------------------+
| User must click OK        |
| (not silent)              |
+---------------------------+

Reason for failure: GET-based purge always triggers a confirmation dialog.

---

9.3. Failure Mode 3 — JS Redirect Loop

+---------------------------+
| Dashboard loads           |
| JS sees "not purged"      |
+-------------+-------------+
              |
              v
+---------------------------+
| JS redirects to purge     |
+-------------+-------------+
              |
              v
+---------------------------+
| Purge dialog appears      |
| User clicks OK            |
+-------------+-------------+
              |
              v
+---------------------------+
| Dashboard reloads         |
| URL has no marker         |
| JS thinks "not purged"    |
+-------------+-------------+
              |
              v
+---------------------------+
| JS redirects again        |
| (infinite loop)           |
+---------------------------+

Reason for failure: The dashboard reloads without a marker, so JS purges again.

---

9.4. Failure Mode 4 — Query Parameter Stripping

+---------------------------+
| JS reloads dashboard with |
| ?purged=1                 |
+-------------+-------------+
              |
              v
+---------------------------+
| Reverse proxy / skin      |
| strips unknown parameter  |
| (purged=1 removed)        |
+-------------+-------------+
              |
              v
+---------------------------+
| Dashboard loads as        |
| Dashboard:XYZ             |
| (no marker)               |
+-------------+-------------+
              |
              v
+---------------------------+
| JS purges again           |
| (loop)                    |
+---------------------------+

Reason for failure: Unknown parameters like purged=1 are removed by canonical URL rewriting.

---

9.5. Failure Mode 5 — API Purge Without CSRF Token

+---------------------------+
| JS calls API purge        |
| without CSRF token        |
+-------------+-------------+
              |
              v
+---------------------------+
| MediaWiki rejects purge   |
| "badtoken" or "permission"|
+-------------+-------------+
              |
              v
+---------------------------+
| Dashboard reloads         |
| still stale               |
+-------------+-------------+

Reason for failure: MediaWiki requires a CSRF token for all write operations, including purge.

---

9.6. Failure Mode 6 — API Purge Works, But Loop Continues

+---------------------------+
| JS purges via API         |
| (correct)                 |
+-------------+-------------+
              |
              v
+---------------------------+
| JS reloads dashboard with |
| ?purged=1                 |
+-------------+-------------+
              |
              v
+---------------------------+
| Skin / proxy strips       |
| ?purged=1                 |
+-------------+-------------+
              |
              v
+---------------------------+
| Dashboard loads without   |
| marker                    |
+-------------+-------------+
              |
              v
+---------------------------+
| JS purges again           |
| (loop)                    |
+---------------------------+

Reason for failure: Even with correct API purge, the loop persists if the marker is stripped.

---

9.7. Final Working Architecture (for comparison)

+---------------------------+
| Dashboard loads           |
| JS executes               |
+-------------+-------------+
              |
              | JS checks:
              |   - Is Dashboard:* ?
              |   - Is mw_purged=1 absent?
              v
+---------------------------+
| Request CSRF token        |
| via API (GET)             |
+-------------+-------------+
              |
              v
+---------------------------+
| Silent purge via API      |
| (POST + token)            |
+-------------+-------------+
              |
              v
+---------------------------+
| Reload dashboard with     |
| ?mw_purged=1              |
+-------------+-------------+
              |
              v
+---------------------------+
| Dashboard loads fresh     |
| JS sees mw_purged=1       |
| -> No purge triggered     |
+---------------------------+

Reason for success: mw_purged=1 is preserved by MediaWiki, and API POST purge is silent.