Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added docs/source/_static/favicon.ico
Binary file not shown.
13 changes: 13 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,26 @@
html_css_files = [
'custom.css',
]
html_favicon = '_static/favicon.ico'
html_theme_options = {
"icon_links": [
{
"name": "GitHub",
"url": "https://github.com/ManagerX-Development/ManagerX",
"icon": "fa-brands fa-square-github",
"type": "fontawesome",
},
{
"name": "Website",
"url": "https://managerx-bot.de",
"icon": "fa-solid fa-globe",
"type": "fontawesome",
},
{
"name": "Instagram",
"url": "https://www.instagram.com/managerx_development",
"icon": "fa-brands fa-instagram",
"type": "fontawesome",
}
],

Expand Down
Binary file added favicon.ico
Binary file not shown.
30 changes: 29 additions & 1 deletion src/api/dashboard/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,31 @@ def calc_trend(today, yesterday):
m_trend, m_trend_val = calc_trend(messages_today, messages_yesterday)
j_trend, j_trend_val = calc_trend(joined_today, joined_yesterday)

# Calculate Server Age (NEU)
server_age_days = (today_dt - guild.created_at).days
server_age_str = f"{server_age_days}d"

# Fetch Staff / User Role members (NEU)
staff_members_list = []
user_members_list = []

if hasattr(bot_instance, 'settings_db') and hasattr(bot_instance.settings_db, 'get_guild_settings'):
guild_settings = bot_instance.settings_db.get_guild_settings(guild_id)
team_role_id = guild_settings.get("team_role_id")
user_role_id = guild_settings.get("user_role_id")

if team_role_id:
team_role = guild.get_role(int(team_role_id))
if team_role:
for m in team_role.members:
staff_members_list.append({"name": m.display_name, "id": str(m.id), "avatar": m.display_avatar.url})

if user_role_id:
user_role = guild.get_role(int(user_role_id))
if user_role:
for m in user_role.members:
user_members_list.append({"name": m.display_name, "id": str(m.id), "avatar": m.display_avatar.url})

# Prepare final stats object
total_members = guild.member_count or len(guild.members)
online_members = 0
Expand All @@ -350,7 +375,10 @@ def calc_trend(today, yesterday):
"messages_today": messages_today,
"messages_trend": m_trend,
"messages_trend_value": m_trend_val,
"history": history
"history": history,
"server_age": server_age_str,
"staff_members": staff_members_list,
"user_members": user_members_list
}
return stats
except Exception as e:
Expand Down
20 changes: 17 additions & 3 deletions src/api/dashboard/settings_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ async def get_settings(guild_id: int):
raise HTTPException(status_code=503, detail="Bot database not ready")

try:
guild_lang = bot_instance.settings_db.get_guild_language(guild_id) if hasattr(bot_instance.settings_db, 'get_guild_language') else "de"
guild_settings = bot_instance.settings_db.get_guild_settings(guild_id) if hasattr(bot_instance.settings_db, 'get_guild_settings') else {}
guild_lang = guild_settings.get("language", "de")

return {
"success": True,
Expand All @@ -63,7 +64,9 @@ async def get_settings(guild_id: int):
"prefix": "!" ,
"auto_mod": True,
"welcome_message": False,
"language": guild_lang
"language": guild_lang,
"user_role_id": str(guild_settings.get("user_role_id")) if guild_settings.get("user_role_id") else None,
"team_role_id": str(guild_settings.get("team_role_id")) if guild_settings.get("team_role_id") else None
}
}
except Exception as e:
Expand All @@ -80,7 +83,18 @@ async def update_settings(guild_id: int, request: Request, user: dict = Depends(
data = await request.json()

try:
# Update logic...
# Update logic
update_data = {}
if "language" in data:
update_data["language"] = data["language"]
if "user_role_id" in data:
update_data["user_role_id"] = int(data["user_role_id"]) if data["user_role_id"] else None
if "team_role_id" in data:
update_data["team_role_id"] = int(data["team_role_id"]) if data["team_role_id"] else None

if update_data and hasattr(bot_instance.settings_db, 'update_guild_settings'):
bot_instance.settings_db.update_guild_settings(guild_id, **update_data)

user_name = user.get("username", "Unbekannter User")
await send_dashboard_notification(guild_id, "Allgemein", user_name)
return {"success": True, "message": "Settings updated"}
Expand Down
39 changes: 34 additions & 5 deletions src/web/components/OverviewSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,40 @@ export default function OverviewSettings({ guildId, initialStats, settings }: Ov
<p className="text-center text-[10px] font-bold uppercase tracking-widest text-muted-foreground mt-4">Nachrichten Volumen (7 Tage)</p>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<QuickStat title="Server Age" value="238d" />
<QuickStat title="Avg Activity" value="High" />
<QuickStat title="Staff Count" value="12" />
<QuickStat title="Uptime" value="99.9%" />
<div className="flex flex-col gap-6">
<div className="grid grid-cols-2 gap-4">
<QuickStat title="Server Age" value={stats?.server_age || "--"} />
<QuickStat title="Avg Activity" value="High" />
</div>

<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-[10px] font-bold uppercase tracking-widest text-primary">Team & Community</span>
<Badge variant="outline" className="text-[9px] h-4 bg-white/5 border-white/10 uppercase tracking-tighter">
{ (stats?.staff_members?.length || 0) + (stats?.user_members?.length || 0) } Members
</Badge>
</div>

<div className="space-y-2 max-h-[120px] overflow-y-auto pr-2 custom-scrollbar">
{stats?.staff_members?.map((m: any) => (
<div key={m.id} className="flex items-center gap-3 p-2 rounded-xl bg-white/5 border border-white/5">
<img src={m.avatar} alt="" className="w-6 h-6 rounded-full border border-primary/30" />
<span className="text-xs font-bold text-white/90 truncate">{m.name}</span>
<Badge className="ml-auto text-[8px] bg-primary/20 text-primary border-primary/20 h-4">TEAM</Badge>
</div>
))}
{stats?.user_members?.map((m: any) => (
<div key={m.id} className="flex items-center gap-3 p-2 rounded-xl bg-white/5 border border-white/5">
<img src={m.avatar} alt="" className="w-6 h-6 rounded-full border border-white/10" />
<span className="text-xs font-bold text-white/90 truncate">{m.name}</span>
<Badge className="ml-auto text-[8px] bg-white/10 text-white/60 border-white/10 h-4">USER</Badge>
</div>
))}
{(!stats?.staff_members?.length && !stats?.user_members?.length) && (
<p className="text-[10px] text-muted-foreground italic text-center py-4">Keine Rollen definiert oder keine Mitglieder gefunden.</p>
)}
</div>
</div>
</div>
</div>
</CardContent>
Expand Down
38 changes: 37 additions & 1 deletion src/web/dashboard/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,51 +68,55 @@
const [welcomeMessage, setWelcomeMessage] = useState(false);
const [theme, setTheme] = useState("dark");
const [language, setLanguage] = useState("de");
const [userRoleId, setUserRoleId] = useState<string | null>(null);
const [teamRoleId, setTeamRoleId] = useState<string | null>(null);

const [stats, setStats] = useState<any>(null);

React.useEffect(() => {
const fetchAllData = async () => {
if (!token || !guildId) return;
setIsLoading(true);
try {
const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:8040';

// Fetch EVERYTHING in ONE call
const res = await fetch(`${baseUrl}/dashboard/guilds/${guildId}/mega-data`, {
headers: { "Authorization": `Bearer ${token}` }
});

if (res.ok) {
const json = await res.json();
if (json.success && json.data) {
const { settings, metadata, stats: guildStats } = json.data;

// Set General Settings
setBotName(settings.bot_name || "ManagerX");
setPrefix(settings.prefix || "!");
setAutoMod(settings.auto_mod ?? true);
setWelcomeMessage(settings.welcome_message ?? false);
setLanguage(settings.language || "de");
setUserRoleId(settings.user_role_id || null);
setTeamRoleId(settings.team_role_id || null);

// Set Guild Metadata
setGuildData({
channels: metadata.channels || [],
roles: metadata.roles || [],
categories: metadata.categories || [],
voiceChannels: metadata.voice_channels || []
});

// Set Stats
setStats(guildStats);
}
}
} catch (e) {
console.error("Failed to fetch consolidated dashboard data", e);
} finally {
setIsLoading(false);
}
};

Check notice on line 119 in src/web/dashboard/SettingsPage.tsx

View check run for this annotation

codefactor.io / CodeFactor

src/web/dashboard/SettingsPage.tsx#L77-L119

Complex Method
fetchAllData();
}, [token, guildId]);

Expand All @@ -122,7 +126,7 @@
const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:8040';
const apiUrl = `${baseUrl}/dashboard/settings/${guildId}`;

const payload = { prefix, autoMod, welcomeMessage, language };
const payload = { prefix, autoMod, welcomeMessage, language, user_role_id: userRoleId, team_role_id: teamRoleId };

const res = await fetch(apiUrl, {
method: "POST",
Expand Down Expand Up @@ -294,6 +298,38 @@
</div>
</div>
</div>

<div className="grid grid-cols-1 md:grid-cols-2 gap-8 pt-4">
<div className="space-y-4">
<Label className="text-sm font-bold uppercase tracking-widest text-primary">Team Rolle</Label>
<select
value={teamRoleId || ""}
onChange={(e) => setTeamRoleId(e.target.value || null)}
className="w-full bg-white/5 border border-white/10 focus:border-primary/50 h-14 rounded-2xl px-6 text-white transition-all appearance-none outline-none"
>
<option value="" className="bg-[#1a1a1a]">Keine Rolle ausgewählt</option>
{guildData.roles.map(role => (
<option key={role.id} value={role.id} className="bg-[#1a1a1a]">{role.name}</option>
))}
</select>
<p className="text-[10px] text-muted-foreground font-medium uppercase tracking-wider">Mitglieder mit dieser Rolle werden als Staff im Dashboard angezeigt.</p>
</div>

<div className="space-y-4">
<Label className="text-sm font-bold uppercase tracking-widest text-primary">User Rolle</Label>
<select
value={userRoleId || ""}
onChange={(e) => setUserRoleId(e.target.value || null)}
className="w-full bg-white/5 border border-white/10 focus:border-primary/50 h-14 rounded-2xl px-6 text-white transition-all appearance-none outline-none"
>
<option value="" className="bg-[#1a1a1a]">Keine Rolle ausgewählt</option>
{guildData.roles.map(role => (
<option key={role.id} value={role.id} className="bg-[#1a1a1a]">{role.name}</option>
))}
</select>
<p className="text-[10px] text-muted-foreground font-medium uppercase tracking-wider">Benutzer mit dieser Rolle werden im User-Quick-View angezeigt.</p>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
Expand Down
Loading