Skip to content

Commit 1e6fdd6

Browse files
committed
LevelUp - dashboard settings buildout [on hold for now]
1 parent 622277f commit 1e6fdd6

File tree

3 files changed

+60
-146
lines changed

3 files changed

+60
-146
lines changed

levelup/dashboard/integration.py

Lines changed: 46 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,21 @@
44
from pathlib import Path
55

66
import discord
7-
import pydantic
87
from redbot.core import commands
98
from redbot.core.i18n import Translator
109

1110
from ..abc import MixinMeta
12-
from ..common import formatter, models
11+
from ..common import formatter
1312

1413
_ = Translator("LevelUp", __file__)
1514
log = logging.getLogger("red.levelup.dashboard")
15+
root = Path(__file__).parent
16+
static = root / "static"
17+
templates = root / "templates"
1618

1719

18-
def dashboard_page(*args, **kwargs):
19-
def decorator(func: t.Callable):
20+
def dashboard_page(*args: t.Any, **kwargs: t.Any) -> t.Callable[[t.Any], t.Any]:
21+
def decorator(func: t.Callable) -> t.Callable[[t.Any], t.Any]:
2022
func.__dashboard_decorator_params__ = (args, kwargs)
2123
return func
2224

@@ -74,12 +76,9 @@ async def get_dashboard_leaderboard(
7476
"error_message": _("There is no data for the weekly leaderboard yet, please chat a bit first."),
7577
}
7678

77-
parent = Path(__file__).parent
78-
79-
source_path = parent / "templates" / "leaderboard.html"
80-
static_dir = parent / "static"
81-
js_path = static_dir / "js" / "leaderboard.js"
82-
css_path = static_dir / "css" / "leaderboard.css"
79+
source_path = templates / "leaderboard.html"
80+
js_path = static / "js" / "leaderboard.js"
81+
css_path = static / "css" / "leaderboard.css"
8382

8483
# Inject JS and CSS into the HTML source for full page loads
8584
source = (
@@ -133,131 +132,74 @@ async def get_dashboard_leaderboard(
133132

134133
@dashboard_page(name="leaderboard", description="Display the guild leaderboard.")
135134
async def leaderboard_page(
136-
self, user: discord.User, guild: discord.Guild, stat: str = None, query: t.Optional[str] = None, **kwargs
135+
self, user: discord.User, guild: discord.Guild, stat: str = None, **kwargs
137136
) -> t.Dict[str, t.Any]:
138137
stat = stat if stat is not None and stat in {"exp", "messages", "voice", "stars"} else "exp"
139-
return await self.get_dashboard_leaderboard(user, guild, "lb", stat, query, **kwargs)
138+
return await self.get_dashboard_leaderboard(user, guild, "lb", stat, **kwargs)
140139

141140
@dashboard_page(name="weekly", description="Display the guild weekly leaderboard.")
142141
async def weekly_page(
143-
self, user: discord.User, guild: discord.Guild, stat: str = None, query: t.Optional[str] = None, **kwargs
142+
self, user: discord.User, guild: discord.Guild, stat: str = None, **kwargs
144143
) -> t.Dict[str, t.Any]:
145144
stat = stat if stat is not None and stat in {"exp", "messages", "voice", "stars"} else "exp"
146-
return await self.get_dashboard_leaderboard(user, guild, "weekly", stat, query, **kwargs)
147-
148-
async def save_settings(self, user: discord.User, guild: discord.Guild, data: dict, **kwargs):
149-
member = guild.get_member(user.id)
150-
if not member:
151-
return {
152-
"status": 1,
153-
"error_title": _("Member not found"),
154-
"error_message": _("You are not a member of this guild."),
155-
}
156-
if not await self.bot.is_admin(member):
157-
return {
158-
"status": 1,
159-
"error_title": _("Insufficient permissions"),
160-
"error_message": _("You need to be an admin to access this page."),
161-
}
145+
return await self.get_dashboard_leaderboard(user, guild, "weekly", stat, **kwargs)
162146

163-
try:
164-
new_conf = models.GuildSettings.model_validate(data)
165-
except pydantic.ValidationError as e:
166-
return {
167-
"status": 1,
168-
"error_title": _("Validation error"),
169-
"error_message": str(e),
170-
}
171-
172-
conf = self.db.get_conf(guild)
173-
for k, v in data.items():
174-
setattr(conf, k, getattr(new_conf, k, v))
175-
self.save()
176-
return {
177-
"status": 0,
178-
"success_title": _("Settings saved"),
179-
"success_message": _("The settings have been saved."),
180-
}
147+
# @dashboard_page(name="settings", description="Configure the leveling system.", methods=("GET", "POST"))
148+
async def cog_settings(self, user: discord.User, guild: discord.Guild, **kwargs):
149+
import wtforms # pip install WTForms
150+
from flask_wtf import FlaskForm # pip install Flask-WTF
181151

182-
# @dashboard_page(name="settings", description="Configure the leveling system.")
183-
async def get_cog_settings(self, user: discord.User, guild: discord.Guild, **kwargs):
184-
if kwargs.get("save") and (data := kwargs.get("new_data")):
185-
log.info(f"Saving settings for {guild.name} by {user.name}")
186-
return await self.save_settings(user, guild, data, **kwargs)
187152
log.info(f"Getting settings for {guild.name} by {user.name}")
188153
member = guild.get_member(user.id)
189154
if not member:
155+
log.warning(f"Member {user.name} not found in guild {guild.name}")
190156
return {
191157
"status": 1,
192158
"error_title": _("Member not found"),
193159
"error_message": _("You are not a member of this guild."),
194160
}
195161
if not await self.bot.is_admin(member):
162+
log.warning(f"Member {user.name} is not an admin in guild {guild.name}")
196163
return {
197164
"status": 1,
198165
"error_title": _("Insufficient permissions"),
199166
"error_message": _("You need to be an admin to access this page."),
200167
}
201168

202169
conf = self.db.get_conf(guild)
203-
settings = conf.model_dump(mode="json", exclude=["users", "users_weekly"])
204170

205-
users = []
206-
for user in guild.members:
207-
users.append(
208-
{
209-
"id": user.id,
210-
"name": user.name,
211-
"avatar": user.display_avatar.url,
212-
}
213-
)
171+
class SettingsForm(kwargs["Form"]):
172+
def __init__(self):
173+
super().__init__(prefix="levelup_settings_form_")
214174

215-
emojis = []
216-
for emoji in guild.emojis:
217-
emojis.append(
218-
{
219-
"id": emoji.id,
220-
"name": emoji.name,
221-
"url": emoji.url,
222-
}
175+
# General settings
176+
enabled = wtforms.BooleanField(_("Enabled:"), default=conf.enabled)
177+
algo_base = wtforms.IntegerField(
178+
_("Algorithm Base:"), default=conf.algorithm.base, validators=[wtforms.validators.InputRequired()]
223179
)
224-
225-
# Add roles data for the settings page
226-
roles = []
227-
for role in sorted(guild.roles, reverse=True):
228-
if role.is_default():
229-
continue
230-
roles.append(
231-
{
232-
"id": role.id,
233-
"name": role.name,
234-
"color": str(role.color),
235-
"position": role.position,
236-
}
180+
algo_multiplier = wtforms.FloatField(
181+
_("Algorithm Multiplier:"), default=conf.algorithm.exp, validators=[wtforms.validators.InputRequired()]
237182
)
238183

239-
parent = Path(__file__).parent
240-
source_path = parent / "templates" / "settings.html"
241-
static_dir = parent / "static"
242-
js_path = static_dir / "js" / "settings.js"
243-
css_path = static_dir / "css" / "settings.css"
184+
# Submit button
185+
submit = wtforms.SubmitField(_("Save Settings"))
244186

245-
# Inject JS and CSS into the HTML source for full page loads
246-
source = (
247-
f"<style>\n{css_path.read_text() if css_path.exists() else ''}\n</style>\n\n"
248-
+ source_path.read_text().strip()
249-
+ f"\n\n<script>\n{js_path.read_text()}\n</script>"
250-
)
187+
form: FlaskForm = SettingsForm()
251188

252-
payload = {
189+
# Handle form submission
190+
if form.validate_on_submit() and await form.validate_dpy_converters():
191+
log.info(f"Form validated for {guild.name} by {user.name}")
192+
conf.enabled = form.enabled.data
193+
conf.algorithm.base = form.algo_base.data or 100
194+
conf.algorithm.exp = form.algo_multiplier.data or 2.0
195+
self.save()
196+
return {
197+
"status": 0,
198+
"notifications": [{"message": _("Settings saved"), "category": "success"}],
199+
"redirect_url": kwargs["request_url"],
200+
}
201+
source = (templates / "settings.html").read_text()
202+
return {
253203
"status": 0,
254-
"web_content": {
255-
"source": source,
256-
"expanded": True,
257-
"settings": settings,
258-
"users": users,
259-
"emojis": emojis,
260-
"roles": roles,
261-
},
204+
"web_content": {"source": source, "settings_form": form},
262205
}
263-
return payload

levelup/dashboard/static/js/settings.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,33 @@ document.addEventListener('alpine:init', () => {
99

1010
// METHODS
1111
async saveSettings(event) {
12-
if (event) event.preventDefault();
12+
console.log(`Event: ${event}`);
1313
console.log("Saving settings:", this.settings);
1414
try {
15-
// Get CSRF token from meta tag
16-
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content;
15+
// Get CSRF token from span element's data-value attribute
16+
const csrfToken = document.querySelector('#settings-csrf-token').value;
17+
console.log("CSRF token:", csrfToken);
1718

1819
if (!csrfToken) {
1920
console.error("CSRF token not found");
2021
alert("CSRF token not found. Please refresh the page and try again.");
2122
return;
2223
}
2324

25+
// Wrap settings in the expected structure
26+
const requestData = {
27+
save: true,
28+
new_data: this.settings
29+
};
30+
2431
const response = await fetch(window.location.href, {
2532
method: 'POST',
2633
headers: {
2734
'Content-Type': 'application/json',
2835
'X-CSRFToken': csrfToken // Changed from X-CSRF-Token to X-CSRFToken
2936
},
3037
credentials: 'same-origin', // Added to ensure cookies are sent
31-
body: JSON.stringify(this.settings)
38+
body: JSON.stringify(requestData)
3239
});
3340

3441
const result = await response.json();

levelup/dashboard/templates/settings.html

Lines changed: 3 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,7 @@
33
src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"
44
></script>
55

6-
<!-- Add CSRF token meta tag -->
7-
<meta name="csrf-token" content="{{ csrf_token }}" />
8-
9-
<div class="container-fluid" x-data="levelUpSettings({{ data }})">
10-
<div class="card mb-3">
11-
<div class="card-header">
12-
<h3>LevelUp Settings</h3>
13-
</div>
14-
<div class="card-body">
15-
<form @submit.prevent>
16-
<!-- Enable/Disable Leveling -->
17-
<div class="mb-3">
18-
<div class="form-check form-switch ps-0">
19-
<input
20-
id="setting-enabled"
21-
class="form-check-input ms-0"
22-
type="checkbox"
23-
x-model="settings.enabled"
24-
x-bind:checked="settings.enabled"
25-
/>
26-
<label class="form-check-label"
27-
>Enable the leveling system in this server.</label
28-
>
29-
</div>
30-
</div>
31-
<!-- Submit Button -->
32-
<div class="mt-4">
33-
<button
34-
type="button"
35-
class="btn btn-primary"
36-
x-bind:href="{{ url_for_query( save=True, new_data=settings ) }}"
37-
>
38-
Save Settings
39-
</button>
40-
</div>
41-
</form>
42-
</div>
43-
</div>
6+
<div class="container-fluid">
7+
<h3 class="text-dark mb-4">This page is a work in progress!</h3>
8+
<div class="card-body">{{ settings_form|safe }}</div>
449
</div>

0 commit comments

Comments
 (0)