fixes lol
This commit is contained in:
parent
c358db55aa
commit
c2bfa06faa
5 changed files with 149 additions and 4 deletions
|
|
@ -2336,6 +2336,41 @@ void AdminController::updateSiteSettings(const HttpRequestPtr &req,
|
|||
};
|
||||
}
|
||||
|
||||
// Update announcement_enabled if provided
|
||||
if (json->isMember("announcement_enabled")) {
|
||||
bool enabled = (*json)["announcement_enabled"].asBool();
|
||||
std::string value = enabled ? "true" : "false";
|
||||
*dbClient << "INSERT INTO site_settings (setting_key, setting_value) VALUES ('announcement_enabled', $1) "
|
||||
"ON CONFLICT (setting_key) DO UPDATE SET setting_value = $1, updated_at = CURRENT_TIMESTAMP"
|
||||
<< value
|
||||
>> [](const Result&) {
|
||||
LOG_INFO << "Announcement enabled setting updated successfully";
|
||||
}
|
||||
>> [](const DrogonDbException& e) {
|
||||
LOG_ERROR << "Failed to update announcement_enabled: " << e.base().what();
|
||||
};
|
||||
}
|
||||
|
||||
// Update announcement_text if provided
|
||||
if (json->isMember("announcement_text")) {
|
||||
std::string text = (*json)["announcement_text"].asString();
|
||||
// Limit to 500 characters
|
||||
if (text.length() > 500) {
|
||||
text = text.substr(0, 500);
|
||||
}
|
||||
// Sanitize to prevent XSS
|
||||
text = htmlEscape(text);
|
||||
*dbClient << "INSERT INTO site_settings (setting_key, setting_value) VALUES ('announcement_text', $1) "
|
||||
"ON CONFLICT (setting_key) DO UPDATE SET setting_value = $1, updated_at = CURRENT_TIMESTAMP"
|
||||
<< text
|
||||
>> [](const Result&) {
|
||||
LOG_INFO << "Announcement text updated successfully";
|
||||
}
|
||||
>> [](const DrogonDbException& e) {
|
||||
LOG_ERROR << "Failed to update announcement_text: " << e.base().what();
|
||||
};
|
||||
}
|
||||
|
||||
// Update censored_words if provided (comma-separated list)
|
||||
if (json->isMember("censored_words")) {
|
||||
// Rate limit: 10 updates per minute per admin
|
||||
|
|
@ -2464,7 +2499,8 @@ void AdminController::getPublicSiteSettings(const HttpRequestPtr &,
|
|||
>> [callback](const Result& r) {
|
||||
// Whitelist of publicly-safe settings
|
||||
static const std::unordered_set<std::string> publicKeys = {
|
||||
"site_title", "logo_path", "logo_display_mode"
|
||||
"site_title", "logo_path", "logo_display_mode",
|
||||
"announcement_enabled", "announcement_text"
|
||||
};
|
||||
|
||||
Json::Value resp;
|
||||
|
|
|
|||
|
|
@ -555,7 +555,9 @@ CREATE TABLE IF NOT EXISTS site_settings (
|
|||
INSERT INTO site_settings (setting_key, setting_value) VALUES
|
||||
('site_title', 'Stream'),
|
||||
('logo_path', ''),
|
||||
('logo_display_mode', 'text') -- 'text', 'image', 'both'
|
||||
('logo_display_mode', 'text'), -- 'text', 'image', 'both'
|
||||
('announcement_enabled', 'false'),
|
||||
('announcement_text', '')
|
||||
ON CONFLICT (setting_key) DO NOTHING;
|
||||
|
||||
-- Trigger for site_settings updated_at
|
||||
|
|
|
|||
|
|
@ -3,5 +3,7 @@ import { writable } from 'svelte/store';
|
|||
export const siteSettings = writable({
|
||||
site_title: 'Stream',
|
||||
logo_path: '',
|
||||
logo_display_mode: 'text'
|
||||
logo_display_mode: 'text',
|
||||
announcement_enabled: false,
|
||||
announcement_text: ''
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@
|
|||
siteSettings.set({
|
||||
site_title: settings.site_title || 'Stream',
|
||||
logo_path: settings.logo_path || '',
|
||||
logo_display_mode: settings.logo_display_mode || 'text'
|
||||
logo_display_mode: settings.logo_display_mode || 'text',
|
||||
announcement_enabled: settings.announcement_enabled === 'true',
|
||||
announcement_text: settings.announcement_text || ''
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
@ -413,6 +415,16 @@
|
|||
.dropdown-item.logout:hover :global(svg) {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.announcement-banner {
|
||||
background: linear-gradient(135deg, #8b5cf6, #a855f7);
|
||||
color: white;
|
||||
padding: 0.75rem 1rem;
|
||||
text-align: center;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if !isPopoutPage}
|
||||
|
|
@ -559,6 +571,12 @@
|
|||
</div>
|
||||
</nav>
|
||||
|
||||
{#if $siteSettings.announcement_enabled && $siteSettings.announcement_text}
|
||||
<div class="announcement-banner">
|
||||
{$siteSettings.announcement_text}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if browser}
|
||||
<GlobalAudioPlayer />
|
||||
<ChatTerminal />
|
||||
|
|
|
|||
|
|
@ -57,6 +57,11 @@
|
|||
lastRenewalError: null,
|
||||
autoRenewalEnabled: true
|
||||
};
|
||||
|
||||
// Announcement Settings
|
||||
let announcementEnabled = false;
|
||||
let announcementText = '';
|
||||
let announcementSaving = false;
|
||||
let sslLoading = false;
|
||||
let sslSaving = false;
|
||||
let sslRequesting = false;
|
||||
|
|
@ -1142,6 +1147,8 @@
|
|||
logo_display_mode: settings.logo_display_mode || 'text'
|
||||
};
|
||||
censoredWords = settings.censored_words || '';
|
||||
announcementEnabled = settings.announcement_enabled === 'true';
|
||||
announcementText = settings.announcement_text || '';
|
||||
} else {
|
||||
console.error('Failed to load site settings');
|
||||
}
|
||||
|
|
@ -1221,6 +1228,35 @@
|
|||
setTimeout(() => { message = ''; error = ''; }, 3000);
|
||||
}
|
||||
|
||||
async function saveAnnouncementSettings() {
|
||||
announcementSaving = true;
|
||||
try {
|
||||
const response = await fetch('/api/admin/settings/site', {
|
||||
method: 'PUT',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
announcement_enabled: announcementEnabled,
|
||||
announcement_text: announcementText
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
message = 'Announcement settings updated successfully';
|
||||
} else {
|
||||
error = 'Failed to update announcement settings';
|
||||
}
|
||||
} catch (e) {
|
||||
error = 'Error updating announcement settings';
|
||||
console.error(e);
|
||||
}
|
||||
announcementSaving = false;
|
||||
|
||||
setTimeout(() => { message = ''; error = ''; }, 3000);
|
||||
}
|
||||
|
||||
async function loadDefaultAvatars() {
|
||||
try {
|
||||
const response = await fetch('/api/admin/default-avatars', {
|
||||
|
|
@ -3603,6 +3639,57 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Announcement Settings -->
|
||||
<div class="card">
|
||||
<h2>Site Announcement</h2>
|
||||
<p style="color: var(--gray); margin-bottom: 2rem;">
|
||||
Display a site-wide announcement banner at the top of all pages
|
||||
</p>
|
||||
|
||||
<div style="display: grid; gap: 1.5rem; max-width: 600px;">
|
||||
<div>
|
||||
<label style="display: flex; align-items: center; gap: 0.75rem; cursor: pointer;">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={announcementEnabled}
|
||||
style="width: auto; margin: 0;"
|
||||
/>
|
||||
<span>Enable announcement banner</span>
|
||||
</label>
|
||||
<small style="color: var(--gray); display: block; margin-top: 0.25rem; margin-left: 1.75rem;">
|
||||
When enabled, the announcement will be displayed at the top of all pages
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="announcementText">
|
||||
Announcement Message
|
||||
</label>
|
||||
<textarea
|
||||
id="announcementText"
|
||||
bind:value={announcementText}
|
||||
placeholder="Enter your announcement message here..."
|
||||
rows="3"
|
||||
maxlength="500"
|
||||
style="margin-top: 0.5rem; width: 100%; resize: vertical;"
|
||||
></textarea>
|
||||
<small style="color: var(--gray); display: block; margin-top: 0.25rem;">
|
||||
{announcementText.length}/500 characters. This message will be displayed to all visitors.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
on:click={saveAnnouncementSettings}
|
||||
disabled={announcementSaving}
|
||||
>
|
||||
{announcementSaving ? 'Saving...' : 'Save Announcement'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{:else if activeTab === 'botkeys'}
|
||||
<div class="card">
|
||||
<h2>Bot API Keys</h2>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue