User:Admin: Difference between revisions
Add Users by Country counter section |
Enhanced Users by Country dashboard with day/week/month breakdown, activity chart, world map, and country table |
||
| Line 80: | Line 80: | ||
== Users by Country == | == Users by Country == | ||
<div id=" | <div id="geo-dashboard" style="font-family:sans-serif; background:#111; border:1px solid #333; border-radius:8px; padding:20px; color:#eee;"> | ||
< | |||
<div style="display:flex; gap:16px; flex-wrap:wrap; margin-bottom:20px;"> | |||
<div style="flex:1; min-width:140px; background:#1a1a1a; border:1px solid #ff6600; border-radius:6px; padding:16px; text-align:center;"> | |||
<div id="geo-total" style="font-size:2em; font-weight:bold; color:#ff6600;">–</div> | |||
<div style="color:#aaa; font-size:0.85em; margin-top:4px;">Total Users</div> | |||
</div> | |||
<div style="flex:1; min-width:140px; background:#1a1a1a; border:1px solid #444; border-radius:6px; padding:16px; text-align:center;"> | |||
<div id="geo-countries" style="font-size:2em; font-weight:bold; color:#ff6600;">–</div> | |||
<div style="color:#aaa; font-size:0.85em; margin-top:4px;">Countries / Regions</div> | |||
</div> | |||
<div style="flex:1; min-width:140px; background:#1a1a1a; border:1px solid #444; border-radius:6px; padding:16px; text-align:center;"> | |||
<div id="geo-active-day" style="font-size:2em; font-weight:bold; color:#ff6600;">–</div> | |||
<div style="color:#aaa; font-size:0.85em; margin-top:4px;">Active Today</div> | |||
</div> | |||
<div style="flex:1; min-width:140px; background:#1a1a1a; border:1px solid #444; border-radius:6px; padding:16px; text-align:center;"> | |||
<div id="geo-active-week" style="font-size:2em; font-weight:bold; color:#ff6600;">–</div> | |||
<div style="color:#aaa; font-size:0.85em; margin-top:4px;">Active This Week</div> | |||
</div> | |||
<div style="flex:1; min-width:140px; background:#1a1a1a; border:1px solid #444; border-radius:6px; padding:16px; text-align:center;"> | |||
<div id="geo-active-month" style="font-size:2em; font-weight:bold; color:#ff6600;">–</div> | |||
<div style="color:#aaa; font-size:0.85em; margin-top:4px;">Active This Month</div> | |||
</div> | |||
</div> | </div> | ||
<div style="display:flex; gap:12px; margin-bottom:16px; flex-wrap:wrap;"> | |||
<button id="geo-btn-day" onclick="geoShowPeriod('day')" style="background:#ff6600; color:#fff; border:none; padding:8px 18px; border-radius:4px; cursor:pointer; font-weight:bold;">Today</button> | |||
<button id="geo-btn-week" onclick="geoShowPeriod('week')" style="background:#333; color:#eee; border:1px solid #555; padding:8px 18px; border-radius:4px; cursor:pointer;">This Week</button> | |||
<button id="geo-btn-month" onclick="geoShowPeriod('month')" style="background:#333; color:#eee; border:1px solid #555; padding:8px 18px; border-radius:4px; cursor:pointer;">This Month</button> | |||
<button id="geo-btn-all" onclick="geoShowPeriod('all')" style="background:#333; color:#eee; border:1px solid #555; padding:8px 18px; border-radius:4px; cursor:pointer;">All Time</button> | |||
<span style="margin-left:auto; color:#666; font-size:0.8em; align-self:center;" id="geo-updated"></span> | |||
</div> | |||
<div id="geo-map-placeholder" style="background:#1a1a1a; border:1px solid #333; border-radius:6px; padding:12px; margin-bottom:16px; min-height:220px; position:relative; overflow:hidden;"> | |||
<svg id="geo-world-map" viewBox="0 0 900 440" style="width:100%; height:auto; display:block;"></svg> | |||
</div> | |||
<div id="geo-table-wrap" style="background:#1a1a1a; border:1px solid #333; border-radius:6px; overflow:hidden;"> | |||
<div style="padding:12px 16px; border-bottom:1px solid #333; font-weight:bold; color:#ff6600; font-size:0.95em;" id="geo-table-title">Users by Country</div> | |||
<table style="width:100%; border-collapse:collapse; font-size:0.9em;"> | |||
<thead> | |||
<tr style="background:#222; color:#aaa; text-align:left;"> | |||
<th style="padding:10px 16px;">#</th> | |||
<th style="padding:10px 16px;">Country</th> | |||
<th style="padding:10px 16px; text-align:right;">Users</th> | |||
<th style="padding:10px 16px;">Share</th> | |||
<th style="padding:10px 16px; text-align:right;">Edits</th> | |||
</tr> | |||
</thead> | |||
<tbody id="geo-table-body"> | |||
<tr><td colspan="5" style="padding:20px; text-align:center; color:#666;">Loading...</td></tr> | |||
</tbody> | |||
</table> | |||
</div> | |||
<div id="geo-activity-chart-wrap" style="background:#1a1a1a; border:1px solid #333; border-radius:6px; padding:16px; margin-top:16px;"> | |||
<div style="font-weight:bold; color:#ff6600; margin-bottom:12px; font-size:0.95em;">Daily Activity (Last 30 Days)</div> | |||
<div id="geo-activity-bars" style="display:flex; align-items:flex-end; gap:3px; height:80px; overflow:hidden;"></div> | |||
<div id="geo-activity-labels" style="display:flex; gap:3px; margin-top:4px; font-size:0.65em; color:#555; overflow:hidden;"></div> | |||
</div> | |||
<div style="margin-top:12px; color:#555; font-size:0.75em;" id="geo-note"> | |||
📍 Activity data is derived from wiki edit logs. Country detection uses Babel language tags on user pages. Users without Babel tags are shown as "Unknown". | |||
</div> | |||
</div> | |||
<script> | |||
(function() { | |||
var GEO = window.AlphaXGeo = window.AlphaXGeo || {}; | |||
var API = '/api.php'; | |||
// ── helpers ────────────────────────────────────────────────── | |||
function isoDate(d) { | |||
return d.toISOString().slice(0,10); | |||
} | |||
function startOf(period) { | |||
var now = new Date(); | |||
if (period === 'day') { | |||
return new Date(now.getFullYear(), now.getMonth(), now.getDate()); | |||
} else if (period === 'week') { | |||
var d = new Date(now); d.setDate(d.getDate() - d.getDay()); | |||
d.setHours(0,0,0,0); return d; | |||
} else if (period === 'month') { | |||
return new Date(now.getFullYear(), now.getMonth(), 1); | |||
} | |||
return new Date(0); | |||
} | |||
// ── country flags & names ───────────────────────────────────── | |||
var COUNTRY_MAP = { | |||
'en':'🇺🇸 English / US','de':'🇩🇪 German','fr':'🇫🇷 French','es':'🇪🇸 Spanish', | |||
'it':'🇮🇹 Italian','pt':'🇧🇷 Portuguese','ru':'🇷🇺 Russian','ja':'🇯🇵 Japanese', | |||
'zh':'🇨🇳 Chinese','ko':'🇰🇷 Korean','ar':'🇸🇦 Arabic','nl':'🇳🇱 Dutch', | |||
'pl':'🇵🇱 Polish','sv':'🇸🇪 Swedish','no':'🇳🇴 Norwegian','da':'🇩🇰 Danish', | |||
'fi':'🇫🇮 Finnish','tr':'🇹🇷 Turkish','cs':'🇨🇿 Czech','hu':'🇭🇺 Hungarian', | |||
'ro':'🇷🇴 Romanian','uk':'🇺🇦 Ukrainian','vi':'🇻🇳 Vietnamese','th':'🇹🇭 Thai', | |||
'id':'🇮🇩 Indonesian','ms':'🇲🇾 Malay','he':'🇮🇱 Hebrew','fa':'🇮🇷 Persian', | |||
'hi':'🇮🇳 Hindi','bn':'🇧🇩 Bengali' | |||
}; | |||
// ── fetch all users ─────────────────────────────────────────── | |||
function fetchUsers() { | |||
return fetch(API + '?action=query&list=allusers&aulimit=500&format=json&origin=*') | |||
.then(function(r){return r.json();}) | |||
.then(function(d){ return d.query.allusers || []; }); | |||
} | |||
// ── fetch recent changes for activity ──────────────────────── | |||
function fetchRecentChanges(rcstart, rclimit) { | |||
rclimit = rclimit || 500; | |||
var url = API + '?action=query&list=recentchanges&rclimit=' + rclimit + | |||
'&rcprop=user|timestamp&format=json&origin=*'; | |||
if (rcstart) url += '&rcstart=' + rcstart; | |||
return fetch(url).then(function(r){return r.json();}) | |||
.then(function(d){ return d.query.recentchanges || []; }); | |||
} | |||
// ── fetch babel info for a user ─────────────────────────────── | |||
function fetchUserBabel(username) { | |||
var url = API + '?action=query&titles=User:' + encodeURIComponent(username) + | |||
'&prop=categories&format=json&origin=*'; | |||
return fetch(url).then(function(r){return r.json();}) | |||
.then(function(d){ | |||
var pages = d.query && d.query.pages ? d.query.pages : {}; | |||
var cats = []; | |||
Object.values(pages).forEach(function(p){ | |||
(p.categories || []).forEach(function(c){ cats.push(c.title); }); | |||
}); | |||
// Look for Category:User XX patterns | |||
var langs = []; | |||
cats.forEach(function(cat){ | |||
var m = cat.match(/Category:User ([a-z]{2,3})/i); | |||
if (m) langs.push(m[1].toLowerCase()); | |||
}); | |||
return langs; | |||
}); | |||
} | |||
// ── world map SVG (simplified) ──────────────────────────────── | |||
var COUNTRY_SHAPES = { | |||
'de': 'M440,145 L455,140 L465,148 L462,165 L448,170 L438,162 Z', | |||
'fr': 'M415,152 L430,148 L438,162 L430,175 L415,172 L408,162 Z', | |||
'es': 'M390,170 L415,168 L415,185 L395,190 L385,182 Z', | |||
'it': 'M450,168 L462,165 L468,180 L460,195 L450,188 L445,178 Z', | |||
'gb': 'M418,135 L428,132 L430,145 L420,148 L414,142 Z', | |||
'ru': 'M460,120 L560,110 L580,135 L540,145 L465,148 Z', | |||
'us': 'M120,150 L220,148 L225,190 L200,200 L115,195 Z', | |||
'cn': 'M620,150 L680,145 L685,175 L650,185 L615,178 Z', | |||
'jp': 'M695,155 L708,152 L710,165 L700,168 L693,163 Z', | |||
'br': 'M195,230 L240,225 L245,275 L215,282 L188,270 Z', | |||
'in': 'M580,175 L615,170 L618,210 L595,218 L575,205 Z', | |||
'au': 'M660,280 L720,275 L725,320 L690,328 L655,315 Z', | |||
}; | |||
function drawWorldMap(highlightLangs) { | |||
var svg = document.getElementById('geo-world-map'); | |||
if (!svg) return; | |||
// Simple world outline | |||
svg.innerHTML = '<rect width="900" height="440" fill="#1a1a1a"/>' + | |||
'<text x="450" y="30" fill="#444" text-anchor="middle" font-size="12" font-family="sans-serif">World Activity Map (language-based approximation)</text>'; | |||
// Draw ocean | |||
var ocean = document.createElementNS('http://www.w3.org/2000/svg','rect'); | |||
ocean.setAttribute('width','900'); ocean.setAttribute('height','440'); | |||
ocean.setAttribute('fill','#0d1f33'); svg.appendChild(ocean); | |||
// Draw continent blobs | |||
var continents = [ | |||
// North America | |||
'M80,100 L240,95 L255,200 L220,230 L170,235 L110,220 L75,170 Z', | |||
// South America | |||
'M155,240 L220,235 L235,360 L195,385 L155,370 L138,320 Z', | |||
// Europe | |||
'M385,90 L480,85 L490,160 L450,175 L400,170 L378,140 Z', | |||
// Africa | |||
'M390,175 L460,170 L470,320 L430,345 L388,335 L375,280 Z', | |||
// Asia | |||
'M490,80 L750,75 L760,230 L700,250 L520,240 L485,180 Z', | |||
// Australia | |||
'M640,280 L740,275 L748,355 L700,368 L638,355 Z', | |||
// Greenland | |||
'M230,50 L290,45 L295,90 L255,95 L228,82 Z', | |||
]; | |||
continents.forEach(function(d){ | |||
var path = document.createElementNS('http://www.w3.org/2000/svg','path'); | |||
path.setAttribute('d', d); | |||
path.setAttribute('fill', '#2a3a2a'); | |||
path.setAttribute('stroke', '#1a2a1a'); | |||
path.setAttribute('stroke-width', '1'); | |||
svg.appendChild(path); | |||
}); | |||
// Highlight active countries | |||
if (highlightLangs && highlightLangs.length > 0) { | |||
Object.keys(COUNTRY_SHAPES).forEach(function(cc){ | |||
var path = document.createElementNS('http://www.w3.org/2000/svg','path'); | |||
path.setAttribute('d', COUNTRY_SHAPES[cc]); | |||
var isActive = highlightLangs.some(function(l){ return l.startsWith(cc); }); | |||
path.setAttribute('fill', isActive ? '#ff6600' : '#3a4a3a'); | |||
path.setAttribute('stroke', '#555'); | |||
path.setAttribute('stroke-width', '1'); | |||
path.setAttribute('opacity', isActive ? '0.9' : '0.5'); | |||
svg.appendChild(path); | |||
}); | |||
} | |||
// Dots for known locations | |||
var locations = [ | |||
{x:160, y:165, label:'North America'}, | |||
{x:450, y:130, label:'Europe'}, | |||
{x:620, y:160, label:'Asia'}, | |||
{x:430, y:260, label:'Africa'}, | |||
{x:190, y:300, label:'South America'}, | |||
{x:690, y:320, label:'Oceania'}, | |||
]; | |||
locations.forEach(function(loc){ | |||
var text = document.createElementNS('http://www.w3.org/2000/svg','text'); | |||
text.setAttribute('x', loc.x); text.setAttribute('y', loc.y); | |||
text.setAttribute('fill', '#444'); text.setAttribute('font-size', '10'); | |||
text.setAttribute('font-family', 'sans-serif'); | |||
text.setAttribute('text-anchor', 'middle'); | |||
text.textContent = loc.label; | |||
svg.appendChild(text); | |||
}); | |||
} | |||
// ── render table ────────────────────────────────────────────── | |||
function renderTable(rows, total, period) { | |||
var tbody = document.getElementById('geo-table-body'); | |||
var titleEl = document.getElementById('geo-table-title'); | |||
if (!tbody) return; | |||
var labels = {day:'Today', week:'This Week', month:'This Month', all:'All Time'}; | |||
if (titleEl) titleEl.textContent = 'Users by Country – ' + (labels[period] || 'All Time'); | |||
if (rows.length === 0) { | |||
tbody.innerHTML = '<tr><td colspan="5" style="padding:20px;text-align:center;color:#555;">No activity data for this period.</td></tr>'; | |||
return; | |||
} | |||
var html = ''; | |||
rows.forEach(function(row, i) { | |||
var bar = '<div style="background:#ff6600;height:8px;border-radius:2px;width:' + | |||
Math.round(row.pct) + '%;min-width:2px;display:inline-block;"></div>' + | |||
'<span style="color:#666;font-size:0.8em;margin-left:6px;">' + row.pct.toFixed(1) + '%</span>'; | |||
var rowBg = i % 2 === 0 ? '#1a1a1a' : '#1e1e1e'; | |||
html += '<tr style="background:' + rowBg + '; border-top:1px solid #222;">' + | |||
'<td style="padding:10px 16px;color:#666;">' + (i+1) + '</td>' + | |||
'<td style="padding:10px 16px;">' + (COUNTRY_MAP[row.lang] || ('🌐 ' + row.lang)) + '</td>' + | |||
'<td style="padding:10px 16px;text-align:right;font-weight:bold;color:#ff6600;">' + row.users + '</td>' + | |||
'<td style="padding:10px 16px;">' + bar + '</td>' + | |||
'<td style="padding:10px 16px;text-align:right;color:#aaa;">' + row.edits + '</td>' + | |||
'</tr>'; | |||
}); | |||
tbody.innerHTML = html; | |||
} | |||
// ── render activity bars ─────────────────────────────────────── | |||
function renderActivityBars(dailyData) { | |||
var wrap = document.getElementById('geo-activity-bars'); | |||
var labelWrap = document.getElementById('geo-activity-labels'); | |||
if (!wrap) return; | |||
var max = Math.max.apply(null, Object.values(dailyData).concat([1])); | |||
var days = []; | |||
for (var i = 29; i >= 0; i--) { | |||
var d = new Date(); d.setDate(d.getDate() - i); | |||
days.push(isoDate(d)); | |||
} | |||
var barsHtml = '', labelsHtml = ''; | |||
days.forEach(function(day, idx) { | |||
var val = dailyData[day] || 0; | |||
var h = Math.max(3, Math.round((val / max) * 70)); | |||
var isToday = idx === 29; | |||
var color = isToday ? '#ff6600' : (val > 0 ? '#cc5500' : '#222'); | |||
barsHtml += '<div title="' + day + ': ' + val + ' edits" style="flex:1;background:' + color + | |||
';height:' + h + 'px;border-radius:2px 2px 0 0;min-width:4px;cursor:default;"></div>'; | |||
if (idx % 5 === 0 || isToday) { | |||
labelsHtml += '<div style="flex:1;text-align:center;white-space:nowrap;">' + day.slice(5) + '</div>'; | |||
} else { | |||
labelsHtml += '<div style="flex:1;"></div>'; | |||
} | |||
}); | |||
wrap.innerHTML = barsHtml; | |||
labelWrap.innerHTML = labelsHtml; | |||
} | |||
// ── main data load ──────────────────────────────────────────── | |||
var cachedUsers = null; | |||
var cachedChanges = null; | |||
var cachedBabel = {}; | |||
var currentPeriod = 'day'; | |||
function loadDashboard() { | |||
var now = new Date(); | |||
Promise.all([ | |||
fetchUsers(), | |||
fetchRecentChanges(null, 500) | |||
]).then(function(results) { | |||
var users = results[0]; | |||
var changes = results[1]; | |||
cachedUsers = users; | |||
cachedChanges = changes; | |||
document.getElementById('geo-total').textContent = users.length; | |||
document.getElementById('geo-updated').textContent = 'Updated: ' + now.toLocaleTimeString(); | |||
// Count activity periods | |||
var dayStart = startOf('day').toISOString(); | |||
var weekStart = startOf('week').toISOString(); | |||
var monthStart = startOf('month').toISOString(); | |||
var activeDay = new Set(), activeWeek = new Set(), activeMonth = new Set(); | |||
var daily = {}; | |||
changes.forEach(function(c) { | |||
if (c.timestamp >= dayStart) activeDay.add(c.user); | |||
if (c.timestamp >= weekStart) activeWeek.add(c.user); | |||
if (c.timestamp >= monthStart) activeMonth.add(c.user); | |||
var day = c.timestamp.slice(0,10); | |||
daily[day] = (daily[day] || 0) + 1; | |||
}); | |||
document.getElementById('geo-active-day').textContent = activeDay.size; | |||
document.getElementById('geo-active-week').textContent = activeWeek.size; | |||
document.getElementById('geo-active-month').textContent = activeMonth.size; | |||
renderActivityBars(daily); | |||
// Fetch babel for all real users (skip bots) | |||
var humanUsers = users.filter(function(u){ | |||
return !['Lucy','Babel AutoCreate','Delete page script','FuzzyBot','Maintenance script','MediaWiki default'].includes(u.name) || u.name === 'Lucy' || u.name === 'Admin'; | |||
}); | |||
var babelPromises = humanUsers.map(function(u) { | |||
return fetchUserBabel(u.name).then(function(langs) { | |||
cachedBabel[u.name] = langs; | |||
}); | |||
}); | |||
Promise.all(babelPromises).then(function() { | |||
geoShowPeriod(currentPeriod); | |||
}); | |||
}).catch(function(e) { | |||
console.error('GEO dashboard error:', e); | |||
}); | |||
} | |||
window.geoShowPeriod = function(period) { | |||
currentPeriod = period; | |||
var btns = ['day','week','month','all']; | |||
btns.forEach(function(p) { | |||
var btn = document.getElementById('geo-btn-' + p); | |||
if (!btn) return; | |||
if (p === period) { | |||
btn.style.background = '#ff6600'; btn.style.color = '#fff'; | |||
btn.style.border = 'none'; btn.style.fontWeight = 'bold'; | |||
} else { | |||
btn.style.background = '#333'; btn.style.color = '#eee'; | |||
btn.style.border = '1px solid #555'; btn.style.fontWeight = 'normal'; | |||
} | |||
}); | |||
if (!cachedChanges || !cachedUsers) return; | |||
var cutoff = period === 'all' ? '1970-01-01' : startOf(period).toISOString(); | |||
// Build user -> edit count map for period | |||
var userEdits = {}; | |||
cachedChanges.forEach(function(c) { | |||
if (c.timestamp >= cutoff) { | |||
userEdits[c.user] = (userEdits[c.user] || 0) + 1; | |||
} | |||
}); | |||
// Map languages to user counts | |||
var langUsers = {}, langEdits = {}; | |||
var withBabel = 0; | |||
cachedUsers.forEach(function(u) { | |||
var langs = cachedBabel[u.name] || []; | |||
if (langs.length === 0) langs = ['unknown']; | |||
else withBabel++; | |||
var edits = userEdits[u.name] || 0; | |||
langs.slice(0,1).forEach(function(lang) { | |||
langUsers[lang] = (langUsers[lang] || 0) + 1; | |||
langEdits[lang] = (langEdits[lang] || 0) + edits; | |||
}); | |||
}); | |||
var total = cachedUsers.length; | |||
document.getElementById('geo-countries').textContent = Object.keys(langUsers).filter(function(l){return l!=='unknown';}).length; | |||
var rows = Object.keys(langUsers).map(function(lang) { | |||
return { lang: lang, users: langUsers[lang], edits: langEdits[lang] || 0, | |||
pct: total > 0 ? (langUsers[lang] / total * 100) : 0 }; | |||
}).sort(function(a,b){ return b.users - a.users; }); | |||
// Move unknown to bottom | |||
rows.sort(function(a,b) { | |||
if (a.lang === 'unknown') return 1; | |||
if (b.lang === 'unknown') return -1; | |||
return b.users - a.users; | |||
}); | |||
renderTable(rows, total, period); | |||
drawWorldMap(rows.filter(function(r){return r.lang!=='unknown';}).map(function(r){return r.lang;})); | |||
}; | |||
// ── boot ───────────────────────────────────────────────────── | |||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', loadDashboard); | |||
} else { | |||
loadDashboard(); | |||
} | |||
})(); | |||
</script> | |||
Revision as of 05:09, 12 May 2026
Admin Management Tools
This page serves as a quick-reference hub for all administrative tools on AlphaX Wiki.
Users & Rights
- User Rights – Assign or change user group memberships
- Block User – Block a user from editing (View blocked users)
- Unblock User – Remove a block from a user
- Autoblocks – View and manage automatic blocks
- Rename User – Rename a user account
- Create Account – Manually create a new user account
- Active Users List – View recently active users
- User List – Browse all registered users
- User Contributions – View contributions by a specific user
- Deleted User Contributions – View deleted contributions by a user
- User Group Rights – Overview of all user group permissions
- Grants – View available OAuth grants
- Password Policies – View password policy settings
Content Management
- View Deleted Pages – Browse and restore deleted pages
- Change Content Model – Change the content model of a page
- Import Pages – Import pages from another wiki
- Export Pages – Export page content as XML
- Merge Page Histories – Merge the edit histories of two pages
- Protected Pages – View all currently protected pages
- Protected Titles – View titles protected from creation
- Compare Pages – Diff two pages or revisions
Recent Changes & Logs
- Recent Changes – View all recent wiki edits
- Logs – Browse all administrative action logs
- New Pages – View recently created pages
- Gallery of New Files – View recently uploaded files
- Watchlist – Your personal watchlist of tracked pages
Maintenance Reports
- Broken Redirects – Redirects pointing to non-existent pages
- Double Redirects – Redirects pointing to other redirects
- Orphaned Pages – Pages with no incoming links
- Unused Files – Uploaded files not used in any page
- Unused Templates – Templates not transcluded anywhere
- Unused Categories – Categories with no members
- Uncategorized Pages – Pages not assigned to any category
- Uncategorized Files – Files not assigned to any category
- Uncategorized Templates – Templates not assigned to any category
- Uncategorized Categories – Categories not in a parent category
- Dead-End Pages – Pages with no outgoing links
- Wanted Pages – Non-existent pages with incoming links
- Wanted Files – Referenced files that don't exist
- Wanted Categories – Categories used but not created
- Wanted Templates – Templates used but not created
- Unwatched Pages – Pages on no user's watchlist
- Short Pages – Pages with the least content
- Long Pages – Pages with the most content
- Oldest Pages – Pages not edited for the longest time
- Pages with Fewest Revisions – Least-edited pages
Media & Files
- Upload File – Upload a new file to the wiki
- File List – Browse all uploaded files
- Files with Duplicates – Find files with duplicate content
- MIME Search – Search files by MIME type
- Search for Duplicate Files – Find files matching a specific file's content
- Media Statistics – Overview of uploaded file statistics
Data & Tools
- Statistics – Wiki-wide statistics (pages, edits, users)
- Version – Installed MediaWiki version and extensions
- System Messages – View and edit interface messages
- View Interwiki Data – Browse interwiki link mappings
- Namespace Information – Details about all namespaces
- API Sandbox – Test the MediaWiki API interactively
- Expand Templates – Preview template expansion
Page Tools
- What Links Here – Find all pages linking to a given page
- All Special Pages – Browse the full list of special pages
Users by Country
<button id="geo-btn-day" onclick="geoShowPeriod('day')" style="background:#ff6600; color:#fff; border:none; padding:8px 18px; border-radius:4px; cursor:pointer; font-weight:bold;">Today</button>
<button id="geo-btn-week" onclick="geoShowPeriod('week')" style="background:#333; color:#eee; border:1px solid #555; padding:8px 18px; border-radius:4px; cursor:pointer;">This Week</button>
<button id="geo-btn-month" onclick="geoShowPeriod('month')" style="background:#333; color:#eee; border:1px solid #555; padding:8px 18px; border-radius:4px; cursor:pointer;">This Month</button>
<button id="geo-btn-all" onclick="geoShowPeriod('all')" style="background:#333; color:#eee; border:1px solid #555; padding:8px 18px; border-radius:4px; cursor:pointer;">All Time</button>
<svg id="geo-world-map" viewBox="0 0 900 440" style="width:100%; height:auto; display:block;"></svg>
</tbody>
| # | Country | Users | Share | Edits |
|---|---|---|---|---|
| Loading... | ||||
📍 Activity data is derived from wiki edit logs. Country detection uses Babel language tags on user pages. Users without Babel tags are shown as "Unknown".
<script> (function() {
var GEO = window.AlphaXGeo = window.AlphaXGeo || {};
var API = '/api.php';
// ── helpers ──────────────────────────────────────────────────
function isoDate(d) {
return d.toISOString().slice(0,10);
}
function startOf(period) {
var now = new Date();
if (period === 'day') {
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
} else if (period === 'week') {
var d = new Date(now); d.setDate(d.getDate() - d.getDay());
d.setHours(0,0,0,0); return d;
} else if (period === 'month') {
return new Date(now.getFullYear(), now.getMonth(), 1);
}
return new Date(0);
}
// ── country flags & names ─────────────────────────────────────
var COUNTRY_MAP = {
'en':'🇺🇸 English / US','de':'🇩🇪 German','fr':'🇫🇷 French','es':'🇪🇸 Spanish',
'it':'🇮🇹 Italian','pt':'🇧🇷 Portuguese','ru':'🇷🇺 Russian','ja':'🇯🇵 Japanese',
'zh':'🇨🇳 Chinese','ko':'🇰🇷 Korean','ar':'🇸🇦 Arabic','nl':'🇳🇱 Dutch',
'pl':'🇵🇱 Polish','sv':'🇸🇪 Swedish','no':'🇳🇴 Norwegian','da':'🇩🇰 Danish',
'fi':'🇫🇮 Finnish','tr':'🇹🇷 Turkish','cs':'🇨🇿 Czech','hu':'🇭🇺 Hungarian',
'ro':'🇷🇴 Romanian','uk':'🇺🇦 Ukrainian','vi':'🇻🇳 Vietnamese','th':'🇹🇭 Thai',
'id':'🇮🇩 Indonesian','ms':'🇲🇾 Malay','he':'🇮🇱 Hebrew','fa':'🇮🇷 Persian',
'hi':'🇮🇳 Hindi','bn':'🇧🇩 Bengali'
};
// ── fetch all users ───────────────────────────────────────────
function fetchUsers() {
return fetch(API + '?action=query&list=allusers&aulimit=500&format=json&origin=*')
.then(function(r){return r.json();})
.then(function(d){ return d.query.allusers || []; });
}
// ── fetch recent changes for activity ────────────────────────
function fetchRecentChanges(rcstart, rclimit) {
rclimit = rclimit || 500;
var url = API + '?action=query&list=recentchanges&rclimit=' + rclimit +
'&rcprop=user|timestamp&format=json&origin=*';
if (rcstart) url += '&rcstart=' + rcstart;
return fetch(url).then(function(r){return r.json();})
.then(function(d){ return d.query.recentchanges || []; });
}
// ── fetch babel info for a user ───────────────────────────────
function fetchUserBabel(username) {
var url = API + '?action=query&titles=User:' + encodeURIComponent(username) +
'&prop=categories&format=json&origin=*';
return fetch(url).then(function(r){return r.json();})
.then(function(d){
var pages = d.query && d.query.pages ? d.query.pages : {};
var cats = [];
Object.values(pages).forEach(function(p){
(p.categories || []).forEach(function(c){ cats.push(c.title); });
});
// Look for Category:User XX patterns
var langs = [];
cats.forEach(function(cat){
var m = cat.match(/Category:User ([a-z]{2,3})/i);
if (m) langs.push(m[1].toLowerCase());
});
return langs;
});
}
// ── world map SVG (simplified) ────────────────────────────────
var COUNTRY_SHAPES = {
'de': 'M440,145 L455,140 L465,148 L462,165 L448,170 L438,162 Z',
'fr': 'M415,152 L430,148 L438,162 L430,175 L415,172 L408,162 Z',
'es': 'M390,170 L415,168 L415,185 L395,190 L385,182 Z',
'it': 'M450,168 L462,165 L468,180 L460,195 L450,188 L445,178 Z',
'gb': 'M418,135 L428,132 L430,145 L420,148 L414,142 Z',
'ru': 'M460,120 L560,110 L580,135 L540,145 L465,148 Z',
'us': 'M120,150 L220,148 L225,190 L200,200 L115,195 Z',
'cn': 'M620,150 L680,145 L685,175 L650,185 L615,178 Z',
'jp': 'M695,155 L708,152 L710,165 L700,168 L693,163 Z',
'br': 'M195,230 L240,225 L245,275 L215,282 L188,270 Z',
'in': 'M580,175 L615,170 L618,210 L595,218 L575,205 Z',
'au': 'M660,280 L720,275 L725,320 L690,328 L655,315 Z',
};
function drawWorldMap(highlightLangs) {
var svg = document.getElementById('geo-world-map');
if (!svg) return;
// Simple world outline
svg.innerHTML = '<rect width="900" height="440" fill="#1a1a1a"/>' +
'<text x="450" y="30" fill="#444" text-anchor="middle" font-size="12" font-family="sans-serif">World Activity Map (language-based approximation)</text>';
// Draw ocean
var ocean = document.createElementNS('http://www.w3.org/2000/svg','rect');
ocean.setAttribute('width','900'); ocean.setAttribute('height','440');
ocean.setAttribute('fill','#0d1f33'); svg.appendChild(ocean);
// Draw continent blobs
var continents = [
// North America
'M80,100 L240,95 L255,200 L220,230 L170,235 L110,220 L75,170 Z',
// South America
'M155,240 L220,235 L235,360 L195,385 L155,370 L138,320 Z',
// Europe
'M385,90 L480,85 L490,160 L450,175 L400,170 L378,140 Z',
// Africa
'M390,175 L460,170 L470,320 L430,345 L388,335 L375,280 Z',
// Asia
'M490,80 L750,75 L760,230 L700,250 L520,240 L485,180 Z',
// Australia
'M640,280 L740,275 L748,355 L700,368 L638,355 Z',
// Greenland
'M230,50 L290,45 L295,90 L255,95 L228,82 Z',
];
continents.forEach(function(d){
var path = document.createElementNS('http://www.w3.org/2000/svg','path');
path.setAttribute('d', d);
path.setAttribute('fill', '#2a3a2a');
path.setAttribute('stroke', '#1a2a1a');
path.setAttribute('stroke-width', '1');
svg.appendChild(path);
});
// Highlight active countries
if (highlightLangs && highlightLangs.length > 0) {
Object.keys(COUNTRY_SHAPES).forEach(function(cc){
var path = document.createElementNS('http://www.w3.org/2000/svg','path');
path.setAttribute('d', COUNTRY_SHAPES[cc]);
var isActive = highlightLangs.some(function(l){ return l.startsWith(cc); });
path.setAttribute('fill', isActive ? '#ff6600' : '#3a4a3a');
path.setAttribute('stroke', '#555');
path.setAttribute('stroke-width', '1');
path.setAttribute('opacity', isActive ? '0.9' : '0.5');
svg.appendChild(path);
});
}
// Dots for known locations
var locations = [
{x:160, y:165, label:'North America'},
{x:450, y:130, label:'Europe'},
{x:620, y:160, label:'Asia'},
{x:430, y:260, label:'Africa'},
{x:190, y:300, label:'South America'},
{x:690, y:320, label:'Oceania'},
];
locations.forEach(function(loc){
var text = document.createElementNS('http://www.w3.org/2000/svg','text');
text.setAttribute('x', loc.x); text.setAttribute('y', loc.y);
text.setAttribute('fill', '#444'); text.setAttribute('font-size', '10');
text.setAttribute('font-family', 'sans-serif');
text.setAttribute('text-anchor', 'middle');
text.textContent = loc.label;
svg.appendChild(text);
});
}
// ── render table ──────────────────────────────────────────────
function renderTable(rows, total, period) {
var tbody = document.getElementById('geo-table-body');
var titleEl = document.getElementById('geo-table-title');
if (!tbody) return;
var labels = {day:'Today', week:'This Week', month:'This Month', all:'All Time'};
if (titleEl) titleEl.textContent = 'Users by Country – ' + (labels[period] || 'All Time');
if (rows.length === 0) {
tbody.innerHTML = 'No activity data for this period.';
return; }
var html = ;
rows.forEach(function(row, i) {
var bar = '
' +
'' + row.pct.toFixed(1) + '%';
var rowBg = i % 2 === 0 ? '#1a1a1a' : '#1e1e1e';
html += '' + '' + (i+1) + '' + '' + (COUNTRY_MAP[row.lang] || ('🌐 ' + row.lang)) + '' + '' + row.users + '' + '' + bar + '' + '' + row.edits + '' + ''; }); tbody.innerHTML = html; } // ── render activity bars ─────────────────────────────────────── function renderActivityBars(dailyData) { var wrap = document.getElementById('geo-activity-bars'); var labelWrap = document.getElementById('geo-activity-labels'); if (!wrap) return; var max = Math.max.apply(null, Object.values(dailyData).concat([1])); var days = []; for (var i = 29; i >= 0; i--) { var d = new Date(); d.setDate(d.getDate() - i); days.push(isoDate(d)); } var barsHtml = , labelsHtml = ; days.forEach(function(day, idx) { var val = dailyData[day] || 0; var h = Math.max(3, Math.round((val / max) * 70)); var isToday = idx === 29; var color = isToday ? '#ff6600' : (val > 0 ? '#cc5500' : '#222'); barsHtml += '
';
if (idx % 5 === 0 || isToday) {
labelsHtml += '
';
} else {
labelsHtml += '
';
} }); wrap.innerHTML = barsHtml; labelWrap.innerHTML = labelsHtml; }
// ── main data load ────────────────────────────────────────────
var cachedUsers = null;
var cachedChanges = null;
var cachedBabel = {};
var currentPeriod = 'day';
function loadDashboard() {
var now = new Date();
Promise.all([
fetchUsers(),
fetchRecentChanges(null, 500)
]).then(function(results) {
var users = results[0];
var changes = results[1];
cachedUsers = users;
cachedChanges = changes;
document.getElementById('geo-total').textContent = users.length;
document.getElementById('geo-updated').textContent = 'Updated: ' + now.toLocaleTimeString();
// Count activity periods
var dayStart = startOf('day').toISOString();
var weekStart = startOf('week').toISOString();
var monthStart = startOf('month').toISOString();
var activeDay = new Set(), activeWeek = new Set(), activeMonth = new Set();
var daily = {};
changes.forEach(function(c) {
if (c.timestamp >= dayStart) activeDay.add(c.user);
if (c.timestamp >= weekStart) activeWeek.add(c.user);
if (c.timestamp >= monthStart) activeMonth.add(c.user);
var day = c.timestamp.slice(0,10);
daily[day] = (daily[day] || 0) + 1;
});
document.getElementById('geo-active-day').textContent = activeDay.size;
document.getElementById('geo-active-week').textContent = activeWeek.size;
document.getElementById('geo-active-month').textContent = activeMonth.size;
renderActivityBars(daily);
// Fetch babel for all real users (skip bots)
var humanUsers = users.filter(function(u){
return !['Lucy','Babel AutoCreate','Delete page script','FuzzyBot','Maintenance script','MediaWiki default'].includes(u.name) || u.name === 'Lucy' || u.name === 'Admin';
});
var babelPromises = humanUsers.map(function(u) {
return fetchUserBabel(u.name).then(function(langs) {
cachedBabel[u.name] = langs;
});
});
Promise.all(babelPromises).then(function() {
geoShowPeriod(currentPeriod);
});
}).catch(function(e) {
console.error('GEO dashboard error:', e);
});
}
window.geoShowPeriod = function(period) {
currentPeriod = period;
var btns = ['day','week','month','all'];
btns.forEach(function(p) {
var btn = document.getElementById('geo-btn-' + p);
if (!btn) return;
if (p === period) {
btn.style.background = '#ff6600'; btn.style.color = '#fff';
btn.style.border = 'none'; btn.style.fontWeight = 'bold';
} else {
btn.style.background = '#333'; btn.style.color = '#eee';
btn.style.border = '1px solid #555'; btn.style.fontWeight = 'normal';
}
});
if (!cachedChanges || !cachedUsers) return;
var cutoff = period === 'all' ? '1970-01-01' : startOf(period).toISOString();
// Build user -> edit count map for period
var userEdits = {};
cachedChanges.forEach(function(c) {
if (c.timestamp >= cutoff) {
userEdits[c.user] = (userEdits[c.user] || 0) + 1;
}
});
// Map languages to user counts
var langUsers = {}, langEdits = {};
var withBabel = 0;
cachedUsers.forEach(function(u) {
var langs = cachedBabel[u.name] || [];
if (langs.length === 0) langs = ['unknown'];
else withBabel++;
var edits = userEdits[u.name] || 0;
langs.slice(0,1).forEach(function(lang) {
langUsers[lang] = (langUsers[lang] || 0) + 1;
langEdits[lang] = (langEdits[lang] || 0) + edits;
});
});
var total = cachedUsers.length;
document.getElementById('geo-countries').textContent = Object.keys(langUsers).filter(function(l){return l!=='unknown';}).length;
var rows = Object.keys(langUsers).map(function(lang) {
return { lang: lang, users: langUsers[lang], edits: langEdits[lang] || 0,
pct: total > 0 ? (langUsers[lang] / total * 100) : 0 };
}).sort(function(a,b){ return b.users - a.users; });
// Move unknown to bottom
rows.sort(function(a,b) {
if (a.lang === 'unknown') return 1;
if (b.lang === 'unknown') return -1;
return b.users - a.users;
});
renderTable(rows, total, period);
drawWorldMap(rows.filter(function(r){return r.lang!=='unknown';}).map(function(r){return r.lang;}));
};
// ── boot ─────────────────────────────────────────────────────
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', loadDashboard);
} else {
loadDashboard();
}
})(); </script>