Initial commit - Aurora Admin v1.0

Co-Authored-By: NixReaper <[email protected]>
This commit is contained in:
WPAuroraAdmin
2026-07-26 16:15:24 -05:00
co-authored by NixReaper
commit 7728104a7c
150 changed files with 24005 additions and 0 deletions
+255
View File
@@ -0,0 +1,255 @@
<script setup>
import { ref, reactive, computed, onMounted } from 'vue';
import AppListShell from '../_shared/AppListShell.vue';
import AppListItem from '../_shared/AppListItem.vue';
import { describeCapability } from './capabilityDescriptions.js';
const props = defineProps({
serverData: { type: Object, required: true },
});
const api = (path, opts = {}) =>
fetch(`${props.serverData.restUrl}aurora-admin/v1${path}`, {
...opts,
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': props.serverData.restNonce,
...(opts.headers || {}),
},
}).then((r) => {
if (!r.ok) throw new Error('Request failed');
return r.json();
});
const roles = ref([]);
const allCapabilities = ref([]);
const search = ref('');
const selectedSlug = ref(null);
const isCreating = ref(false);
const errorMsg = ref('');
const load = async () => {
const data = await api('/roles');
roles.value = data.roles;
allCapabilities.value = data.allCapabilities;
};
onMounted(load);
const filtered = computed(() => {
const q = search.value.trim().toLowerCase();
if (!q) return roles.value;
return roles.value.filter((r) => r.name.toLowerCase().includes(q));
});
const selected = computed(() => roles.value.find((r) => r.slug === selectedSlug.value) || null);
const draft = reactive({ slug: '', name: '', capabilities: [] });
const selectRole = (r) => {
selectedSlug.value = r.slug;
isCreating.value = false;
errorMsg.value = '';
Object.assign(draft, { slug: r.slug, name: r.name, capabilities: [...r.capabilities] });
};
const newRole = () => {
selectedSlug.value = null;
isCreating.value = true;
errorMsg.value = '';
Object.assign(draft, { slug: '', name: '', capabilities: ['read'] });
};
const toggleCap = (cap) => {
const idx = draft.capabilities.indexOf(cap);
if (idx === -1) draft.capabilities.push(cap);
else draft.capabilities.splice(idx, 1);
};
const saving = ref(false);
const save = async () => {
saving.value = true;
errorMsg.value = '';
try {
if (isCreating.value) {
await api('/roles', {
method: 'POST',
body: JSON.stringify({ slug: draft.slug, displayName: draft.name, capabilities: draft.capabilities }),
});
await load();
isCreating.value = false;
selectRole(roles.value.find((r) => r.slug === draft.slug));
} else if (selectedSlug.value) {
await api(`/roles/${selectedSlug.value}`, {
method: 'PUT',
body: JSON.stringify({ displayName: draft.name, capabilities: draft.capabilities }),
});
await load();
}
} catch (e) {
errorMsg.value = 'Could not save — check the role slug is unique.';
} finally {
saving.value = false;
}
};
const remove = async () => {
if (!selectedSlug.value || selected.value?.isDefault) return;
await api(`/roles/${selectedSlug.value}`, { method: 'DELETE' });
selectedSlug.value = null;
await load();
};
</script>
<template>
<AppListShell
v-model:search="search"
title="Role Editor"
search-placeholder="Search roles…"
:count-label="`${filtered.length} ROLES`"
detail-icon="dashicons-groups"
detail-title="Role Details"
detail-text="Select a role from the list to view and edit its capabilities."
:has-selection="!!selected || isCreating"
>
<template #header-actions>
<button type="button" class="is-primary" @click="newRole">+ New Role</button>
</template>
<template #list>
<AppListItem
v-for="r in filtered"
:key="r.slug"
icon="dashicons-groups"
:title="r.name"
:subtitle="`${r.userCount} user${r.userCount === 1 ? '' : 's'}`"
:active="r.slug === selectedSlug"
@click="selectRole(r)"
/>
</template>
<template #detail>
<div class="role-editor">
<div class="role-editor__row">
<input
v-if="isCreating"
v-model="draft.slug"
type="text"
placeholder="role_slug"
class="role-editor__slug"
/>
<input v-model="draft.name" type="text" placeholder="Display name" class="role-editor__name" />
</div>
<p v-if="errorMsg" class="role-editor__error">{{ errorMsg }}</p>
<div class="role-editor__section">
<h3>Capabilities</h3>
<div class="role-editor__caps">
<div v-for="cap in allCapabilities" :key="cap" class="role-editor__cap">
<label>
<input type="checkbox" :checked="draft.capabilities.includes(cap)" @change="toggleCap(cap)" />
{{ cap }}
</label>
<span class="role-editor__cap-info" tabindex="0">
<span class="dashicons dashicons-info-outline" />
<span class="role-editor__tooltip" role="tooltip">{{ describeCapability(cap) }}</span>
</span>
</div>
</div>
</div>
<div class="role-editor__actions">
<button
type="button"
class="is-danger"
:disabled="!selectedSlug || selected?.isDefault"
:title="selected?.isDefault ? 'Default WordPress roles cannot be deleted' : ''"
@click="remove"
>
Delete
</button>
<button type="button" class="is-primary" :disabled="saving" @click="save">
{{ saving ? 'Saving…' : 'Save' }}
</button>
</div>
</div>
</template>
</AppListShell>
</template>
<style scoped>
.role-editor { display: flex; flex-direction: column; gap: 18px; }
.role-editor__row { display: flex; gap: 10px; }
.role-editor__slug, .role-editor__name {
padding: 8px 10px; border-radius: var(--aurora-radius-sm); border: 1px solid var(--aurora-border);
background: var(--aurora-bg-subtle); color: var(--aurora-text); font-size: 0.875rem;
}
.role-editor__name { flex: 1; font-weight: 700; }
.role-editor__slug { width: 160px; }
.role-editor__error { color: #ef4444; font-size: 0.8125rem; margin: 0; }
.role-editor__section h3 {
font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.03em;
color: var(--aurora-text-muted); margin: 0 0 8px;
}
.role-editor__caps {
display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 8px 16px; max-height: 380px; overflow-y: auto;
/* Tooltips (position: absolute, below) need room to render outside the
grid cell without getting clipped by the scroll container. */
padding-bottom: 8px;
}
.role-editor__cap {
display: flex; align-items: center; justify-content: space-between; gap: 6px;
font-size: 0.8125rem; color: var(--aurora-text);
}
.role-editor__cap label { display: flex; align-items: center; gap: 6px; min-width: 0; }
.role-editor__cap label span,
.role-editor__cap label {
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.role-editor__cap-info {
position: relative;
display: inline-flex; align-items: center; justify-content: center;
width: 18px; height: 18px; flex-shrink: 0;
color: var(--aurora-text-muted); cursor: help;
}
.role-editor__cap-info .dashicons { font-size: 15px; width: 15px; height: 15px; }
.role-editor__cap-info:hover { color: var(--aurora-accent); }
.role-editor__tooltip {
position: absolute;
bottom: calc(100% + 8px);
right: -8px;
z-index: 20;
width: 260px;
padding: 10px 12px;
border-radius: var(--aurora-radius-sm);
border: 1px solid var(--aurora-frame-border);
background: var(--aurora-frame-bg);
color: var(--aurora-text);
font-size: 0.75rem;
font-weight: 400;
line-height: 1.5;
white-space: normal;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
visibility: hidden;
opacity: 0;
transition: opacity 0.1s ease;
pointer-events: none;
}
.role-editor__cap-info:hover .role-editor__tooltip,
.role-editor__cap-info:focus .role-editor__tooltip {
visibility: visible;
opacity: 1;
}
.role-editor__actions { display: flex; justify-content: flex-end; gap: 8px; }
.role-editor__actions button {
border: none; border-radius: var(--aurora-radius-sm); padding: 9px 18px; font-size: 0.875rem; cursor: pointer;
background: var(--aurora-bg-subtle); color: var(--aurora-text);
}
.role-editor__actions .is-primary { background: var(--aurora-accent); color: #fff; }
.role-editor__actions .is-danger { color: #ef4444; }
.role-editor__actions button:disabled { opacity: 0.5; cursor: default; }
</style>
@@ -0,0 +1,101 @@
/**
* Human-readable descriptions for WordPress's capability system, shown as
* an info tooltip next to each capability checkbox in Role Editor.
*
* Two groups: the ~60 named capabilities WordPress core actually checks
* (edit_posts, manage_options, etc.), and the legacy level_0…level_10
* "user level" system from WordPress 1.x/early 2.x. Levels predate named
* capabilities and are deprecated, but WordPress still creates them on
* every default role for backward compatibility with old themes/plugins
* that call current_user_can('level_X') or check $user->user_level
* directly instead of a named capability — core itself hasn't checked a
* level anywhere for years. They don't map to anything meaningful beyond
* that compatibility shim, which is exactly why they look unexplained.
*/
const LEVEL_MATCH = /^level_(\d{1,2})$/;
const CORE_CAPABILITIES = {
read: 'The baseline capability every logged-in user needs just to access wp-admin at all.',
edit_posts: 'Edit their own posts.',
edit_others_posts: "Edit posts authored by other users.",
edit_private_posts: 'Edit private posts (not publicly visible).',
edit_published_posts: 'Edit posts that have already been published.',
publish_posts: 'Publish posts, rather than only saving them as drafts.',
delete_posts: 'Delete their own posts.',
delete_others_posts: "Delete posts authored by other users.",
delete_private_posts: 'Delete private posts.',
delete_published_posts: 'Delete posts that have already been published.',
read_private_posts: "View private posts that aren't publicly visible.",
edit_pages: 'Edit their own pages.',
edit_others_pages: "Edit pages authored by other users.",
edit_private_pages: 'Edit private pages.',
edit_published_pages: 'Edit pages that have already been published.',
publish_pages: 'Publish pages, rather than only saving them as drafts.',
delete_pages: 'Delete their own pages.',
delete_others_pages: "Delete pages authored by other users.",
delete_private_pages: 'Delete private pages.',
delete_published_pages: 'Delete pages that have already been published.',
read_private_pages: "View private pages that aren't publicly visible.",
moderate_comments: 'Approve, edit, and delete comments awaiting moderation.',
manage_categories: 'Create, edit, and delete post categories and tags.',
manage_links: 'Manage the legacy Links (blogroll) feature.',
upload_files: 'Upload files to the Media Library.',
list_users: 'View the list of registered users.',
create_users: 'Create new user accounts.',
edit_users: "Edit other users' profiles and roles.",
delete_users: 'Delete other user accounts.',
promote_users: "Change other users' roles.",
remove_users: 'Remove other users from a site (multisite: from this one site, not delete the account entirely).',
manage_options: "Change site-wide settings — one of the broadest capabilities short of being a full administrator.",
edit_theme_options: 'Change theme settings such as menus, widgets, and the Customizer.',
switch_themes: 'Activate a different installed theme.',
install_themes: 'Install new themes from the WordPress theme directory or by file upload.',
update_themes: 'Update installed themes to a newer version.',
delete_themes: 'Delete theme files from the site.',
edit_themes: 'Edit theme source code directly from the admin — high risk, equivalent to direct server file access.',
activate_plugins: 'Activate and deactivate plugins.',
install_plugins: 'Install new plugins from the WordPress plugin directory or by file upload.',
update_plugins: 'Update installed plugins to a newer version.',
delete_plugins: 'Delete plugin files from the site.',
edit_plugins: 'Edit plugin source code directly from the admin — high risk, equivalent to direct server file access.',
edit_files: 'Edit plugin and theme files directly from the admin (Theme/Plugin Editor) — high risk, equivalent to direct server file access.',
edit_dashboard: "Customize the admin dashboard's own widgets.",
update_core: 'Update WordPress core to a newer version.',
import: 'Import content into the site from an external source (Tools › Import).',
export: 'Export site content to a file (Tools › Export).',
customize: 'Access the Theme Customizer.',
unfiltered_html: 'Post raw, unfiltered HTML and JavaScript in content — a real cross-site-scripting (XSS) risk if granted to anyone other than a fully trusted administrator.',
unfiltered_upload: "Upload any file type without WordPress's normal file-type restrictions — a real security risk; only Administrators have this by default, and only on single-site installs.",
manage_network: '(Multisite) Access the network admin dashboard.',
manage_sites: '(Multisite) Create, edit, and delete individual sites on the network.',
manage_network_users: '(Multisite) Manage user accounts network-wide.',
manage_network_plugins: '(Multisite) Manage plugins network-wide.',
manage_network_themes: '(Multisite) Manage themes network-wide.',
manage_network_options: '(Multisite) Change network-wide settings.',
delete_site: '(Multisite) Delete this entire site.',
create_sites: '(Multisite) Create new sites on the network.',
};
export const describeCapability = (cap) => {
const levelMatch = cap.match(LEVEL_MATCH);
if (levelMatch) {
const n = Number(levelMatch[1]);
return (
`Legacy WordPress "user level" ${n} (0–10 scale, 10 highest) from the old pre-2.0 permissions ` +
"system. Deprecated in favor of the named capabilities below, but WordPress still assigns the " +
"full set of levels a role qualifies for, purely so old themes/plugins that check " +
`current_user_can('level_${n}') or a user's numeric level directly still work. Core itself hasn't ` +
'checked a level in years — safe to leave alone; removing it only affects compatibility with very old code.'
);
}
return CORE_CAPABILITIES[cap] || 'Custom capability — not part of WordPress core. Likely added by a plugin or theme.';
};