1
0
mirror of https://github.com/Jermolene/TiddlyWiki5 synced 2025-04-08 19:56:39 +00:00

fix users list page, add manage roles page

This commit is contained in:
webplusai 2024-10-01 07:08:41 +00:00
parent 887025bfd7
commit 316aa018b1
12 changed files with 745 additions and 104 deletions

View File

@ -34,8 +34,6 @@ GET /admin/users
last_login: user.last_login ? new Date(user.last_login).toISOString() : ''
}));
console.log("Processed userList =>", userList);
response.writeHead(200, "OK", {
"Content-Type": "text/html"
});
@ -45,10 +43,12 @@ GET /admin/users
variables: {
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/get-users",
"user-list": JSON.stringify(userList),
"username": state.authenticatedUser ? state.authenticatedUser.username : "Guest",
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no"
}
});
response.write(html);
response.end();
};
}());
}());

View File

@ -0,0 +1,36 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/routes/handlers/manage-roles.js
type: application/javascript
module-type: mws-route
GET /admin/roles
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "GET";
exports.path = /^\/admin\/roles\/?$/;
exports.handler = function(request, response, state) {
var roles = state.server.sqlTiddlerDatabase.listRoles();
response.writeHead(200, "OK", {"Content-Type": "text/html"});
var html = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/page", {
variables: {
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/manage-roles",
"roles-list": JSON.stringify(roles),
"username": state.authenticatedUser ? state.authenticatedUser.username : "Guest",
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no"
}
});
response.write(html);
response.end();
};
}());

View File

@ -0,0 +1,70 @@
/*\
title: $:/plugins/tiddlywiki/multiwikiserver/routes/handlers/manage-user.js
type: application/javascript
module-type: mws-route
GET /admin/users/:user_id
\*/
(function() {
/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";
exports.method = "GET";
exports.path = /^\/admin\/users\/([^\/]+)\/?$/;
exports.handler = function(request,response,state) {
var user_id = $tw.utils.decodeURIComponentSafe(state.params[0]);
console.log("user_id =>", user_id)
var userData = state.server.sqlTiddlerDatabase.getUser(user_id);
if(!userData) {
response.writeHead(404, "Not Found", {"Content-Type": "text/html"});
var errorHtml = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/error", {
variables: {
"error-message": "User not found"
}
});
response.write(errorHtml);
response.end();
return;
}
// Convert dates to strings and ensure all necessary fields are present
const user = {
user_id: userData.user_id || '',
username: userData.username || '',
email: userData.email || '',
created_at: userData.created_at ? new Date(userData.created_at).toISOString() : '',
last_login: userData.last_login ? new Date(userData.last_login).toISOString() : ''
};
// Get all roles which the user has been assigned
var userRoles = state.server.sqlTiddlerDatabase.getUserRoles(user_id);
var allRoles = state.server.sqlTiddlerDatabase.listRoles();
response.writeHead(200, "OK", {
"Content-Type": "text/html"
});
// Render the html
var html = $tw.mws.store.adminWiki.renderTiddler("text/plain", "$:/plugins/tiddlywiki/multiwikiserver/templates/page", {
variables: {
"page-content": "$:/plugins/tiddlywiki/multiwikiserver/templates/manage-user",
"user": JSON.stringify(user),
"user-roles": JSON.stringify(userRoles),
"all-roles": JSON.stringify(allRoles),
"username": state.authenticatedUser ? state.authenticatedUser.username : "Guest",
"user-is-admin": state.authenticatedUser && state.authenticatedUser.isAdmin ? "yes" : "no"
}
});
response.write(html);
response.end();
};
}());

View File

@ -1156,6 +1156,17 @@ SqlTiddlerDatabase.prototype.removePermissionFromRole = function(roleId, permiss
});
};
SqlTiddlerDatabase.prototype.getUserRoles = function(userId) {
const query = `
SELECT r.role_id, r.role_name
FROM user_roles ur
JOIN roles r ON ur.role_id = r.role_id
WHERE ur.user_id = $userId
`;
return this.engine.runStatementGetAll(query, { $userId: userId });
};
exports.SqlTiddlerDatabase = SqlTiddlerDatabase;
})();

View File

@ -0,0 +1,40 @@
title: $:/plugins/tiddlywiki/multiwikiserver/templates/add-user-modal
\define add-user-actions()
<$action-sendmessage $message="tm-server-request"
method="POST"
url="/admin/users"
headers="Content-Type: application/json"
body={{{ [{"username": "$(newUsername)$", "email": "$(newEmail)$", "password": "$(newPassword)$"}jsonify[]] }}}
redirectAfterSuccess="/admin/users"/>
<$action-deletetiddler $tiddler="$:/temp/newUsername"/>
<$action-deletetiddler $tiddler="$:/temp/newEmail"/>
<$action-deletetiddler $tiddler="$:/temp/newPassword"/>
\end
<h1>Add New User</h1>
<div>
<label>Username:
<$edit-text tiddler="$:/temp/newUsername" tag="input" default=""/>
</label>
</div>
<div>
<label>Email:
<$edit-text tiddler="$:/temp/newEmail" tag="input" default=""/>
</label>
</div>
<div>
<label>Password:
<$edit-text tiddler="$:/temp/newPassword" tag="input" type="password" default=""/>
</label>
</div>
<$button class="tc-btn-big-green">
Add User
<<add-user-actions>>
<$action-sendmessage $message="tm-close-tiddler"/>
</$button>
<$button class="tc-btn-invisible" message="tm-close-tiddler">
Cancel
</$button>

View File

@ -0,0 +1,27 @@
title: $:/plugins/tiddlywiki/multiwikiserver/templates/edit-role-modal
\define save-role-actions()
<<edit-role-actions {{!!role-id}}>>
\end
<h1>Edit Role</h1>
<div>
<label>Role Name:
<$edit-text tiddler="$:/temp/editRoleName" tag="input" default=""/>
</label>
</div>
<div>
<label>Role Description:
<$edit-text tiddler="$:/temp/editRoleDescription" tag="input" default=""/>
</label>
</div>
<$button class="tc-btn-invisible">
Save Changes
<<save-role-actions>>
<$action-sendmessage $message="tm-close-tiddler"/>
</$button>
<$button class="tc-btn-invisible" message="tm-close-tiddler">
Cancel
</$button>

View File

@ -0,0 +1,32 @@
title: $:/plugins/tiddlywiki/multiwikiserver/templates/edit-user-modal
\define edit-user-actions()
<$action-sendmessage $message="tm-server-request"
method="PUT"
url={{{ [[$:/admin/users/]addsuffix{!!user-id}] }}}
headers="Content-Type: application/json"
body={{{ [{"username": "$(editUsername)$", "email": "$(editEmail)$"}jsonify[]] }}}
redirectAfterSuccess="/admin/users"/>
\end
<h1>Edit User</h1>
<div>
<label>Username:
<$edit-text tiddler="$:/temp/editUsername" tag="input" default=""/>
</label>
</div>
<div>
<label>Email:
<$edit-text tiddler="$:/temp/editEmail" tag="input" default=""/>
</label>
</div>
<$button class="tc-btn-big-green">
Save Changes
<<edit-user-actions>>
<$action-sendmessage $message="tm-close-tiddler"/>
</$button>
<$button class="tc-btn-invisible" message="tm-close-tiddler">
Cancel
</$button>

View File

@ -21,26 +21,11 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-index
</$genesis>
\end
<div class="mws-header">
<h1>Wikis Available Here</h1>
<div class="mws-user-info">
<span>Hello, <$text text=<<username>>/></span>
<$list filter="[<user-is-admin>match[yes]]">
<div class="mws-admin-dropdown">
<button class="mws-admin-dropbtn">⚙️</button>
<div class="mws-admin-dropdown-content">
<a href="/admin/acl">Manage ACL</a>
<a href="/admin/users">Manage Users</a>
<a href="/admin/permissions">Manage Permissions</a>
<a href="/admin/roles">Manage Roles</a>
</div>
</div>
</$list>
<form action="/logout" method="post" class="mws-logout-form">
<input type="submit" value="Logout" class="mws-logout-button"/>
</form>
</div>
</div>
<$tiddler tiddler="$:/plugins/tiddlywiki/multiwikiserver/templates/mws-header">
<$set name="page-title" value="Wikis Available Here">
<$transclude/>
</$set>
</$tiddler>
<ul class="mws-vertical-list">
<$list filter="[<recipe-list>jsonindexes[]] :sort[<currentTiddler>jsonget[recipe_name]]" variable="recipe-index">
@ -88,7 +73,6 @@ title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-index
</li>
</$list>
</ul>
<form action="/recipes" method="post" class="mws-form">
<div class="mws-form-heading">
Create a new recipe or modify and existing one

View File

@ -1,89 +1,113 @@
<!--
title: $:/plugins/tiddlywiki/multiwikiserver/templates/get-users
-->
\define lingo-base() $:/language/ControlPanel/Tools/
\define edit-user-actions(user-id)
<$action-sendmessage $message="tm-modal" $param="$:/plugins/tiddlywiki/multiwikiserver/templates/edit-user-modal" user-id=<<user-id>>/>
\end
<h1>User Management</h1>
\define delete-user-actions(user-id)
<$action-sendmessage $message="tm-server-request"
method="DELETE"
url={{{ [[$:/admin/users/]addsuffix<user-id>] }}}
redirectAfterSuccess="/admin/users"/>
\end
<!-- Display raw user-list for debugging -->
<h2>Debug Info</h2>
<p><strong>Raw user list JSON:</strong> <$text text=<<user-list>>/></p>
<$tiddler tiddler="$:/plugins/tiddlywiki/multiwikiserver/templates/mws-header">
<$set name="page-title" value="User Management">
<$transclude/>
</$set>
</$tiddler>
<!-- Attempt to parse the user-list JSON -->
<$set name="userList" value=<<user-list>>>
<!-- Display a message if the userList is empty -->
<$list filter="[<userList>!is[blank]]" emptyMessage="The user list is empty or not provided.">
<p><strong>User list found:</strong> <$text text=<<userList>>/></p>
<!-- Attempt to parse the user list -->
<$set name="parsedUserList" value={{{ [<userList>jsonparse[]] }}}/>
<!-- Check if parsedUserList has any entries -->
<$list filter="[<parsedUserList>count[]compare:number:gt[0]]" emptyMessage="No users found or failed to parse user data">
<!-- Display parsed user list for debugging -->
<p><strong>Parsed User List (as JSON):</strong> <$text text={{{ [<parsedUserList>jsonstringify[]] }}}/></p>
<!-- Display total user count -->
<p><strong>Total users:</strong> <$text text={{{ [<parsedUserList>count[]] }}}/></p>
<!-- Render the user table if parsing was successful -->
<table class="tc-view-field-table">
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Created At</th>
<th>Last Login</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<$list filter="[<userList>jsonindexes[]]" variable="userIndex">
<$let currentUser={{{ [<userList>jsonget<userIndex>] }}}>
<tr>
<td><$text text={{{ [<currentUser>jsonget[username]] }}}/></td>
<td><$text text={{{ [<currentUser>jsonget[email]] }}}/></td>
<td><$text text={{{ [<currentUser>jsonget[created_at]] }}}/></td>
<td><$text text={{{ [<currentUser>jsonget[last_login]] }}}/></td>
<td>
<$button message="tm-server-command" param="edit-user" user_id={{{ [<currentUser>jsonget[user_id]] }}} class="tc-btn-invisible">
{{$:/core/images/edit-button}} Edit
</$button>
<$button message="tm-server-command" param="delete-user" user_id={{{ [<currentUser>jsonget[user_id]] }}} class="tc-btn-invisible">
{{$:/core/images/delete-button}} Delete
</$button>
</td>
</tr>
</$let>
</$list>
</tbody>
</table>
<div class="users-container">
<div class="users-list">
<$list filter="[<user-list>jsonindexes[]]" variable="user-index">
<$let currentUser={{{ [<user-list>jsonextract<user-index>] }}}>
<$set name="user-id" value={{{ [<currentUser>jsonget[user_id]] }}}>
<a href={{{ [[/admin/users/]addsuffix<user-id>] }}} class="user-item">
<div class="user-info">
<span class="user-name">
<$text text={{{ [<currentUser>jsonget[username]] }}}/>
</span>
<span class="user-email">
<$text text={{{ [<currentUser>jsonget[email]] }}}/>
</span>
</div>
<div class="user-details">
<span class="user-created">
Created: <$text text={{{ [<currentUser>jsonget[created_at]] }}}/>
</span>
<span class="user-last-login">
Last Login: <$text text={{{ [<currentUser>jsonget[last_login]] }}}/>
</span>
</div>
</a>
</$set>
</$let>
</$list>
</$list>
</$set>
<$button message="tm-modal" param="$:/plugins/tiddlywiki/multiwikiserver/templates/add-user-modal" class="tc-btn-big-green">
Add New User
</$button>
</div>
<div class="add-user-form">
<$button class="tc-btn-big-green">
Add New User
<$action-sendmessage $message="tm-modal" $param="$:/plugins/tiddlywiki/multiwikiserver/templates/add-user-modal"/>
</$button>
</div>
</div>
<style>
.tc-view-field-table {
width: 100%;
border-collapse: collapse;
}
.tc-view-field-table th, .tc-view-field-table td {
border: 1px solid <<colour table-border>>;
padding: 0.5em;
}
.tc-view-field-table th {
background-color: <<colour table-header-background>>;
font-weight: bold;
}
.tc-view-field-table tr:nth-child(even) {
background-color: <<colour table-header-background>>;
}
.users-container {
max-width: 800px;
margin: 2rem auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 2rem;
}
.user-item {
display: block;
width: 100%;
text-align: left;
background: none;
border: none;
border-bottom: 1px solid #eee;
padding: 1rem 0;
cursor: pointer;
transition: background-color 0.3s ease;
text-decoration: none;
}
.user-item:hover {
background-color: #f5f5f5;
text-decoration: none;
}
.user-info {
display: flex;
justify-content: space-between;
align-items: center;
}
.user-name {
font-weight: bold;
}
.user-email {
color: #666;
}
.user-details {
font-size: 0.9em;
color: #888;
margin-top: 0.5rem;
}
.add-user-form {
margin-top: 2rem;
text-align: right;
}
.tc-btn-big-green {
background-color: #4CAF50;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
}
.tc-btn-big-green:hover {
background-color: #45a049;
}
</style>

View File

@ -0,0 +1,123 @@
title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-roles
\define add-role-actions()
<$action-sendmessage $message="tm-server-request"
method="POST"
url="/admin/roles"
headers="Content-Type: application/json"
body={{{ [{"name": "$(newRoleName)$", "description": "$(newRoleDescription)$"}jsonify[]] }}}
redirectAfterSuccess="/admin/roles"/>
<$action-setfield $tiddler="$:/temp/newRoleName" text=""/>
<$action-setfield $tiddler="$:/temp/newRoleDescription" text=""/>
\end
\define edit-role-actions(role-id)
<$action-sendmessage $message="tm-server-request"
method="PUT"
url={{{ [[$:/admin/roles/]addsuffix<role-id>] }}}
headers="Content-Type: application/json"
body={{{ [{"name": "$(newRoleName)$", "description": "$(newRoleDescription)$"}jsonify[]] }}}
redirectAfterSuccess="/admin/roles"/>
\end
\define delete-role-actions(role-id)
<$action-sendmessage $message="tm-server-request"
method="DELETE"
url={{{ [[$:/admin/roles/]addsuffix<role-id>] }}}
redirectAfterSuccess="/admin/roles"/>
\end
<$tiddler tiddler="$:/plugins/tiddlywiki/multiwikiserver/templates/mws-header">
<$set name="page-title" value="Manage Roles">
<$transclude/>
</$set>
</$tiddler>
<div class="roles-container">
<div class="roles-list">
<$list filter="[<roles-list>jsonindexes[]]" variable="role-index">
<$let role={{{ [<roles-list>jsonextract<role-index>] }}}>
<div class="role-item">
<span class="role-name">
<$text text={{{ [<role>jsonget[role_name]] }}}/>
</span>
<span class="role-description">
<$text text={{{ [<role>jsonget[description]] }}}/>
</span>
<div class="role-actions">
<$button class="tc-btn-invisible">
Edit
<$action-setfield $tiddler="$:/temp/editRoleName" text={{{ [<role>jsonget[role_name]] }}}/>
<$action-setfield $tiddler="$:/temp/editRoleDescription" text={{{ [<role>jsonget[description]] }}}/>
<$action-sendmessage $message="tm-modal" $param="$:/plugins/tiddlywiki/multiwikiserver/templates/edit-role-modal" role-id={{{ [<role>jsonget[role_id]] }}}/>
</$button>
<$button class="tc-btn-invisible">
Delete
<$action-confirm $message="Are you sure you want to delete this role?">
<<delete-role-actions {{{ [<role>jsonget[role_id]] }}}>>
</$action-confirm>
</$button>
</div>
</div>
</$let>
</$list>
</div>
<div class="add-role-form">
<$edit-text tiddler="$:/temp/newRoleName" tag="input" default="" placeholder="Role Name"/>
<$edit-text tiddler="$:/temp/newRoleDescription" tag="input" default="" placeholder="Role Description"/>
<$button class="tc-btn-invisible">
Add Role
<<add-role-actions>>
</$button>
</div>
</div>
<style>
.roles-container {
max-width: 800px;
margin: 2rem auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
padding: 2rem;
}
.role-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 0;
border-bottom: 1px solid #eee;
}
.role-name {
font-weight: bold;
}
.role-description {
color: #666;
margin-left: 1rem;
}
.role-actions button {
margin-left: 0.5rem;
padding: 0.5rem 1rem;
background: none;
border: none;
cursor: pointer;
}
.add-role-form {
margin-top: 2rem;
display: flex;
gap: 1rem;
}
.add-role-form input {
flex-grow: 1;
padding: 0.5rem;
border: 1px solid #ccc;
}
.add-role-form button {
padding: 0.5rem 1rem;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,198 @@
title: $:/plugins/tiddlywiki/multiwikiserver/templates/manage-user
<$tiddler tiddler="$:/plugins/tiddlywiki/multiwikiserver/templates/mws-header">
<$set name="page-title" value="User Profile">
<$transclude/>
</$set>
</$tiddler>
<div class="user-profile-container">
<div class="user-profile-header">
<div class="user-profile-avatar">
<$text text={{{ [<user>jsonget[username]substr[0,1]uppercase[]] }}}/>
</div>
<h1 class="user-profile-name"><$text text={{{ [<user>jsonget[username]] }}}/></h1>
<p class="user-profile-email"><$text text={{{ [<user>jsonget[email]] }}}/></p>
</div>
<div class="user-profile-details">
<div class="user-profile-item">
<span class="user-profile-label">User ID:</span>
<span class="user-profile-value"><$text text={{{ [<user>jsonget[user_id]] }}}/></span>
</div>
<div class="user-profile-item">
<span class="user-profile-label">Created At:</span>
<span class="user-profile-value"><$text text={{{ [<user>jsonget[created_at]split[T]first[]] }}}/></span>
</div>
<div class="user-profile-item">
<span class="user-profile-label">Last Login:</span>
<span class="user-profile-value"><$text text={{{ [<user>jsonget[last_login]split[T]first[]] }}}/></span>
</div>
<div class="user-profile-roles">
<h2>User Roles</h2>
<ul>
<$list filter="[<user-roles>jsonindexes[]]" variable="role-index">
<li>
<$text text={{{ [<user-roles>jsonextract<role-index>jsonget[role_name]] }}}/>
</li>
</$list>
</ul>
</div>
<div class="user-actions">
<$button class="tc-btn-invisible">
{{$:/core/images/edit-button}} Edit
<<edit-user-actions {{{ [<currentUser>jsonget[user_id]] }}}>>
</$button>
<$button class="tc-btn-invisible">
{{$:/core/images/delete-button}} Delete
<$action-confirm $message="Are you sure you want to delete this user?">
<<delete-user-actions {{{ [<currentUser>jsonget[user_id]] }}}>>
</$action-confirm>
</$button>
</div>
<hr />
<div class="user-profile-roles-management">
<h2>Manage User Roles</h2>
<select id="roleSelect">
<option value="">Select a role to add</option>
<$list filter="[<all-roles>jsonindexes[]]" variable="role-index">
<$let role={{{ [<all-roles>jsonextract<role-index>] }}}>
<option value={{{ [<role>jsonget[role_id]] }}}>
<$text text={{{ [<role>jsonget[role_name]] }}}/>
</option>
</$let>
</$list>
</select>
<button onclick="addRoleToUser()">Add Role</button>
</div>
</div>
</div>
<script>
function addRoleToUser() {
const roleId = document.getElementById('roleSelect').value;
if (roleId) {
fetch('/admin/users/{{{ [<user>jsonget[user_id]] }}}/roles', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ role_id: roleId })
}).then(() => location.reload());
}
}
function removeRoleFromUser(roleId) {
if (confirm("Are you sure you want to remove this role from the user?")) {
fetch('/admin/users/{{{ [<user>jsonget[user_id]] }}}/roles/' + roleId, {
method: 'DELETE'
}).then(() => location.reload());
}
}
</script>
<style>
.user-profile-container {
max-width: 600px;
margin: 2rem auto;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.user-profile-header {
background: #3498db;
color: #fff;
padding: 2rem;
text-align: center;
}
.user-profile-avatar {
width: 120px;
height: 120px;
border-radius: 50%;
margin: 0 auto 1rem;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
* {
color: #3498db;
}
}
.user-profile-name {
font-size: 1.5rem;
margin: 0;
}
.user-profile-email {
font-size: 1rem;
opacity: 0.8;
margin: 0.5rem 0 0;
}
.user-profile-details {
padding: 2rem;
}
.user-profile-item {
margin-bottom: 1rem;
}
.user-profile-label {
font-weight: bold;
color: #555;
}
.user-profile-value {
color: #333;
}
.user-profile-roles {
margin-top: 2rem;
}
.user-profile-roles h2 {
font-size: 1.2rem;
color: #3498db;
margin-bottom: 1rem;
}
.user-profile-roles ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.user-profile-roles li {
background: #f1f1f1;
padding: 0.5rem 1rem;
border-radius: 20px;
display: inline-block;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.user-profile-roles-management {
margin-top: 2rem;
}
.user-profile-roles-management select {
margin-right: 1rem;
}
.user-actions {
margin-top: 0.5rem;
}
.user-actions button {
background: none;
border: none;
cursor: pointer;
padding: 0.25rem 0.5rem;
margin-right: 0.5rem;
}
</style>

View File

@ -0,0 +1,96 @@
title: $:/plugins/tiddlywiki/multiwikiserver/templates/mws-header
<div class="mws-header">
<h1><$text text=<<page-title>>/></h1>
<div class="mws-user-info">
<span>Hello, <$text text=<<username>>/></span>
<$list filter="[<user-is-admin>match[yes]]">
<div class="mws-admin-dropdown">
<button class="mws-admin-dropbtn">⚙️</button>
<div class="mws-admin-dropdown-content">
<a href="/admin/acl">Manage ACL</a>
<a href="/admin/users">Manage Users</a>
<a href="/admin/permissions">Manage Permissions</a>
<a href="/admin/roles">Manage Roles</a>
</div>
</div>
</$list>
<form action="/logout" method="post" class="mws-logout-form">
<input type="submit" value="Logout" class="mws-logout-button"/>
</form>
</div>
</div>
<style>
.mws-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background-color: #f0f0f0;
margin-bottom: 20px;
}
.mws-user-info {
display: flex;
align-items: center;
}
.mws-logout-form {
margin-left: 10px;
}
.mws-logout-button {
padding: 5px 10px;
background-color: #f44336;
color: white;
border: none;
cursor: pointer;
}
.mws-logout-button:hover {
background-color: #d32f2f;
}
.mws-admin-dropdown {
position: relative;
display: inline-block;
margin-left: 10px;
}
.mws-admin-dropbtn {
color: white;
padding: 5px;
font-size: 16px;
border: none;
cursor: pointer;
}
.mws-admin-dropbtn:hover, .mws-admin-dropbtn:focus {
cursor: pointer;
opacity: 0.8;
}
.mws-admin-dropdown-content {
display: none;
position: absolute;
background-color: #f1f1f1;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
right: 0;
}
.mws-admin-dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
.mws-admin-dropdown-content a:hover {background-color: #ddd;}
.mws-admin-dropdown:hover .mws-admin-dropdown-content {display: block;}
.mws-admin-dropdown:hover {background-color: #2980B9;}
</style>