Compare commits

...

17 Commits

Author SHA1 Message Date
Joel Falknau
152ebf86f9
Version Bump 4.9.0 2025-08-14 12:32:46 +10:00
Ariel Rin
68069d1043 Merge branch 'retract-group-application-button' into 'master'
[ADD] Retract group application button

See merge request allianceauth/allianceauth!1748
2025-08-14 02:28:42 +00:00
Ariel Rin
9ff926ae4d Merge branch '1745alt' into 'master'
Bump SQL and Redis for new installs

See merge request allianceauth/allianceauth!1751
2025-08-14 02:27:14 +00:00
Ariel Rin
b28cbdad31 Bump SQL and Redis for new installs 2025-08-14 02:27:14 +00:00
Ariel Rin
3c1bae463e Merge branch 'aa-framework-js-functions' into 'master'
[ADD] JS functions to the AA framework

See merge request allianceauth/allianceauth!1747
2025-08-14 02:09:10 +00:00
Ariel Rin
91fbdb9ec1 Merge branch 'fix-debug-warning-widget' into 'master'
[FIX] Debug warning dashboard widget

See merge request allianceauth/allianceauth!1746
2025-08-14 02:08:48 +00:00
Ariel Rin
c1abc56ebc Merge branch 'svg-sprite' into 'master'
[ADD] SVG sprite to the Alliance Auth framework

See merge request allianceauth/allianceauth!1750
2025-08-14 00:57:20 +00:00
Peter Pfeufer
f1582165bc
[ADD] SVG sprite to the Alliance Auth framework 2025-08-13 11:46:36 +02:00
Peter Pfeufer
0f155369a1
[CHANGE] Move it to the top 2025-08-11 09:06:27 +02:00
Peter Pfeufer
f81c1d1b31
[ADD] Retract group application button
See #1401
2025-08-09 22:40:07 +02:00
Peter Pfeufer
0360184c2d
[ADD] QuerySelector function to prevent forms from double submitting
This is to prevent forms from submitting multiple times when users double-click or even more …
2025-08-09 16:37:20 +02:00
Peter Pfeufer
099a39a2a2
[ADD] objectDeepMerge function
Recursively merges properties from source objects into a target object. If a property at the current level is an object,
and both target and source have it, the property is merged. Otherwise, the source property overwrites the target property.

This function does not modify the source objects and prevents prototype pollution by not allowing `__proto__`, `constructor`,
and `prototype` property names.
2025-08-09 15:54:39 +02:00
Peter Pfeufer
c1cd7ca64f
[CHANGE] To async 2025-08-09 10:36:22 +02:00
Peter Pfeufer
4cc108ab7f
[MISC] Formatting 2025-08-08 19:39:42 +02:00
Peter Pfeufer
0028310aa5
[CHANGE] Switch to framework fetch functions 2025-08-08 19:29:55 +02:00
Peter Pfeufer
18e9453fed
[ADD] JS functions to the AA framework 2025-08-08 19:08:55 +02:00
Peter Pfeufer
db74ddfdf5
[FIX] Debug warning dashboard widget
Bringing and layout the markup in line with the rest of the dashboard widgets.
2025-08-07 13:47:58 +02:00
22 changed files with 634 additions and 236 deletions

View File

@ -5,7 +5,7 @@ manage online service access.
# This will make sure the app is always imported when # This will make sure the app is always imported when
# Django starts so that shared_task will use this app. # Django starts so that shared_task will use this app.
__version__ = '4.8.0' __version__ = '4.9.0'
__title__ = 'Alliance Auth' __title__ = 'Alliance Auth'
__title_useragent__ = 'AllianceAuth' __title_useragent__ = 'AllianceAuth'
__url__ = 'https://gitlab.com/allianceauth/allianceauth' __url__ = 'https://gitlab.com/allianceauth/allianceauth'

View File

@ -15,6 +15,10 @@
ul#nav-right:has(li) + ul#nav-right-character-control > li:first-child { ul#nav-right:has(li) + ul#nav-right-character-control > li:first-child {
display: list-item !important; display: list-item !important;
} }
form.is-submitting button[type="submit"] {
cursor: not-allowed;
}
} }
@media all and (max-width: 991px) { @media all and (max-width: 991px) {

View File

@ -0,0 +1,300 @@
/**
* Functions and utilities for the Alliance Auth framework.
*/
/* jshint -W097 */
'use strict';
/**
* Checks if the given item is an array.
*
* @usage
* ```javascript
* if (isArray(someVariable)) {
* console.log('This is an array');
* } else {
* console.log('This is not an array');
* }
* ```
*
* @param {*} item - The item to check.
* @returns {boolean} True if the item is an array, false otherwise.
*/
const isArray = (item) => {
return Array.isArray(item);
};
/**
* Checks if the given item is a plain object, excluding arrays and dates.
*
* @usage
* ```javascript
* if (isObject(someVariable)) {
* console.log('This is a plain object');
* } else {
* console.log('This is not a plain object');
* }
* ```
*
* @param {*} item - The item to check.
* @returns {boolean} True if the item is a plain object, false otherwise.
*/
const isObject = (item) => {
return (
item && typeof item === 'object' && !isArray(item) && !(item instanceof Date)
);
};
/**
* Fetch data from an ajax URL
*
* Do not call this function directly, use `fetchGet` or `fetchPost` instead.
*
* @param {string} url The URL to fetch data from
* @param {string} method The HTTP method to use for the request (default: 'get')
* @param {string|null} csrfToken The CSRF token to include in the request headers (default: null)
* @param {string|null} payload The payload (JSON|Object) to send with the request (default: null)
* @param {boolean} responseIsJson Whether the response is expected to be JSON or not (default: true)
* @returns {Promise<string>} The fetched data
* @throws {Error} Throws an error when:
* - The method is not valid (only `get` and `post` are allowed).
* - The CSRF token is required but not provided for POST requests.
* - The payload is not an object when using POST method.
* - The response status is not OK (HTTP 200-299).
* - There is a network error or if the response cannot be parsed as JSON.
*/
const _fetchAjaxData = async ({
url,
method = 'get',
csrfToken = null,
payload = null,
responseIsJson = true
}) => {
const normalizedMethod = method.toLowerCase();
// Validate the method
const validMethods = ['get', 'post'];
if (!validMethods.includes(normalizedMethod)) {
throw new Error(`Invalid method: ${method}. Valid methods are: get, post`);
}
const headers = {};
// Set headers based on response type
if (responseIsJson) {
headers['Accept'] = 'application/json'; // jshint ignore:line
headers['Content-Type'] = 'application/json';
}
let requestUrl = url;
let body = null;
if (normalizedMethod === 'post') {
if (!csrfToken) {
throw new Error('CSRF token is required for POST requests');
}
headers['X-CSRFToken'] = csrfToken;
if (payload !== null && !isObject(payload)) {
throw new Error('Payload must be an object when using POST method');
}
body = payload ? JSON.stringify(payload) : null;
} else if (normalizedMethod === 'get' && payload) {
const queryParams = new URLSearchParams(payload).toString(); // jshint ignore:line
requestUrl += (url.includes('?') ? '&' : '?') + queryParams;
}
/**
* Throws an error with a formatted message.
*
* @param {Response} response The error object containing the message to throw.
*/
const throwHTTPStatusError = (response) => {
throw new Error(`Error: ${response.status} - ${response.statusText}`);
};
try {
const response = await fetch(requestUrl, {
method: method.toUpperCase(),
headers: headers,
body: body
});
/**
* Throws an error if the response status is not OK (HTTP 200-299).
* This is used to handle HTTP errors gracefully.
*/
if (!response.ok) {
throwHTTPStatusError(response);
}
return responseIsJson ? await response.json() : await response.text();
} catch (error) {
// Log the error message to the console
console.log(`Error: ${error.message}`);
throw error;
}
};
/**
* Fetch data from an ajax URL using the GET method.
* This function is a wrapper around _fetchAjaxData to simplify GET requests.
*
* @usage
* ```javascript
* fetchGet({
* url: url,
* responseIsJson: false
* }).then((data) => {
* // Process the fetched data
* }).catch((error) => {
* console.error(`Error: ${error.message}`);
*
* // Handle the error appropriately
* });
* ```
*
* @param {string} url The URL to fetch data from
* @param {string|null} payload The payload (JSON) to send with the request (default: null)
* @param {boolean} responseIsJson Whether the response is expected to be JSON or not (default: true)
* @return {Promise<string>} The fetched data
*/
const fetchGet = async ({
url,
payload = null,
responseIsJson = true
}) => {
return await _fetchAjaxData({
url: url,
method: 'get',
payload: payload,
responseIsJson: responseIsJson
});
};
/**
* Fetch data from an ajax URL using the POST method.
* This function is a wrapper around _fetchAjaxData to simplify POST requests.
* It requires a CSRF token for security purposes.
*
* @usage
* ```javascript
* fetchPost({
* url: url,
* csrfToken: csrfToken,
* payload: {
* key: 'value',
* anotherKey: 'anotherValue'
* },
* responseIsJson: true
* }).then((data) => {
* // Process the fetched data
* }).catch((error) => {
* console.error(`Error: ${error.message}`);
*
* // Handle the error appropriately
* });
* ```
*
* @param {string} url The URL to fetch data from
* @param {string|null} csrfToken The CSRF token to include in the request headers (default: null)
* @param {string|null} payload The payload (JSON) to send with the request (default: null)
* @param {boolean} responseIsJson Whether the response is expected to be JSON or not (default: true)
* @return {Promise<string>} The fetched data
*/
const fetchPost = async ({
url,
csrfToken,
payload = null,
responseIsJson = true
}) => {
return await _fetchAjaxData({
url: url,
method: 'post',
csrfToken: csrfToken,
payload: payload,
responseIsJson: responseIsJson
});
};
/**
* Recursively merges properties from source objects into a target object. If a property at the current level is an object,
* and both target and source have it, the property is merged. Otherwise, the source property overwrites the target property.
* This function does not modify the source objects and prevents prototype pollution by not allowing __proto__, constructor,
* and prototype property names.
*
* @usage
* ```javascript
* const target = {a: 1, b: {c: 2}};
* const source1 = {b: {d: 3}, e: 4 };
* const source2 = {a: 5, b: {c: 6}};
*
* const merged = objectDeepMerge(target, source1, source2);
*
* console.log(merged); // {a: 5, b: {c: 6, d: 3}, e: 4}
* ```
*
* @param {Object} target The target object to merge properties into.
* @param {...Object} sources One or more source objects from which to merge properties.
* @returns {Object} The target object after merging properties from sources.
*/
function objectDeepMerge (target, ...sources) {
if (!sources.length) {
return target;
}
// Iterate through each source object without modifying the `sources` array.
sources.forEach(source => {
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
continue; // Skip potentially dangerous keys to prevent prototype pollution.
}
if (!target[key] || !isObject(target[key])) {
target[key] = {};
}
objectDeepMerge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
}
});
return target;
}
/**
* When the document is ready
*/
$(document).ready(() => {
/**
* Prevent double form submits by adding a class to the form
* when it is submitted.
*
* This class can be used to show a visual indicator that the form is being
* submitted, such as a spinner.
*
* This is useful to prevent users from double-clicking the submit button
* and submitting the form multiple times.
*/
document.querySelectorAll('form').forEach((form) => {
form.addEventListener('submit', (e) => {
// Prevent if already submitting
if (form.classList.contains('is-submitting')) {
e.preventDefault();
}
// Add class to hook our visual indicator on
form.classList.add('is-submitting');
});
});
});

View File

@ -0,0 +1,23 @@
<svg id="alliance-auth-svg-sprite" width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- Alliance Auth Logo -->
<symbol id="aa-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<g transform="translate(41.953499,36.607802)">
<path style="display:inline;fill:#e14852;stroke-width:0.32" d="M 131.07236,159.67687 C 109.26615,147.02458 91.302022,136.55002 91.152067,136.40007 l -0.272649,-0.27265 23.786292,-13.82371 c 13.08247,-7.60304 23.9186,-13.82025 24.08029,-13.81602 l 0.294,0.008 15.93273,36.83413 c 8.763,20.25877 15.891,36.95054 15.84,37.09283 l -0.0927,0.25869 z" />
<path style="display:inline;fill:#436195;stroke-width:0.32" d="m 1.28,182.46369 c 0,-0.16969 17.354495,-40.46543 38.565546,-89.546103 L 78.411088,3.68 C 79.919052,1.4903841 82.294641,0.02199886 86.08,0.01224344 89.865359,0.00248802 92.288,1.4677954 93.674477,3.5158445 l 21.668143,50.1206965 21.66814,50.120699 -0.26538,0.23285 C 136.59942,104.11816 106.528,121.61441 69.92,142.87065 33.312,164.12688 2.892,181.80046 2.32,182.14527 l -1.04,0.62693 z" />
</g>
</symbol>
<!-- Loading Spinner -->
<symbol id="aa-loading-spinner" xmlns="http://www.w3.org/2000/svg" stroke="currentColor" viewBox="0 0 24 24">
<g>
<circle cx="12" cy="12" r="10" fill="none" stroke-width="1" stroke-linecap="round">
<animate attributeName="stroke-dasharray" dur="1.5s" calcMode="spline" values="0 150;42 150;42 150;42 150" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite" />
<animate attributeName="stroke-dashoffset" dur="1.5s" calcMode="spline" values="0;-16;-59;-59" keyTimes="0;0.475;0.95;1" keySplines="0.42,0,0.58,1;0.42,0,0.58,1;0.42,0,0.58,1" repeatCount="indefinite" />
</circle>
<animateTransform attributeName="transform" type="rotate" dur="2s" values="0 12 12;360 12 12" repeatCount="indefinite" />
</g>
</symbol>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -70,8 +70,8 @@
{% translate "Leave" %} {% translate "Leave" %}
</a> </a>
{% else %} {% else %}
<button type="button" class="btn btn-primary" disabled> <button type="button" class="btn btn-secondary cursor-help me-1" data-bs-tooltip="aa-tooltip" title="{% translate 'Request pending' %}">
{% translate "Pending" %} <i class="fa-regular fa-hourglass-half"></i>
</button> </button>
{% endif %} {% endif %}
{% elif not g.request %} {% elif not g.request %}
@ -85,9 +85,13 @@
</a> </a>
{% endif %} {% endif %}
{% else %} {% else %}
<button type="button" class="btn btn-primary" disabled> <button type="button" class="btn btn-secondary cursor-help me-1" data-bs-tooltip="aa-tooltip" title="{% translate 'Request pending' %}">
{% translate "Pending" %} <i class="fa-regular fa-hourglass-half"></i>
</button> </button>
<a href="{% url 'groupmanagement:request_retract' g.group.id %}" class="btn btn-danger">
{% translate "Retract" %}
</a>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>

View File

@ -10,6 +10,11 @@ urlpatterns = [
path( path(
"group/request/leave/<int:group_id>/", views.group_request_leave, name="request_leave" "group/request/leave/<int:group_id>/", views.group_request_leave, name="request_leave"
), ),
path(
"group/request/retract/<int:group_id>/",
views.group_request_retract,
name="request_retract"
),
# group management # group management
path("groupmanagement/requests/", views.group_management, name="management"), path("groupmanagement/requests/", views.group_management, name="management"),
path("groupmanagement/membership/", views.group_membership, name="membership"), path("groupmanagement/membership/", views.group_membership, name="membership"),

View File

@ -420,3 +420,42 @@ def group_request_leave(request, group_id):
grouprequest.notify_leaders() grouprequest.notify_leaders()
messages.success(request, _('Applied to leave group %(group)s.') % {"group": group}) messages.success(request, _('Applied to leave group %(group)s.') % {"group": group})
return redirect("groupmanagement:groups") return redirect("groupmanagement:groups")
@login_required
def group_request_retract(request, group_id):
logger.debug(
f"group_request_retract called by user {request.user} for group id {group_id}"
)
group = get_object_or_404(Group, id=group_id)
if not GroupManager.check_internal_group(group):
logger.warning(
f"User {request.user} attempted to retract group request for "
f"group id {group_id} but it is not a joinable group"
)
messages.warning(
request,
_("You cannot retract that request"),
)
return redirect('groupmanagement:groups')
try:
group_request = GroupRequest.objects.get(
user=request.user, group=group, leave_request=False
)
group_request.delete()
logger.info(f"Deleted group request for user {request.user} to group {group}")
messages.success(
request, _('Retracted application to group %(group)s.') % {"group": group}
)
except GroupRequest.DoesNotExist:
logger.info(
f"{request.user} attempted to retract group request for "
f"group id {group_id} but has no open request"
)
messages.warning(
request, _("You have no open request for that group.")
)
return redirect("groupmanagement:groups")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
version="1.1"
id="svg122"
width="256"
height="256"
viewBox="0 0 255.99999 256"
sodipodi:docname="allianceauth (2).svg"
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs126" />
<sodipodi:namedview
id="namedview124"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="3.8506576"
inkscape:cx="30.514269"
inkscape:cy="102.57988"
inkscape:window-width="2560"
inkscape:window-height="1369"
inkscape:window-x="1912"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g128" />
<g
inkscape:groupmode="layer"
inkscape:label="Image"
id="g128">
<g
id="g2338"
transform="translate(41.953499,36.607802)">
<path
style="display:inline;fill:#e14852;stroke-width:0.32"
d="M 131.07236,159.67687 C 109.26615,147.02458 91.302022,136.55002 91.152067,136.40007 l -0.272649,-0.27265 23.786292,-13.82371 c 13.08247,-7.60304 23.9186,-13.82025 24.08029,-13.81602 l 0.294,0.008 15.93273,36.83413 c 8.763,20.25877 15.891,36.95054 15.84,37.09283 l -0.0927,0.25869 z"
id="path2342"
sodipodi:nodetypes="cscsccsscc" />
<path
style="display:inline;fill:#436195;stroke-width:0.32"
d="m 1.28,182.46369 c 0,-0.16969 17.354495,-40.46543 38.565546,-89.546103 L 78.411088,3.68 C 79.919052,1.4903841 82.294641,0.02199886 86.08,0.01224344 89.865359,0.00248802 92.288,1.4677954 93.674477,3.5158445 l 21.668143,50.1206965 21.66814,50.120699 -0.26538,0.23285 C 136.59942,104.11816 106.528,121.61441 69.92,142.87065 33.312,164.12688 2.892,181.80046 2.32,182.14527 l -1.04,0.62693 z"
id="path2340"
sodipodi:nodetypes="ssczcccssscs" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -1,4 +1,4 @@
/* global notificationUpdateSettings */ /* global notificationUpdateSettings, fetchGet */
/** /**
* This script refreshed the notification icon in the top menu * This script refreshed the notification icon in the top menu
@ -19,22 +19,9 @@ $(() => {
* Update the notification icon in the top menu * Update the notification icon in the top menu
*/ */
const updateNotificationIcon = () => { const updateNotificationIcon = () => {
fetch(userNotificationCountViewUrl) fetchGet({url: userNotificationCountViewUrl})
.then((response) => { .then((data) => {
if (response.ok) { elementNotificationIcon.toggleClass('text-danger', data.unread_count > 0);
return response.json();
}
throw new Error('Something went wrong');
})
.then((responseJson) => {
const unreadCount = responseJson.unread_count;
if (unreadCount > 0) {
elementNotificationIcon.addClass('text-danger');
} else {
elementNotificationIcon.removeClass('text-danger');
}
}) })
.catch((error) => { .catch((error) => {
console.log(`Failed to load HTMl to render notifications item. Error: ${error.message}`); console.log(`Failed to load HTMl to render notifications item. Error: ${error.message}`);

View File

@ -8,30 +8,22 @@
</div> </div>
<script> <script>
const elemCard = document.getElementById("esi-alert"); const elemCard = document.getElementById('esi-alert');
const elemMessage = document.getElementById("esi-data"); const elemMessage = document.getElementById('esi-data');
const elemCode = document.getElementById("esi-code"); const elemCode = document.getElementById('esi-code');
fetch('{% url "authentication:esi_check" %}') fetchGet({url: '{% url "authentication:esi_check" %}'})
.then((response) => { .then((data) => {
if (response.ok) { console.log('ESI Check: ', JSON.stringify(data, null, 2));
return response.json();
}
throw new Error("Something went wrong");
})
.then((responseJson) => {
console.log("ESI Check: ", JSON.stringify(responseJson, null, 2));
const status = responseJson.status; if (data.status !== 200) {
if (status !== 200) { elemCode.textContent = data.status;
elemCode.textContent = status elemMessage.textContent = data.data.error;
elemMessage.textContent = responseJson.data.error;
new bootstrap.Collapse(elemCard, { new bootstrap.Collapse(elemCard, {toggle: true});
toggle: true
})
} }
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.error('Error fetching ESI check:', error);
}); });
</script> </script>

View File

@ -1,6 +1,23 @@
{% load i18n %} {% load i18n %}
{% load humanize %} {% load humanize %}
{% if debug %}
<div id="aa-dashboard-panel-debug" class="col-12 mb-3">
<div class="card text-bg-warning">
<div class="card-body">
{% translate "Debug mode" as widget_title %}
{% include "framework/dashboard/widget-title.html" with title=widget_title %}
<div>
<p class="text-center">
{% translate "Debug mode is currently turned on!<br>Make sure to turn it off as soon as you are finished testing." %}
</p>
</div>
</div>
</div>
</div>
{% endif %}
{% if notifications %} {% if notifications %}
<div id="aa-dashboard-panel-admin-notifications" class="col-12 mb-3"> <div id="aa-dashboard-panel-admin-notifications" class="col-12 mb-3">
<div class="card"> <div class="card">
@ -118,49 +135,25 @@
</div> </div>
</div> </div>
{% if debug %}
<div id="aa-dashboard-panel-debug" class="col-12 mb-3">
<div class="card text-bg-warning">
<div class="card-body">
<h5 class="card-title">{% translate "Debug mode" %}</h5>
<p class="card-text">
{% translate "Debug mode is currently turned on. Make sure to turn it off as soon as you are finished testing" %}
</p>
</div>
</div>
</div>
{% endif %}
<script> <script>
const elemRunning = document.getElementById("task-counts"); const elemRunning = document.getElementById('task-counts');
const elemQueued = document.getElementById("queued-tasks-count"); const elemQueued = document.getElementById('queued-tasks-count');
fetch('{% url "authentication:task_counts" %}') fetchGet({url: '{% url "authentication:task_counts" %}'})
.then((response) => { .then((data) => {
if (response.ok) { const running = data.tasks_running;
return response.json(); const queued = data.tasks_queued;
}
throw new Error("Something went wrong");
})
.then((responseJson) => {
const running = responseJson.tasks_running;
if (running == null) {
elemRunning.textContent = "N/A";
} else {
elemRunning.textContent = running.toLocaleString();
}
const queued = responseJson.tasks_queued; const updateTaskCount = (element, value) => {
if (queued == null) { element.textContent = value == null ? 'N/A' : value.toLocaleString();
elemQueued.textContent = "N/A"; };
} else {
elemQueued.textContent = queued.toLocaleString(); updateTaskCount(elemRunning, running);
} updateTaskCount(elemQueued, queued);
}) })
.catch((error) => { .catch((error) => {
console.log(error); console.error('Error fetching task queue:', error);
elemRunning.textContent = "ERROR";
elemQueued.textContent = "ERROR"; [elemRunning, elemQueued].forEach(elem => elem.textContent = 'ERROR');
}); });
</script> </script>

View File

@ -23,6 +23,9 @@
{% include 'bundles/fontawesome.html' %} {% include 'bundles/fontawesome.html' %}
{% include 'bundles/auth-framework-css.html' %} {% include 'bundles/auth-framework-css.html' %}
{% include 'bundles/jquery-js.html' %}
{% include 'bundles/auth-framework-js.html' %}
<style> <style>
@media all { @media all {
.nav-padding { .nav-padding {
@ -137,8 +140,6 @@
})(); })();
</script> </script>
{% include 'bundles/jquery-js.html' %}
{% theme_js %} {% theme_js %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
@ -159,5 +160,7 @@
{% block extra_script %} {% block extra_script %}
{% endblock extra_script %} {% endblock extra_script %}
</script> </script>
{% include "framework/svg/sprite.svg" %}
</body> </body>
</html> </html>

View File

@ -0,0 +1,3 @@
{% load sri %}
{% sri_static 'allianceauth/framework/js/auth-framework.js' %}

View File

@ -1,3 +1,3 @@
{% load static %} <svg class="svg-alliance-auth-logo" width="{{ logo_width|default:"128px" }}" height="{% if logo_height %}{{ logo_height }}{% else %}{{ logo_width|default:"128px" }}{% endif %}">
<use href="#aa-logo"></use>
<img src="{% static 'allianceauth/images/auth-logo.svg' %}" width="{{ logo_width|default:"128px" }}" height="{% if logo_height %}{{ logo_height }}{% else %}{{ logo_width|default:"128px" }}{% endif %}" alt="{{ SITE_NAME }}"> </svg>

Before

Width:  |  Height:  |  Size: 243 B

After

Width:  |  Height:  |  Size: 217 B

View File

@ -1,7 +1,7 @@
PROTOCOL=https:// PROTOCOL=https://
AUTH_SUBDOMAIN=%AUTH_SUBDOMAIN% AUTH_SUBDOMAIN=%AUTH_SUBDOMAIN%
DOMAIN=%DOMAIN% DOMAIN=%DOMAIN%
AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v4.8.0 AA_DOCKER_TAG=registry.gitlab.com/allianceauth/allianceauth/auth:v4.9.0
# Nginx Proxy Manager # Nginx Proxy Manager
PROXY_HTTP_PORT=80 PROXY_HTTP_PORT=80

View File

@ -1,5 +1,5 @@
FROM python:3.11-slim FROM python:3.11-slim
ARG AUTH_VERSION=v4.8.0 ARG AUTH_VERSION=v4.9.0
ARG AUTH_PACKAGE=allianceauth==${AUTH_VERSION} ARG AUTH_PACKAGE=allianceauth==${AUTH_VERSION}
ENV AUTH_USER=allianceauth ENV AUTH_USER=allianceauth
ENV AUTH_GROUP=allianceauth ENV AUTH_GROUP=allianceauth

View File

@ -44,8 +44,8 @@ x-allianceauth-health-check: &allianceauth-health-checks
services: services:
auth_mysql: auth_mysql:
image: mariadb:10.11 image: mariadb:11.8
command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --default-authentication-plugin=mysql_native_password] command: [mariadbd, --default-authentication-plugin=mysql_native_password]
volumes: volumes:
- ./mysql-data:/var/lib/mysql - ./mysql-data:/var/lib/mysql
- ./setup.sql:/docker-entrypoint-initdb.d/setup.sql - ./setup.sql:/docker-entrypoint-initdb.d/setup.sql
@ -77,7 +77,7 @@ services:
max-file: "5" max-file: "5"
redis: redis:
image: redis:7 image: redis:8
command: redis-server command: redis-server
restart: always restart: always
volumes: volumes:
@ -132,7 +132,7 @@ services:
replicas: 2 replicas: 2
grafana: grafana:
image: grafana/grafana-oss:9.5.2 image: grafana/grafana-oss:latest
restart: always restart: always
depends_on: depends_on:
- auth_mysql - auth_mysql

View File

@ -13,5 +13,7 @@ The Alliance Auth framework is split into several submodules, each of which is d
framework/api framework/api
framework/css framework/css
framework/js
framework/templates framework/templates
framework/svg-sprite
::: :::

View File

@ -0,0 +1,124 @@
# JavaScript Framework
This contains some simple JavaScript functions that are used throughout Alliance Auth,
so they can be used by community app developers as well.
The JS file is already loaded in the base template, so it is globally available.
## Functions
The following functions are available in the JavaScript framework:
### isArray()
Checks if the given value is an array.
Usage:
```javascript
/* global isArray */
if (isArray(someVariable)) {
console.log('This is an array');
} else {
console.log('This is not an array');
}
```
### isObject()
Checks if the given value is an object.
Usage:
```javascript
/* global isObject */
if (isObject(someVariable)) {
console.log('This is a plain object');
} else {
console.log('This is not a plain object');
}
```
### fetchGet()
Performs a GET request to the given URL and returns a Promise that resolves with the response data.
Usage:
```javascript
/* global fetchGet */
fetchGet({
url: url,
responseIsJson: false
}).then((data) => {
// Process the fetched data
}).catch((error) => {
console.error(`Error: ${error.message}`);
// Handle the error appropriately
});
```
#### fetchGet() Parameters
- `url`: The URL to fetch data from.
- `payload`: Optional data to send with the request. Can be an object or a string.
- `responseIsJson`: Optional boolean indicating if the response should be parsed as JSON (default is `true`).
### fetchPost()
Performs a POST request to the given URL with the provided data and returns a Promise that resolves with the response data.
Usage:
```javascript
/* global fetchPost */
fetchPost({
url: url,
csrfToken: csrfToken,
payload: {
key: 'value',
anotherKey: 'anotherValue'
},
responseIsJson: true
}).then((data) => {
// Process the fetched data
}).catch((error) => {
console.error(`Error: ${error.message}`);
// Handle the error appropriately
});
```
#### fetchPost() Parameters
- `url`: The URL to send the POST request to.
- `csrfToken`: The CSRF token to include in the request headers.
- `payload`: The data as JS object to send with the request.
- `responseIsJson`: Optional boolean indicating if the response should be parsed as JSON (default is `true`).
### objectDeepMerge()
Recursively merges properties from source objects into a target object. If a property at the current level is an object,
and both target and source have it, the property is merged. Otherwise, the source property overwrites the target property.
This function does not modify the source objects and prevents prototype pollution by not allowing `__proto__`, `constructor`,
and `prototype` property names.
Usage:
```javascript
/* global objectDeepMerge */
const target = {a: 1, b: {c: 2}};
const source1 = {b: {d: 3}, e: 4 };
const source2 = {a: 5, b: {c: 6}};
const merged = objectDeepMerge(target, source1, source2);
console.log(merged); // {a: 5, b: {c: 6, d: 3}, e: 4}
```

View File

@ -0,0 +1,50 @@
# SVG Sprite
An SVG sprite is a collection of SVG images combined into a single SVG file. This allows for efficient loading and management of icons in web applications.
The Alliance Auth framework provides a built-in SVG sprite that contains a selection of icons used in Alliance Auth. This sprite is automatically included in the base template, so you don't need to do anything special to use it.
## Using the SVG Sprite
To use an icon from the SVG sprite, you can use the following HTML syntax:
```html
<svg>
<use href="#[icon-name]"></use>
</svg>
```
Replace `[icon-name]` with the name of the icon you want to use. For example, to use the Alliance Auth logo, you would write:
```html
<svg>
<use href="#aa-logo"></use>
</svg>
```
## Available Icons
The following icons are available in the Alliance Auth SVG sprite:
- `aa-logo`: The Alliance Auth logo
- `aa-loading-spinner`: A loading spinner icon
### Alliance Auth Logo
The Alliance Auth logo can be used with the following code:
```html
<svg>
<use href="#aa-logo"></use>
</svg>
```
### Loading Spinner
The loading spinner can be used with the following code:
```html
<svg>
<use href="#aa-loading-spinner"></use>
</svg>
```

View File

@ -14,9 +14,8 @@ Alliance Auth can be installed on any in-support *nix operating system.
Our install documentation targets the following operating systems. Our install documentation targets the following operating systems.
- Ubuntu 20.04 - Not Recommended for new installs
- Ubuntu 22.04 - Ubuntu 22.04
- Centos 7 - Ubuntu 24.04
- CentOS Stream 8 - CentOS Stream 8
- CentOS Stream 9 - CentOS Stream 9
@ -27,7 +26,7 @@ To install on your favorite flavour of Linux, identify and install equivalent pa
It is recommended to ensure your OS is fully up-to-date before proceeding. We may also add Package Repositories here, used later in the documentation. It is recommended to ensure your OS is fully up-to-date before proceeding. We may also add Package Repositories here, used later in the documentation.
::::{tabs} ::::{tabs}
:::{group-tab} Ubuntu 2004, 2204, 2404 :::{group-tab} Ubuntu 2204, 2404
```shell ```shell
sudo apt-get update sudo apt-get update
@ -35,14 +34,6 @@ sudo apt-get upgrade
sudo do-dist-upgrade sudo do-dist-upgrade
``` ```
:::
:::{group-tab} CentOS 7
```shell
yum install epel-release
sudo yum upgrade
```
::: :::
:::{group-tab} CentOS Stream 8 :::{group-tab} CentOS Stream 8
@ -70,7 +61,7 @@ Install Python 3.11 and related tools on your system.
::::{tabs} ::::{tabs}
:::{group-tab} Ubuntu 2004, 2204, 2404 :::{group-tab} Ubuntu 2204, 2404
```shell ```shell
sudo add-apt-repository ppa:deadsnakes/ppa sudo add-apt-repository ppa:deadsnakes/ppa
@ -78,20 +69,6 @@ sudo apt-get update
sudo apt-get install python3.11 python3.11-dev python3.11-venv sudo apt-get install python3.11 python3.11-dev python3.11-venv
``` ```
:::
:::{group-tab} CentOS 7
We need to build Python from source
```bash
cd ~
sudo yum install gcc openssl-devel bzip2-devel libffi-devel wget
wget https://www.python.org/ftp/python/3.11.7/Python-3.11.7.tgz
tar xvf Python-3.11.7.tgz
cd Python-3.11.7/
./configure --enable-optimizations --enable-shared
sudo make altinstall
```
::: :::
:::{group-tab} CentOS Stream 8 :::{group-tab} CentOS Stream 8
We need to build Python from source We need to build Python from source
@ -125,27 +102,19 @@ sudo make altinstall
### Database ### Database
It's recommended to use a database service instead of SQLite. Many options are available, but this guide will use MariaDB 10.11 It's recommended to use a database service instead of SQLite. Many options are available, but this guide will use MariaDB 11.8
::::{tabs} ::::{tabs}
:::{group-tab} Ubuntu 2004, 2204, 2404 :::{group-tab} Ubuntu 2204, 2404
Follow the instructions at <https://mariadb.org/download/?t=repo-config&d=20.04+%22focal%22&v=10.11&r_m=osuosl> to add the MariaDB repository to your host. Follow the instructions at <https://mariadb.org/download/?t=repo-config&d=24.04+"noble"&v=11.8> to add the MariaDB repository to your host.
```shell ```shell
sudo apt-get install mariadb-server mariadb-client libmysqlclient-dev sudo apt-get install mariadb-server mariadb-client libmysqlclient-dev
``` ```
:::
:::{group-tab} CentOS 7
Follow the instructions at <https://mariadb.org/download/?t=repo-config&d=CentOS+7&v=10.11&r_m=osuosl> to add the MariaDB repository to your host.
```shell
sudo yum install MariaDB-server MariaDB-client MariaDB-devel MariaDB-shared
```
::: :::
:::{group-tab} CentOS Stream 8 :::{group-tab} CentOS Stream 8
Follow the instructions at <https://mariadb.org/download/?t=repo-config&d=CentOS+Stream&v=10.11&r_m=osuosl> to add the MariaDB repository to your host. Follow the instructions at <https://mariadb.org/download/?t=repo-config&d=CentOS+Stream&v=11.8> to add the MariaDB repository to your host.
```shell ```shell
sudo dnf install mariadb mariadb-server mariadb-devel sudo dnf install mariadb mariadb-server mariadb-devel
@ -153,7 +122,7 @@ sudo dnf install mariadb mariadb-server mariadb-devel
::: :::
:::{group-tab} CentOS Stream 9 :::{group-tab} CentOS Stream 9
Follow the instructions at <https://mariadb.org/download/?t=repo-config&d=CentOS+Stream&v=10.11&r_m=osuosl> to add the MariaDB repository to your host. Follow the instructions at <https://mariadb.org/download/?t=repo-config&d=CentOS+Stream&v=11.8> to add the MariaDB repository to your host.
```shell ```shell
sudo dnf install mariadb mariadb-server mariadb-devel sudo dnf install mariadb mariadb-server mariadb-devel
@ -164,12 +133,9 @@ sudo dnf install mariadb mariadb-server mariadb-devel
:::::{important} :::::{important}
::::{tabs} ::::{tabs}
:::{group-tab} Ubuntu 2004, 2204, 2404 :::{group-tab} Ubuntu 2204, 2404
If you don't plan on running the database on the same server as auth you still need to install the `libmysqlclient-dev` package If you don't plan on running the database on the same server as auth you still need to install the `libmysqlclient-dev` package
::: :::
:::{group-tab} CentOS 7
If you don't plan on running the database on the same server as auth you still need to install the `mariadb-devel` package
:::
:::{group-tab} CentOS Stream 8 :::{group-tab} CentOS Stream 8
If you don't plan on running the database on the same server as auth you still need to install the `mariadb-devel` package If you don't plan on running the database on the same server as auth you still need to install the `mariadb-devel` package
::: :::
@ -185,7 +151,7 @@ A few extra utilities are also required for the installation of packages.
::::{tabs} ::::{tabs}
:::{group-tab} Ubuntu 2004, 2204, 2404 :::{group-tab} Ubuntu 2204, 2404
```shell ```shell
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
@ -195,18 +161,6 @@ sudo apt-get update
sudo apt-get install unzip git redis-server curl libssl-dev libbz2-dev libffi-dev build-essential pkg-config sudo apt-get install unzip git redis-server curl libssl-dev libbz2-dev libffi-dev build-essential pkg-config
``` ```
:::
:::{group-tab} CentOS 7
```shell
sudo yum install gcc gcc-c++ unzip git redis curl bzip2-devel openssl-devel libffi-devel wget pkg-config
```
```shell
sudo systemctl enable redis.service
sudo systemctl start redis.service
```
::: :::
:::{group-tab} CentOS Stream 8 :::{group-tab} CentOS Stream 8
@ -282,19 +236,12 @@ mysql_secure_installation
For security and permissions, it's highly recommended you create a separate user to install auth under. Do not log in as this account. For security and permissions, it's highly recommended you create a separate user to install auth under. Do not log in as this account.
::::{tabs} ::::{tabs}
:::{group-tab} Ubuntu 2004, 2204, 2404 :::{group-tab} Ubuntu 2204, 2404
```shell ```shell
sudo adduser --disabled-login allianceserver --shell /bin/bash sudo adduser --disabled-login allianceserver --shell /bin/bash
``` ```
:::
:::{group-tab} CentOS 7
```shell
sudo passwd -l allianceserver
```
::: :::
:::{group-tab} CentOS Stream 8 :::{group-tab} CentOS Stream 8
@ -497,27 +444,12 @@ exit
::::{tabs} ::::{tabs}
:::{group-tab} Ubuntu 2004, 2204, 2404 :::{group-tab} Ubuntu 2204, 2404
```shell ```shell
sudo apt-get install supervisor sudo apt-get install supervisor
``` ```
:::
:::{group-tab} CentOS 7
```shell
sudo dnf install supervisor
```
```shell
sudo systemctl enable supervisord.service
```
```shell
sudo systemctl start supervisord.service
```
::: :::
:::{group-tab} CentOS Stream 8 :::{group-tab} CentOS Stream 8
@ -554,19 +486,12 @@ sudo systemctl start supervisord.service
Once installed, it needs a configuration file to know which processes to watch. Your Alliance Auth project comes with a ready-to-use template which will ensure the Celery workers, Celery task scheduler and Gunicorn are all running. Once installed, it needs a configuration file to know which processes to watch. Your Alliance Auth project comes with a ready-to-use template which will ensure the Celery workers, Celery task scheduler and Gunicorn are all running.
::::{tabs} ::::{tabs}
:::{group-tab} Ubuntu 2004, 2204, 2404 :::{group-tab} Ubuntu 2204, 2404
```shell ```shell
ln -s /home/allianceserver/myauth/supervisor.conf /etc/supervisor/conf.d/myauth.conf ln -s /home/allianceserver/myauth/supervisor.conf /etc/supervisor/conf.d/myauth.conf
``` ```
:::
:::{group-tab} CentOS 7
```shell
sudo ln -s /home/allianceserver/myauth/supervisor.conf /etc/supervisord.d/myauth.ini
```
::: :::
:::{group-tab} CentOS Stream 8 :::{group-tab} CentOS Stream 8