mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-08-24 02:41:42 +02:00
Compare commits
16 Commits
bb53816360
...
d660d5b044
Author | SHA1 | Date | |
---|---|---|---|
|
d660d5b044 | ||
|
9ff926ae4d | ||
|
b28cbdad31 | ||
|
3c1bae463e | ||
|
91fbdb9ec1 | ||
|
c1abc56ebc | ||
|
f1582165bc | ||
|
0f155369a1 | ||
|
f81c1d1b31 | ||
|
0360184c2d | ||
|
099a39a2a2 | ||
|
c1cd7ca64f | ||
|
4cc108ab7f | ||
|
0028310aa5 | ||
|
18e9453fed | ||
|
db74ddfdf5 |
@ -15,6 +15,10 @@
|
||||
ul#nav-right:has(li) + ul#nav-right-character-control > li:first-child {
|
||||
display: list-item !important;
|
||||
}
|
||||
|
||||
form.is-submitting button[type="submit"] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (max-width: 991px) {
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
23
allianceauth/framework/templates/framework/svg/sprite.svg
Normal file
23
allianceauth/framework/templates/framework/svg/sprite.svg
Normal 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 |
@ -70,8 +70,8 @@
|
||||
{% translate "Leave" %}
|
||||
</a>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>
|
||||
{% translate "Pending" %}
|
||||
<button type="button" class="btn btn-secondary cursor-help me-1" data-bs-tooltip="aa-tooltip" title="{% translate 'Request pending' %}">
|
||||
<i class="fa-regular fa-hourglass-half"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% elif not g.request %}
|
||||
@ -85,9 +85,13 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-primary" disabled>
|
||||
{% translate "Pending" %}
|
||||
<button type="button" class="btn btn-secondary cursor-help me-1" data-bs-tooltip="aa-tooltip" title="{% translate 'Request pending' %}">
|
||||
<i class="fa-regular fa-hourglass-half"></i>
|
||||
</button>
|
||||
|
||||
<a href="{% url 'groupmanagement:request_retract' g.group.id %}" class="btn btn-danger">
|
||||
{% translate "Retract" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -10,6 +10,11 @@ urlpatterns = [
|
||||
path(
|
||||
"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
|
||||
path("groupmanagement/requests/", views.group_management, name="management"),
|
||||
path("groupmanagement/membership/", views.group_membership, name="membership"),
|
||||
|
@ -420,3 +420,42 @@ def group_request_leave(request, group_id):
|
||||
grouprequest.notify_leaders()
|
||||
messages.success(request, _('Applied to leave group %(group)s.') % {"group": group})
|
||||
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 |
@ -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 |
@ -1,4 +1,4 @@
|
||||
/* global notificationUpdateSettings */
|
||||
/* global notificationUpdateSettings, fetchGet */
|
||||
|
||||
/**
|
||||
* This script refreshed the notification icon in the top menu
|
||||
@ -19,22 +19,9 @@ $(() => {
|
||||
* Update the notification icon in the top menu
|
||||
*/
|
||||
const updateNotificationIcon = () => {
|
||||
fetch(userNotificationCountViewUrl)
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
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');
|
||||
}
|
||||
fetchGet({url: userNotificationCountViewUrl})
|
||||
.then((data) => {
|
||||
elementNotificationIcon.toggleClass('text-danger', data.unread_count > 0);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(`Failed to load HTMl to render notifications item. Error: ${error.message}`);
|
||||
|
@ -8,30 +8,22 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const elemCard = document.getElementById("esi-alert");
|
||||
const elemMessage = document.getElementById("esi-data");
|
||||
const elemCode = document.getElementById("esi-code");
|
||||
const elemCard = document.getElementById('esi-alert');
|
||||
const elemMessage = document.getElementById('esi-data');
|
||||
const elemCode = document.getElementById('esi-code');
|
||||
|
||||
fetch('{% url "authentication:esi_check" %}')
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
throw new Error("Something went wrong");
|
||||
})
|
||||
.then((responseJson) => {
|
||||
console.log("ESI Check: ", JSON.stringify(responseJson, null, 2));
|
||||
fetchGet({url: '{% url "authentication:esi_check" %}'})
|
||||
.then((data) => {
|
||||
console.log('ESI Check: ', JSON.stringify(data, null, 2));
|
||||
|
||||
const status = responseJson.status;
|
||||
if (status !== 200) {
|
||||
elemCode.textContent = status
|
||||
elemMessage.textContent = responseJson.data.error;
|
||||
new bootstrap.Collapse(elemCard, {
|
||||
toggle: true
|
||||
})
|
||||
if (data.status !== 200) {
|
||||
elemCode.textContent = data.status;
|
||||
elemMessage.textContent = data.data.error;
|
||||
|
||||
new bootstrap.Collapse(elemCard, {toggle: true});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
console.error('Error fetching ESI check:', error);
|
||||
});
|
||||
</script>
|
||||
|
@ -1,6 +1,23 @@
|
||||
{% load i18n %}
|
||||
{% 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 %}
|
||||
<div id="aa-dashboard-panel-admin-notifications" class="col-12 mb-3">
|
||||
<div class="card">
|
||||
@ -118,49 +135,25 @@
|
||||
</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>
|
||||
const elemRunning = document.getElementById("task-counts");
|
||||
const elemQueued = document.getElementById("queued-tasks-count");
|
||||
const elemRunning = document.getElementById('task-counts');
|
||||
const elemQueued = document.getElementById('queued-tasks-count');
|
||||
|
||||
fetch('{% url "authentication:task_counts" %}')
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
return response.json();
|
||||
}
|
||||
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();
|
||||
}
|
||||
fetchGet({url: '{% url "authentication:task_counts" %}'})
|
||||
.then((data) => {
|
||||
const running = data.tasks_running;
|
||||
const queued = data.tasks_queued;
|
||||
|
||||
const queued = responseJson.tasks_queued;
|
||||
if (queued == null) {
|
||||
elemQueued.textContent = "N/A";
|
||||
} else {
|
||||
elemQueued.textContent = queued.toLocaleString();
|
||||
}
|
||||
const updateTaskCount = (element, value) => {
|
||||
element.textContent = value == null ? 'N/A' : value.toLocaleString();
|
||||
};
|
||||
|
||||
updateTaskCount(elemRunning, running);
|
||||
updateTaskCount(elemQueued, queued);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
elemRunning.textContent = "ERROR";
|
||||
elemQueued.textContent = "ERROR";
|
||||
console.error('Error fetching task queue:', error);
|
||||
|
||||
[elemRunning, elemQueued].forEach(elem => elem.textContent = 'ERROR');
|
||||
});
|
||||
</script>
|
||||
|
@ -23,6 +23,9 @@
|
||||
{% include 'bundles/fontawesome.html' %}
|
||||
{% include 'bundles/auth-framework-css.html' %}
|
||||
|
||||
{% include 'bundles/jquery-js.html' %}
|
||||
{% include 'bundles/auth-framework-js.html' %}
|
||||
|
||||
<style>
|
||||
@media all {
|
||||
.nav-padding {
|
||||
@ -137,8 +140,6 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
{% include 'bundles/jquery-js.html' %}
|
||||
|
||||
{% theme_js %}
|
||||
|
||||
{% if user.is_authenticated %}
|
||||
@ -159,5 +160,7 @@
|
||||
{% block extra_script %}
|
||||
{% endblock extra_script %}
|
||||
</script>
|
||||
|
||||
{% include "framework/svg/sprite.svg" %}
|
||||
</body>
|
||||
</html>
|
||||
|
3
allianceauth/templates/bundles/auth-framework-js.html
Normal file
3
allianceauth/templates/bundles/auth-framework-js.html
Normal file
@ -0,0 +1,3 @@
|
||||
{% load sri %}
|
||||
|
||||
{% sri_static 'allianceauth/framework/js/auth-framework.js' %}
|
@ -1,3 +1,3 @@
|
||||
{% load static %}
|
||||
|
||||
<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 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>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 243 B After Width: | Height: | Size: 217 B |
@ -44,8 +44,8 @@ x-allianceauth-health-check: &allianceauth-health-checks
|
||||
|
||||
services:
|
||||
auth_mysql:
|
||||
image: mariadb:10.11
|
||||
command: [mysqld, --character-set-server=utf8mb4, --collation-server=utf8mb4_unicode_ci, --default-authentication-plugin=mysql_native_password]
|
||||
image: mariadb:11.8
|
||||
command: [mariadbd, --default-authentication-plugin=mysql_native_password]
|
||||
volumes:
|
||||
- ./mysql-data:/var/lib/mysql
|
||||
- ./setup.sql:/docker-entrypoint-initdb.d/setup.sql
|
||||
@ -77,7 +77,7 @@ services:
|
||||
max-file: "5"
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
image: redis:8
|
||||
command: redis-server
|
||||
restart: always
|
||||
volumes:
|
||||
@ -132,7 +132,7 @@ services:
|
||||
replicas: 2
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana-oss:9.5.2
|
||||
image: grafana/grafana-oss:latest
|
||||
restart: always
|
||||
depends_on:
|
||||
- auth_mysql
|
||||
|
@ -13,5 +13,7 @@ The Alliance Auth framework is split into several submodules, each of which is d
|
||||
|
||||
framework/api
|
||||
framework/css
|
||||
framework/js
|
||||
framework/templates
|
||||
framework/svg-sprite
|
||||
:::
|
||||
|
124
docs/development/custom/framework/js.md
Normal file
124
docs/development/custom/framework/js.md
Normal 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}
|
||||
```
|
50
docs/development/custom/framework/svg-sprite.md
Normal file
50
docs/development/custom/framework/svg-sprite.md
Normal 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>
|
||||
```
|
@ -14,9 +14,8 @@ Alliance Auth can be installed on any in-support *nix operating system.
|
||||
|
||||
Our install documentation targets the following operating systems.
|
||||
|
||||
- Ubuntu 20.04 - Not Recommended for new installs
|
||||
- Ubuntu 22.04
|
||||
- Centos 7
|
||||
- Ubuntu 24.04
|
||||
- CentOS Stream 8
|
||||
- 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.
|
||||
|
||||
::::{tabs}
|
||||
:::{group-tab} Ubuntu 2004, 2204, 2404
|
||||
:::{group-tab} Ubuntu 2204, 2404
|
||||
|
||||
```shell
|
||||
sudo apt-get update
|
||||
@ -35,14 +34,6 @@ sudo apt-get upgrade
|
||||
sudo do-dist-upgrade
|
||||
```
|
||||
|
||||
:::
|
||||
:::{group-tab} CentOS 7
|
||||
|
||||
```shell
|
||||
yum install epel-release
|
||||
sudo yum upgrade
|
||||
```
|
||||
|
||||
:::
|
||||
:::{group-tab} CentOS Stream 8
|
||||
|
||||
@ -70,7 +61,7 @@ Install Python 3.11 and related tools on your system.
|
||||
|
||||
::::{tabs}
|
||||
|
||||
:::{group-tab} Ubuntu 2004, 2204, 2404
|
||||
:::{group-tab} Ubuntu 2204, 2404
|
||||
|
||||
```shell
|
||||
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
|
||||
```
|
||||
|
||||
:::
|
||||
:::{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
|
||||
We need to build Python from source
|
||||
@ -125,27 +102,19 @@ sudo make altinstall
|
||||
|
||||
### 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}
|
||||
:::{group-tab} Ubuntu 2004, 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.
|
||||
:::{group-tab} Ubuntu 2204, 2404
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
sudo dnf install mariadb mariadb-server mariadb-devel
|
||||
@ -164,12 +133,9 @@ sudo dnf install mariadb mariadb-server mariadb-devel
|
||||
|
||||
:::::{important}
|
||||
::::{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
|
||||
:::
|
||||
:::{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
|
||||
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}
|
||||
|
||||
:::{group-tab} Ubuntu 2004, 2204, 2404
|
||||
:::{group-tab} Ubuntu 2204, 2404
|
||||
|
||||
```shell
|
||||
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
|
||||
```
|
||||
|
||||
:::
|
||||
:::{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
|
||||
|
||||
@ -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.
|
||||
::::{tabs}
|
||||
|
||||
:::{group-tab} Ubuntu 2004, 2204, 2404
|
||||
:::{group-tab} Ubuntu 2204, 2404
|
||||
|
||||
```shell
|
||||
sudo adduser --disabled-login allianceserver --shell /bin/bash
|
||||
```
|
||||
|
||||
:::
|
||||
:::{group-tab} CentOS 7
|
||||
|
||||
```shell
|
||||
sudo passwd -l allianceserver
|
||||
```
|
||||
|
||||
:::
|
||||
:::{group-tab} CentOS Stream 8
|
||||
|
||||
@ -497,27 +444,12 @@ exit
|
||||
|
||||
::::{tabs}
|
||||
|
||||
:::{group-tab} Ubuntu 2004, 2204, 2404
|
||||
:::{group-tab} Ubuntu 2204, 2404
|
||||
|
||||
```shell
|
||||
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
|
||||
|
||||
@ -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.
|
||||
::::{tabs}
|
||||
|
||||
:::{group-tab} Ubuntu 2004, 2204, 2404
|
||||
:::{group-tab} Ubuntu 2204, 2404
|
||||
|
||||
```shell
|
||||
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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user