Fix most current issues (overlay issues, some issues with search functionality, and clear warnings functionality)
This commit is contained in:
+84
-5
@@ -3,7 +3,7 @@ const fs = require('node:fs');
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const {addWarning, getWarnings} = require('./services/warnings');
|
const {addWarning, getWarnings, clearWarnings} = require('./services/warnings');
|
||||||
const {getGuildPermissions, updateGuildPermissions} = require('./services/permissions');
|
const {getGuildPermissions, updateGuildPermissions} = require('./services/permissions');
|
||||||
const DEFAULT_SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
const DEFAULT_SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
||||||
const PERMISSION_BITS = {
|
const PERMISSION_BITS = {
|
||||||
@@ -129,12 +129,10 @@ const getSession = (sessionId) => {
|
|||||||
if (!session) {
|
if (!session) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session.expiresAt <= Date.now()) {
|
if (session.expiresAt <= Date.now()) {
|
||||||
sessionStore.delete(sessionId);
|
sessionStore.delete(sessionId);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -146,7 +144,6 @@ const ensureOAuthConfig = () => {
|
|||||||
if (!DISCORD_CLIENT_ID || !DISCORD_CLIENT_SECRET || !DISCORD_REDIRECT_URI) {
|
if (!DISCORD_CLIENT_ID || !DISCORD_CLIENT_SECRET || !DISCORD_REDIRECT_URI) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
clientId: DISCORD_CLIENT_ID,
|
clientId: DISCORD_CLIENT_ID,
|
||||||
clientSecret: DISCORD_CLIENT_SECRET,
|
clientSecret: DISCORD_CLIENT_SECRET,
|
||||||
@@ -179,7 +176,6 @@ const buildAuthorizationUrl = (state) => {
|
|||||||
state,
|
state,
|
||||||
prompt: 'consent',
|
prompt: 'consent',
|
||||||
});
|
});
|
||||||
|
|
||||||
return `${DISCORD_OAUTH_BASE}/authorize?${params.toString()}`;
|
return `${DISCORD_OAUTH_BASE}/authorize?${params.toString()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -235,6 +231,24 @@ const fetchDiscordGuildRoles = async (guildId) => {
|
|||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchDiscordGuildMembersSearch = async (guildId, query) => {
|
||||||
|
if (!DISCORD_BOT_TOKEN) {
|
||||||
|
throw new Error(BOT_TOKEN_ERROR_MESSAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await axios.get(`${DISCORD_API_BASE}/guilds/${guildId}/members/search`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bot ${DISCORD_BOT_TOKEN}`,
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
query,
|
||||||
|
limit: 20,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
const fetchDiscordBotGuilds = async () => {
|
const fetchDiscordBotGuilds = async () => {
|
||||||
if (!DISCORD_BOT_TOKEN) {
|
if (!DISCORD_BOT_TOKEN) {
|
||||||
throw new Error(BOT_TOKEN_ERROR_MESSAGE);
|
throw new Error(BOT_TOKEN_ERROR_MESSAGE);
|
||||||
@@ -582,6 +596,54 @@ const createDashboardApp = () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.get(
|
||||||
|
'/api/guilds/:guildId/members',
|
||||||
|
ensureSession,
|
||||||
|
ensureManageableGuild,
|
||||||
|
async (req, res) => {
|
||||||
|
const query = typeof req.query.query === 'string' ? req.query.query.trim() : '';
|
||||||
|
if (query.length < 2) {
|
||||||
|
return res.json({members: []});
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const members = await fetchDiscordGuildMembersSearch(req.params.guildId, query);
|
||||||
|
const normalized = (members || [])
|
||||||
|
.map((member) => {
|
||||||
|
const user = member.user || {};
|
||||||
|
if (!user.id) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
id: user.id,
|
||||||
|
user: {
|
||||||
|
id: user.id,
|
||||||
|
username: user.username || '',
|
||||||
|
discriminator: user.discriminator || '',
|
||||||
|
global_name: user.global_name || '',
|
||||||
|
avatar: user.avatar || '',
|
||||||
|
},
|
||||||
|
nick: member.nick || '',
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
return res.json({members: normalized});
|
||||||
|
} catch (error) {
|
||||||
|
const status = getDiscordStatusCode(error);
|
||||||
|
if (status === 403 || status === 404) {
|
||||||
|
return res.status(409).json({
|
||||||
|
error: 'BOT_NOT_IN_GUILD',
|
||||||
|
message: 'Bot not added to this guild.',
|
||||||
|
inviteUrl: buildInviteUrl(req.params.guildId),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (error.message === BOT_TOKEN_ERROR_MESSAGE) {
|
||||||
|
return res.status(503).json({error: 'BOT_TOKEN_MISSING', message: 'Discord bot token not configured.'});
|
||||||
|
}
|
||||||
|
return sendDiscordApiError(res, error, 'Failed to search guild members');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
'/api/guilds/:guildId/commands',
|
'/api/guilds/:guildId/commands',
|
||||||
ensureSession,
|
ensureSession,
|
||||||
@@ -651,6 +713,23 @@ const createDashboardApp = () => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.delete(
|
||||||
|
'/api/guilds/:guildId/warnings/:userId',
|
||||||
|
ensureSession,
|
||||||
|
ensureManageableGuild,
|
||||||
|
async (req, res) => {
|
||||||
|
try {
|
||||||
|
const warnings = await clearWarnings({
|
||||||
|
guildId: req.params.guildId,
|
||||||
|
userId: req.params.userId,
|
||||||
|
});
|
||||||
|
return res.json({warnings});
|
||||||
|
} catch (error) {
|
||||||
|
return res.status(500).json({error: 'Failed to clear warnings'});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
return app;
|
return app;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+10
-1
@@ -142,12 +142,21 @@
|
|||||||
<div class="warnings-layout">
|
<div class="warnings-layout">
|
||||||
<div class="warnings-pane">
|
<div class="warnings-pane">
|
||||||
<h2>User lookup</h2>
|
<h2>User lookup</h2>
|
||||||
<p class="muted">Fetch warnings for a user ID.</p>
|
<p class="muted">Search members or paste an ID.</p>
|
||||||
|
<div class="field">
|
||||||
|
<label for="warningsUserSearch">Find member</label>
|
||||||
|
<input type="search" id="warningsUserSearch" placeholder="Type a username" />
|
||||||
|
</div>
|
||||||
|
<div class="member-list" id="warningsUserResults"></div>
|
||||||
|
<p class="muted" id="warningsSelectedUser">No user selected.</p>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="warningsUserInput">User ID</label>
|
<label for="warningsUserInput">User ID</label>
|
||||||
<input type="text" id="warningsUserInput" placeholder="123456789" />
|
<input type="text" id="warningsUserInput" placeholder="123456789" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="button-row">
|
||||||
<button class="button" id="fetchWarningsButton">Fetch warnings</button>
|
<button class="button" id="fetchWarningsButton">Fetch warnings</button>
|
||||||
|
<button class="button button--ghost" id="clearWarningsButton">Clear warnings</button>
|
||||||
|
</div>
|
||||||
<div class="spinner" id="warningsSpinner" hidden></div>
|
<div class="spinner" id="warningsSpinner" hidden></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="warnings-pane">
|
<div class="warnings-pane">
|
||||||
|
|||||||
+159
-8
@@ -9,6 +9,7 @@ const state = {
|
|||||||
botInstalled: false,
|
botInstalled: false,
|
||||||
dirty: false,
|
dirty: false,
|
||||||
originalPermissions: null,
|
originalPermissions: null,
|
||||||
|
selectedWarningUser: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const elements = {
|
const elements = {
|
||||||
@@ -45,7 +46,11 @@ const elements = {
|
|||||||
saveBar: document.getElementById('saveBar'),
|
saveBar: document.getElementById('saveBar'),
|
||||||
botInactiveCallout: document.getElementById('botInactiveCallout'),
|
botInactiveCallout: document.getElementById('botInactiveCallout'),
|
||||||
warningsUserInput: document.getElementById('warningsUserInput'),
|
warningsUserInput: document.getElementById('warningsUserInput'),
|
||||||
|
warningsUserSearch: document.getElementById('warningsUserSearch'),
|
||||||
|
warningsUserResults: document.getElementById('warningsUserResults'),
|
||||||
|
warningsSelectedUser: document.getElementById('warningsSelectedUser'),
|
||||||
fetchWarningsButton: document.getElementById('fetchWarningsButton'),
|
fetchWarningsButton: document.getElementById('fetchWarningsButton'),
|
||||||
|
clearWarningsButton: document.getElementById('clearWarningsButton'),
|
||||||
warningsList: document.getElementById('warningsList'),
|
warningsList: document.getElementById('warningsList'),
|
||||||
warningsEmpty: document.getElementById('warningsEmpty'),
|
warningsEmpty: document.getElementById('warningsEmpty'),
|
||||||
warningsSpinner: document.getElementById('warningsSpinner'),
|
warningsSpinner: document.getElementById('warningsSpinner'),
|
||||||
@@ -85,7 +90,12 @@ const setLoadingDetails = (isLoading) => {
|
|||||||
elements.settingsPanel.hidden = true;
|
elements.settingsPanel.hidden = true;
|
||||||
elements.permissionsPanel.hidden = true;
|
elements.permissionsPanel.hidden = true;
|
||||||
elements.warningsPanel.hidden = true;
|
elements.warningsPanel.hidden = true;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const activeTab = document.querySelector('.tab.active')?.dataset.tab || 'settings';
|
||||||
|
elements.settingsPanel.hidden = activeTab !== 'settings';
|
||||||
|
elements.permissionsPanel.hidden = activeTab !== 'permissions';
|
||||||
|
elements.warningsPanel.hidden = activeTab !== 'warnings';
|
||||||
};
|
};
|
||||||
|
|
||||||
const setDirty = (isDirty) => {
|
const setDirty = (isDirty) => {
|
||||||
@@ -311,13 +321,57 @@ const renderAdminUsers = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatMemberDisplay = (member) => {
|
||||||
|
const user = member?.user || {};
|
||||||
|
const displayName = member?.nick || user.global_name || user.username || 'Unknown user';
|
||||||
|
const tag = user.discriminator && user.discriminator !== '0' ? `#${user.discriminator}` : '';
|
||||||
|
return `${displayName}${tag}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setSelectedWarningUser = (member) => {
|
||||||
|
state.selectedWarningUser = member || null;
|
||||||
|
if (!member) {
|
||||||
|
elements.warningsSelectedUser.textContent = 'No user selected.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
elements.warningsSelectedUser.textContent = `${formatMemberDisplay(member)} (${member.id})`;
|
||||||
|
elements.warningsUserInput.value = member.id;
|
||||||
|
elements.newWarningUserInput.value = member.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setManualWarningUser = (userId) => {
|
||||||
|
state.selectedWarningUser = null;
|
||||||
|
if (!userId) {
|
||||||
|
elements.warningsSelectedUser.textContent = 'No user selected.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
elements.warningsSelectedUser.textContent = `Using manual ID: ${userId}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetWarningSelection = () => {
|
||||||
|
setSelectedWarningUser(null);
|
||||||
|
elements.warningsUserResults.innerHTML = '';
|
||||||
|
elements.warningsUserSearch.value = '';
|
||||||
|
elements.warningsUserInput.value = '';
|
||||||
|
elements.newWarningUserInput.value = '';
|
||||||
|
elements.warningsList.innerHTML = '';
|
||||||
|
elements.warningsEmpty.hidden = true;
|
||||||
|
};
|
||||||
|
|
||||||
const ensureCommandEntry = (commandName) => {
|
const ensureCommandEntry = (commandName) => {
|
||||||
if (!state.permissions.commandPermissions[commandName]) {
|
if (!state.permissions.commandPermissions[commandName]) {
|
||||||
state.permissions.commandPermissions[commandName] = {
|
state.permissions.commandPermissions[commandName] = {
|
||||||
allowedRoleIds: [],
|
allowedRoleIds: [],
|
||||||
allowedUserIds: [],
|
allowedUserIds: [],
|
||||||
|
restricted: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const entry = state.permissions.commandPermissions[commandName];
|
||||||
|
entry.allowedRoleIds = Array.isArray(entry.allowedRoleIds) ? entry.allowedRoleIds : [];
|
||||||
|
entry.allowedUserIds = Array.isArray(entry.allowedUserIds) ? entry.allowedUserIds : [];
|
||||||
|
if (typeof entry.restricted !== 'boolean') {
|
||||||
|
entry.restricted = entry.allowedRoleIds.length > 0 || entry.allowedUserIds.length > 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderCommands = () => {
|
const renderCommands = () => {
|
||||||
@@ -332,6 +386,7 @@ const renderCommands = () => {
|
|||||||
commands.forEach((commandName) => {
|
commands.forEach((commandName) => {
|
||||||
ensureCommandEntry(commandName);
|
ensureCommandEntry(commandName);
|
||||||
const commandConfig = state.permissions.commandPermissions[commandName];
|
const commandConfig = state.permissions.commandPermissions[commandName];
|
||||||
|
const isRestricted = Boolean(commandConfig.restricted);
|
||||||
|
|
||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = `command-card${state.botInstalled ? '' : ' command-card--disabled'}`;
|
card.className = `command-card${state.botInstalled ? '' : ' command-card--disabled'}`;
|
||||||
@@ -345,7 +400,7 @@ const renderCommands = () => {
|
|||||||
toggleLabel.className = 'toggle';
|
toggleLabel.className = 'toggle';
|
||||||
const toggle = document.createElement('input');
|
const toggle = document.createElement('input');
|
||||||
toggle.type = 'checkbox';
|
toggle.type = 'checkbox';
|
||||||
toggle.checked = commandConfig.allowedRoleIds.length > 0 || commandConfig.allowedUserIds.length > 0;
|
toggle.checked = isRestricted;
|
||||||
toggleLabel.appendChild(toggle);
|
toggleLabel.appendChild(toggle);
|
||||||
toggleLabel.appendChild(document.createTextNode('Restricted'));
|
toggleLabel.appendChild(document.createTextNode('Restricted'));
|
||||||
|
|
||||||
@@ -440,7 +495,7 @@ const renderCommands = () => {
|
|||||||
clearButton.addEventListener('click', () => {
|
clearButton.addEventListener('click', () => {
|
||||||
commandConfig.allowedRoleIds = [];
|
commandConfig.allowedRoleIds = [];
|
||||||
commandConfig.allowedUserIds = [];
|
commandConfig.allowedUserIds = [];
|
||||||
toggle.checked = false;
|
commandConfig.restricted = false;
|
||||||
setDirty(true);
|
setDirty(true);
|
||||||
renderCommands();
|
renderCommands();
|
||||||
});
|
});
|
||||||
@@ -452,15 +507,15 @@ const renderCommands = () => {
|
|||||||
|
|
||||||
toggle.addEventListener('change', () => {
|
toggle.addEventListener('change', () => {
|
||||||
setDirty(true);
|
setDirty(true);
|
||||||
|
commandConfig.restricted = toggle.checked;
|
||||||
if (!toggle.checked) {
|
if (!toggle.checked) {
|
||||||
commandConfig.allowedRoleIds = [];
|
commandConfig.allowedRoleIds = [];
|
||||||
commandConfig.allowedUserIds = [];
|
commandConfig.allowedUserIds = [];
|
||||||
setDirty(true);
|
|
||||||
}
|
}
|
||||||
renderCommands();
|
renderCommands();
|
||||||
});
|
});
|
||||||
|
|
||||||
const controlsDisabled = !state.botInstalled || !toggle.checked;
|
const controlsDisabled = !state.botInstalled || !isRestricted;
|
||||||
userInput.disabled = controlsDisabled;
|
userInput.disabled = controlsDisabled;
|
||||||
addUserButton.disabled = controlsDisabled;
|
addUserButton.disabled = controlsDisabled;
|
||||||
|
|
||||||
@@ -473,8 +528,8 @@ const renderCommands = () => {
|
|||||||
setDirty(true);
|
setDirty(true);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
disabled: !state.botInstalled || !toggle.checked,
|
disabled: !state.botInstalled || !isRestricted,
|
||||||
emptyMessage: toggle.checked ? 'Select roles to allow.' : 'Allow all roles.',
|
emptyMessage: isRestricted ? 'Select roles to allow.' : 'Allow all roles.',
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -567,6 +622,7 @@ const loadGuildDetails = async () => {
|
|||||||
setLoadingDetails(true);
|
setLoadingDetails(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
setDirty(false);
|
setDirty(false);
|
||||||
|
resetWarningSelection();
|
||||||
|
|
||||||
const guild = state.guilds.find((entry) => entry.id === state.selectedGuildId);
|
const guild = state.guilds.find((entry) => entry.id === state.selectedGuildId);
|
||||||
updateBotInstalled();
|
updateBotInstalled();
|
||||||
@@ -699,8 +755,64 @@ const switchTab = (tabName) => {
|
|||||||
elements.warningsPanel.hidden = tabName !== 'warnings';
|
elements.warningsPanel.hidden = tabName !== 'warnings';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resolveWarningUserId = (value) => {
|
||||||
|
if (value && value.trim()) {
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
return state.selectedWarningUser?.id || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderWarningMembers = (members = []) => {
|
||||||
|
elements.warningsUserResults.innerHTML = '';
|
||||||
|
if (!members.length) {
|
||||||
|
const empty = document.createElement('span');
|
||||||
|
empty.className = 'muted';
|
||||||
|
empty.textContent = 'No members found.';
|
||||||
|
elements.warningsUserResults.appendChild(empty);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
members.forEach((member) => {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.type = 'button';
|
||||||
|
button.className = `member-item${state.selectedWarningUser?.id === member.id ? ' active' : ''}`;
|
||||||
|
const meta = document.createElement('div');
|
||||||
|
meta.className = 'member-item__meta';
|
||||||
|
const name = document.createElement('span');
|
||||||
|
name.className = 'member-item__name';
|
||||||
|
name.textContent = formatMemberDisplay(member);
|
||||||
|
const id = document.createElement('span');
|
||||||
|
id.className = 'member-item__id';
|
||||||
|
id.textContent = member.id;
|
||||||
|
meta.appendChild(name);
|
||||||
|
meta.appendChild(id);
|
||||||
|
button.appendChild(meta);
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
setSelectedWarningUser(member);
|
||||||
|
renderWarningMembers(members);
|
||||||
|
});
|
||||||
|
elements.warningsUserResults.appendChild(button);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const searchWarningMembers = async (query) => {
|
||||||
|
if (!state.selectedGuildId || !query || query.trim().length < 2) {
|
||||||
|
elements.warningsUserResults.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const data = await fetchDashboardJson(
|
||||||
|
`/api/guilds/${state.selectedGuildId}/members?query=${encodeURIComponent(query.trim())}`,
|
||||||
|
);
|
||||||
|
const members = data.members || [];
|
||||||
|
renderWarningMembers(members);
|
||||||
|
} catch (error) {
|
||||||
|
handleFetchError(error, 'Failed to search members.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const fetchWarnings = async () => {
|
const fetchWarnings = async () => {
|
||||||
const userId = elements.warningsUserInput.value.trim();
|
const userId = resolveWarningUserId(elements.warningsUserInput.value);
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
setError({message: 'Enter a user ID to fetch warnings.'});
|
setError({message: 'Enter a user ID to fetch warnings.'});
|
||||||
return;
|
return;
|
||||||
@@ -761,7 +873,7 @@ const updateWarningCount = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addWarning = async () => {
|
const addWarning = async () => {
|
||||||
const userId = elements.newWarningUserInput.value.trim();
|
const userId = resolveWarningUserId(elements.newWarningUserInput.value);
|
||||||
const reason = elements.warningReasonInput.value.trim();
|
const reason = elements.warningReasonInput.value.trim();
|
||||||
if (!userId || !reason) {
|
if (!userId || !reason) {
|
||||||
elements.warningStatus.textContent = 'User ID and reason are required.';
|
elements.warningStatus.textContent = 'User ID and reason are required.';
|
||||||
@@ -787,6 +899,29 @@ const addWarning = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearWarnings = async () => {
|
||||||
|
const userId = resolveWarningUserId(elements.warningsUserInput.value);
|
||||||
|
if (!userId) {
|
||||||
|
setError({message: 'Select a user to clear warnings.'});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const proceed = window.confirm('Clear all warnings for this user?');
|
||||||
|
if (!proceed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await fetchDashboardJson(`/api/guilds/${state.selectedGuildId}/warnings/${userId}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
});
|
||||||
|
elements.warningsList.innerHTML = '';
|
||||||
|
elements.warningsEmpty.hidden = false;
|
||||||
|
elements.warningStatus.textContent = 'Warnings cleared.';
|
||||||
|
dashboardApi.showToast('Warnings cleared.', 'success');
|
||||||
|
} catch (error) {
|
||||||
|
handleFetchError(error, 'Failed to clear warnings.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const refreshBotStatus = async () => {
|
const refreshBotStatus = async () => {
|
||||||
await loadBotGuilds();
|
await loadBotGuilds();
|
||||||
updateBotInstalled();
|
updateBotInstalled();
|
||||||
@@ -877,8 +1012,23 @@ const initDashboard = async () => {
|
|||||||
elements.savePermissionsButton.addEventListener('click', savePermissions);
|
elements.savePermissionsButton.addEventListener('click', savePermissions);
|
||||||
elements.discardChangesButton.addEventListener('click', discardChanges);
|
elements.discardChangesButton.addEventListener('click', discardChanges);
|
||||||
elements.fetchWarningsButton.addEventListener('click', fetchWarnings);
|
elements.fetchWarningsButton.addEventListener('click', fetchWarnings);
|
||||||
|
elements.clearWarningsButton.addEventListener('click', clearWarnings);
|
||||||
elements.addWarningButton.addEventListener('click', addWarning);
|
elements.addWarningButton.addEventListener('click', addWarning);
|
||||||
elements.warningReasonInput.addEventListener('input', updateWarningCount);
|
elements.warningReasonInput.addEventListener('input', updateWarningCount);
|
||||||
|
elements.warningsUserSearch.addEventListener(
|
||||||
|
'input',
|
||||||
|
dashboardApi.debounce((event) => {
|
||||||
|
searchWarningMembers(event.target.value);
|
||||||
|
}, 250),
|
||||||
|
);
|
||||||
|
elements.warningsUserInput.addEventListener('input', (event) => {
|
||||||
|
setManualWarningUser(event.target.value.trim());
|
||||||
|
});
|
||||||
|
elements.newWarningUserInput.addEventListener('input', (event) => {
|
||||||
|
if (event.target.value.trim()) {
|
||||||
|
setManualWarningUser(event.target.value.trim());
|
||||||
|
}
|
||||||
|
});
|
||||||
elements.inviteButton.addEventListener('click', openInvite);
|
elements.inviteButton.addEventListener('click', openInvite);
|
||||||
elements.refreshBotButton.addEventListener('click', refreshBotStatus);
|
elements.refreshBotButton.addEventListener('click', refreshBotStatus);
|
||||||
elements.clearAllCommands.addEventListener('click', () => {
|
elements.clearAllCommands.addEventListener('click', () => {
|
||||||
@@ -886,6 +1036,7 @@ const initDashboard = async () => {
|
|||||||
state.permissions.commandPermissions[commandName] = {
|
state.permissions.commandPermissions[commandName] = {
|
||||||
allowedRoleIds: [],
|
allowedRoleIds: [],
|
||||||
allowedUserIds: [],
|
allowedUserIds: [],
|
||||||
|
restricted: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
setDirty(true);
|
setDirty(true);
|
||||||
|
|||||||
@@ -18,6 +18,10 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
@@ -532,6 +536,52 @@ textarea {
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.member-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
max-height: 220px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: #131826;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
background: var(--card);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-item__meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-item__name {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-item__id {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-item.active {
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
.toggle {
|
.toggle {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -701,6 +751,10 @@ textarea {
|
|||||||
z-index: 40;
|
z-index: 40;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.modal__content {
|
.modal__content {
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
|
|||||||
@@ -40,7 +40,21 @@ const getWarnings = async ({ guildId, userId }) => {
|
|||||||
return record?.warnings ?? [];
|
return record?.warnings ?? [];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearWarnings = async ({ guildId, userId }) => {
|
||||||
|
const collection = await getWarningsCollection();
|
||||||
|
const result = await collection.findOneAndUpdate(
|
||||||
|
{ guildId, userId },
|
||||||
|
{ $set: { warnings: [] } },
|
||||||
|
{ returnDocument: 'after', upsert: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
await updateStrikes({ guildId, userId, strikes: 0 });
|
||||||
|
|
||||||
|
return result.value?.warnings ?? [];
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
addWarning,
|
addWarning,
|
||||||
getWarnings,
|
getWarnings,
|
||||||
|
clearWarnings,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user