added web and fix some problems
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-Make (push) Successful in 9s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-CMake (push) Successful in 14s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-Meson (push) Failing after 8s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-Make (push) Successful in 9s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-CMake (push) Successful in 14s
Prepare Build and Testing executable binary / Prepare-Build-Testing-With-Meson (push) Failing after 8s
This commit is contained in:
1 parent
ccfc8b535e
commit
155c259ea9
29 files changed
+946
-119
No files matched your search
@@ -0,0 +1,46 @@
|
||||
const adduserbutton = document.querySelector('#adduserbutton')
|
||||
const adduserform = document.querySelector('.adduserform')
|
||||
const canceladduserbutton = document.querySelector('#canceladduser')
|
||||
const adduserformbody = document.querySelector('.adduserformbody')
|
||||
const usernameinput = document.querySelector('#adduserformusername')
|
||||
const passwordinput = document.querySelector('#adduserformpassword')
|
||||
const groupinput = document.querySelector('#adduserformgroup')
|
||||
const is_superuserinput = document.querySelector('#adduserformissuperuser')
|
||||
const emailinput = document.querySelector('#adduserformemail')
|
||||
|
||||
adduserbutton.addEventListener('click', () => {
|
||||
adduserform.classList.add('active')
|
||||
})
|
||||
|
||||
canceladduserbutton.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
adduserform.classList.remove('active')
|
||||
})
|
||||
|
||||
adduserformbody.addEventListener('submit', async (e) => {
|
||||
e.preventDefault()
|
||||
const hashedpassword = await hashPassword(passwordinput.value)
|
||||
const new_user = {
|
||||
username: usernameinput.value,
|
||||
email: emailinput.value,
|
||||
password: hashedpassword,
|
||||
group: groupinput.value,
|
||||
is_superuser: is_superuserinput.checked
|
||||
}
|
||||
const status = await fetch(routes.adduser(), {
|
||||
method: 'POST',
|
||||
body: `{"token":"${localStorage.getItem('token')}","username":"${new_user.username}","email":"${new_user.email}","password":"${new_user.password}","group":"${new_user.group}","is_superuser":"${new_user.is_superuser}"}`
|
||||
}).then(data => data.json()).then(jsondata => jsondata).catch((error) => {
|
||||
notification(`Ошибка на сервере: ${error}`, "error")
|
||||
})
|
||||
if (!status.error) {
|
||||
adduserform.classList.remove('active')
|
||||
await get_all_users()
|
||||
notification("Пользователь успешно добавлен", "success")
|
||||
usernameinput.value = ""
|
||||
passwordinput.value = ""
|
||||
groupinput.value = ""
|
||||
} else {
|
||||
notification(status.error, "error")
|
||||
}
|
||||
})
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
/*!
|
||||
* Bootstrap v5.3.5 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2025 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||
*/
|
||||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e(require("@popperjs/core")):"function"==typeof define&&define.amd?define(["@popperjs/core"],e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e(t.Popper)}(this,(function(t){"use strict";function e(t){const e=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t)for(const i in t)if("default"!==i){const s=Object.getOwnPropertyDescriptor(t,i);Object.defineProperty(e,i,s.get?s:{enumerable:!0,get:()=>t[i]})}return e.default=t,Object.freeze(e)}const i=e(t),s=new Map,n={set(t,e,i){s.has(t)||s.set(t,new Map);const n=s.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>s.has(t)&&s.get(t).get(e)||null,remove(t,e){if(!s.has(t))return;const i=s.get(t);i.delete(e),0===i.size&&s.delete(t)}},o="transitionend",r=t=>(t&&window.CSS&&window.CSS.escape&&(t=t.replace(/#([^\s"#']+)/g,((t,e)=>`#${CSS.escape(e)}`))),t),a=t=>{t.dispatchEvent(new Event(o))},l=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),c=t=>l(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(r(t)):null,h=t=>{if(!l(t)||0===t.getClientRects().length)return!1;const e="visible"===getComputedStyle(t).getPropertyValue("visibility"),i=t.closest("details:not([open])");if(!i)return e;if(i!==t){const e=t.closest("summary");if(e&&e.parentNode!==i)return!1;if(null===e)return!1}return e},d=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),u=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?u(t.parentNode):null},_=()=>{},g=t=>{t.offsetHeight},f=()=>window.jQuery&&!document.body.hasAttribute("data-bs-no-jquery")?window.jQuery:null,m=[],p=()=>"rtl"===document.documentElement.dir,b=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,s=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=s,t.jQueryInterface)}},"loading"===document.readyState?(m.length||document.addEventListener("DOMContentLoaded",(()=>{for(const t of m)t()})),m.push(e)):e()},v=(t,e=[],i=t)=>"function"==typeof t?t.call(...e):i,y=(t,e,i=!0)=>{if(!i)return void v(t);const s=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const s=Number.parseFloat(e),n=Number.parseFloat(i);return s||n?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(e)+5;let n=!1;const r=({target:i})=>{i===e&&(n=!0,e.removeEventListener(o,r),v(t))};e.addEventListener(o,r),setTimeout((()=>{n||a(e)}),s)},w=(t,e,i,s)=>{const n=t.length;let o=t.indexOf(e);return-1===o?!i&&s?t[n-1]:t[0]:(o+=i?1:-1,s&&(o=(o+n)%n),t[Math.max(0,Math.min(o,n-1))])},A=/[^.]*(?=\..*)\.|.*/,E=/\..*/,C=/::\d+$/,T={};let k=1;const $={mouseenter:"mouseover",mouseleave:"mouseout"},S=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${k++}`||t.uidEvent||k++}function O(t){const e=L(t);return t.uidEvent=e,T[e]=T[e]||{},T[e]}function I(t,e,i=null){return Object.values(t).find((t=>t.callable===e&&t.delegationSelector===i))}function D(t,e,i){const s="string"==typeof e,n=s?i:e||i;let o=M(t);return S.has(o)||(o=t),[s,n,o]}function N(t,e,i,s,n){if("string"!=typeof e||!t)return;let[o,r,a]=D(e,i,s);if(e in $){const t=t=>function(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};r=t(r)}const l=O(t),c=l[a]||(l[a]={}),h=I(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&n);const d=L(r,e.replace(A,"")),u=o?function(t,e,i){return function s(n){const o=t.querySelectorAll(e);for(let{target:r}=n;r&&r!==this;r=r.parentNode)for(const a of o)if(a===r)return F(n,{delegateTarget:r}),s.oneOff&&j.off(t,n.type,e,i),i.apply(r,[n])}}(t,i,r):function(t,e){return function i(s){return F(s,{delegateTarget:t}),i.oneOff&&j.off(t,s.type,e),e.apply(t,[s])}}(t,r);u.delegationSelector=o?i:null,u.callable=r,u.oneOff=n,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function P(t,e,i,s,n){const o=I(e[i],s,n);o&&(t.removeEventListener(i,o,Boolean(n)),delete e[i][o.uidEvent])}function x(t,e,i,s){const n=e[i]||{};for(const[o,r]of Object.entries(n))o.includes(Line truncated
|
||||
//# sourceMappingURL=bootstrap.min.js.map
|
||||
@@ -0,0 +1,34 @@
|
||||
const changepasswordform = document.querySelector('.changepasswordform')
|
||||
const cancelchangepassword = document.querySelector('#cancelchangepassword')
|
||||
const changepasswordformbody = document.querySelector('.changepasswordformbody')
|
||||
const changepasswordnewpassword = document.querySelector('#changepasswordnewpassword')
|
||||
|
||||
let changepassuserid = "";
|
||||
|
||||
const change_password = (userid) => {
|
||||
changepasswordform.classList.add('active')
|
||||
changepassuserid = userid
|
||||
}
|
||||
|
||||
cancelchangepassword.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
changepasswordform.classList.remove('active')
|
||||
})
|
||||
|
||||
changepasswordformbody.addEventListener('submit', async e => {
|
||||
e.preventDefault()
|
||||
const hashedpassword = await hashPassword(changepasswordnewpassword.value)
|
||||
const status = await fetch(routes.change_password_url(), {
|
||||
method: 'POST',
|
||||
body: `{"token":"${localStorage.getItem('token')}","userid":${changepassuserid},"new_password":"${hashedpassword}"}`
|
||||
}).then(data => data.json()).then(jsondata => jsondata).catch((error) => {
|
||||
notification(`Ошибка на сервере: ${error}`, "error")
|
||||
})
|
||||
if (!status.error) {
|
||||
notification("Пароль успешно изменен", "success")
|
||||
changepasswordform.classList.remove('active')
|
||||
changepasswordnewpassword.value = ""
|
||||
} else {
|
||||
notification(status.error, "error")
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
const changeuserform = document.querySelector('.changeuserform')
|
||||
const changeuserformbody = document.querySelector('.changeuserformbody')
|
||||
const changeuserformusername = document.querySelector('#changeuserformusername')
|
||||
const changeuserformemail = document.querySelector('#changeuserformemail')
|
||||
const changeuserformgroup = document.querySelector('#changeuserformgroup')
|
||||
const changeuserformissuperuser = document.querySelector('#changeuserformissuperuser')
|
||||
const cancelchangeuser = document.querySelector("#cancelchangeuser")
|
||||
|
||||
let changedatauserid = "";
|
||||
let userdata = "";
|
||||
|
||||
const change_user_data = async (userid) => {
|
||||
changeuserform.classList.add('active')
|
||||
changedatauserid = userid
|
||||
const all_users = await fetch(routes.all_users(), {
|
||||
method: 'POST',
|
||||
body: `{"token":"${localStorage.getItem('token')}"}`
|
||||
}).then(data => data.json()).then(jsondata => jsondata).catch((error) => {
|
||||
notification(`Ошибка на сервере: ${error}`, "error")
|
||||
})
|
||||
userdata = all_users.find(usr => usr.id === userid)
|
||||
changeuserformusername.value = userdata.username
|
||||
changeuserformgroup.value = userdata.group
|
||||
changeuserformissuperuser.checked = userdata.is_superuser === "1"
|
||||
changeuserformemail.value = userdata.email
|
||||
}
|
||||
|
||||
cancelchangeuser.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
changeuserform.classList.remove('active')
|
||||
})
|
||||
|
||||
changeuserformbody.addEventListener('submit', async (e) => {
|
||||
e.preventDefault()
|
||||
let request = {
|
||||
token: localStorage.getItem('token'),
|
||||
userid: changedatauserid,
|
||||
username: changeuserformusername.value,
|
||||
email: changeuserformemail.value,
|
||||
is_superuser: changeuserformissuperuser.checked,
|
||||
group: changeuserformgroup.value
|
||||
};
|
||||
const status = await fetch(routes.change_user(), {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(request)
|
||||
}).then(data => data.json()).then(jsondata => jsondata).catch((error) => {
|
||||
notification(`Ошибка на сервере: ${error}`, "error")
|
||||
})
|
||||
if (!status.error) {
|
||||
notification("Данные пользователя успешно изменены", "success")
|
||||
changeuserform.classList.remove('active')
|
||||
await get_all_users()
|
||||
} else {
|
||||
notification(status.error, "error")
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
const userstablebody = document.querySelector('#alluserstablebody')
|
||||
const confirmform = document.querySelector('.confirmform')
|
||||
const confirmformcancel = document.querySelector('#no')
|
||||
const confirmformok = document.querySelector('#yes')
|
||||
|
||||
let useridtodelete = "";
|
||||
|
||||
userstablebody.addEventListener('click', e => {
|
||||
if (e.target.classList.contains('deleteuser')) {
|
||||
useridtodelete = e.target.id
|
||||
confirmform.classList.add("active")
|
||||
}
|
||||
if (e.target.classList.contains('changepassword')) {
|
||||
change_password(e.target.id)
|
||||
}
|
||||
if (e.target.classList.contains('changeuser')) {
|
||||
change_user_data(parseInt(e.target.id))
|
||||
}
|
||||
})
|
||||
|
||||
confirmformcancel.addEventListener('click', () => {
|
||||
useridtodelete = ""
|
||||
confirmform.classList.remove('active')
|
||||
})
|
||||
|
||||
confirmformok.addEventListener('click', async () => {
|
||||
const status = await fetch(routes.deluser(), {
|
||||
method: 'DELETE',
|
||||
body: `{"token":"${localStorage.getItem('token')}","userid":${useridtodelete}}`
|
||||
}).then(data => data.json()).then(jsondata => jsondata).catch((error) => {
|
||||
notification(`Ошибка на сервере: ${error}`, "error")
|
||||
})
|
||||
if (status.success) {
|
||||
confirmform.classList.remove('active')
|
||||
await get_all_users()
|
||||
notification("Пользователь успешно удален", "success")
|
||||
} else {
|
||||
notification("Can't delete user", "error")
|
||||
confirmform.classList.remove('active')
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
const get_all_users = async () => {
|
||||
const response = await fetch(routes.all_users(), {
|
||||
method: "POST",
|
||||
body: `{"token":"${localStorage.getItem("token")}"}`
|
||||
}).then(data => data.json()).then(jsondata => jsondata).catch((error) => {
|
||||
notification(`Ошибка на сервере: ${error}`, "error")
|
||||
})
|
||||
const alluserstablebody = document.querySelector('#alluserstablebody')
|
||||
alluserstablebody.innerHTML = ""
|
||||
for (const user of response) {
|
||||
alluserstablebody.innerHTML += `
|
||||
<tr>
|
||||
<th><p>${user.id}</p></th>
|
||||
<th>${user.username}</th>
|
||||
<th>${user.email}</th>
|
||||
<th>${user.group}</th>
|
||||
<th>${user.is_superuser}</th>
|
||||
<th><button class="deleteuser btn btn-danger" id="${user.id}">Delete</button></th>
|
||||
<th><button class="changepassword btn btn-warning" id="${user.id}">Change password</button></th>
|
||||
<th><button class="changeuser btn btn-warning" id="${user.id}">Change user data</button></th>
|
||||
</tr>
|
||||
`
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
const loginform = document.querySelector('#loginform')
|
||||
const loginformbody = document.querySelector('#loginformbody')
|
||||
const loginforminputusername = document.querySelector('#loginforminputusername')
|
||||
const loginforminputpassword = document.querySelector('#loginforminputpassword')
|
||||
const exitbutton = document.querySelector('#exitbutton')
|
||||
const usernameheader = document.querySelector('.usernameheader')
|
||||
|
||||
if (localStorage.getItem("token") === null) {
|
||||
loginform.classList.add("active")
|
||||
}
|
||||
|
||||
loginformbody.addEventListener('submit', (e) => {
|
||||
e.preventDefault()
|
||||
hashPassword(loginforminputpassword.value).then(hashedpassword => {
|
||||
login(loginforminputusername.value, hashedpassword)
|
||||
})
|
||||
})
|
||||
|
||||
const login = async (username, password) => {
|
||||
const response = await fetch(routes.login(), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
// 'Access-Control-Allow-Origin': '*',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
// mode: 'no-cors',
|
||||
body: `{"username":"${username}","password":"${password}"}`
|
||||
}).then(data => data.json()).then(jsondata => jsondata).catch((error) => {
|
||||
if (error.message === "Unexpected end of JSON input") {
|
||||
notification(`Ошибка на сервере: Создайте файл users.json и запишите в него первого пользователя`, "error")
|
||||
} else {
|
||||
notification(`Ошибка на сервере: ${error}`, "error")
|
||||
}
|
||||
})
|
||||
if (response.error) {
|
||||
loginforminputusername.classList.add('is-invalid')
|
||||
loginforminputpassword.classList.add('is-invalid')
|
||||
notification("Неправильный логин или пароль", "error")
|
||||
}
|
||||
if (response.token) {
|
||||
notification("Успешно", "success")
|
||||
loginform.classList.remove("active")
|
||||
localStorage.setItem("token", response.token)
|
||||
await get_user_data()
|
||||
loginforminputusername.value = ""
|
||||
loginforminputpassword.value = ""
|
||||
loginforminputusername.classList.remove('is-invalid')
|
||||
loginforminputpassword.classList.remove('is-invalid')
|
||||
}
|
||||
}
|
||||
|
||||
const get_user_data = async () => {
|
||||
const response = await fetch(routes.user(), {
|
||||
method: "POST",
|
||||
body: `{"token":"${localStorage.getItem("token")}"}`
|
||||
}).then(data => data.json()).then(jsondata => jsondata).catch((error) => {
|
||||
notification(`Ошибка на сервере: ${error}`, "error")
|
||||
})
|
||||
if (response.is_superuser) {
|
||||
await get_all_users()
|
||||
usernameheader.innerHTML = response.username
|
||||
} else {
|
||||
document.querySelector('#alluserstablebody').innerHTML = ""
|
||||
loginform.classList.add("active")
|
||||
notification("Вы не можете войти потому что не являетесь администратором", "error")
|
||||
localStorage.clear()
|
||||
}
|
||||
if (response.id) {
|
||||
localStorage.setItem("userdata", JSON.stringify(response))
|
||||
}
|
||||
}
|
||||
|
||||
message.addEventListener('click', () => {
|
||||
message.classList.remove("active")
|
||||
})
|
||||
|
||||
exitbutton.addEventListener('click', () => {
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('userdata')
|
||||
loginform.classList.add('active')
|
||||
notification('Вы вышли', "success")
|
||||
})
|
||||
@@ -0,0 +1,18 @@
|
||||
const message = document.querySelector('.message')
|
||||
const messagetext = document.querySelector('.messagetext')
|
||||
|
||||
const notification = (text, type) => {
|
||||
if (type === "error") {
|
||||
message.classList.remove('success')
|
||||
message.classList.add('error')
|
||||
} else {
|
||||
message.classList.remove('error')
|
||||
message.classList.add('success')
|
||||
}
|
||||
messagetext.innerHTML = text
|
||||
message.classList.add('active')
|
||||
setTimeout(() => {
|
||||
message.classList.remove('active')
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
const host = window.location.origin
|
||||
const prefix = 'api'
|
||||
|
||||
const routes = {
|
||||
login: () => [host, prefix, 'token'].join('/'),
|
||||
user: () => [host, prefix, 'users', 'user'].join('/'),
|
||||
all_users: () => [host, prefix, 'users'].join('/'),
|
||||
access: () => [host, prefix, 'token', 'verify'].join('/'),
|
||||
adduser: () => [host, prefix, 'users', 'add'].join('/'),
|
||||
deluser: () => [host, prefix, 'users'].join('/'),
|
||||
change_password_url: () => [host, prefix, 'users', 'password'].join('/'),
|
||||
change_user: () => [host, prefix, 'users'].join('/')
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
const hashPassword = async (password) => {
|
||||
const encoder = new TextEncoder()
|
||||
const data = encoder.encode(password)
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', data)
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||
const hashHex = hashArray.map(b=>('00' + b.toString(16)).slice(-2)).join('')
|
||||
return hashHex
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
const themecheckbox = document.querySelector('#themecheckbox')
|
||||
|
||||
themecheckbox.addEventListener('click', (e) => {
|
||||
if (themecheckbox.checked) {
|
||||
document.documentElement.setAttribute('data-bs-theme', 'dark')
|
||||
localStorage.setItem('bs-theme', 'dark')
|
||||
} else {
|
||||
document.documentElement.setAttribute('data-bs-theme', 'light')
|
||||
localStorage.setItem('bs-theme', 'light')
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
var theme = localStorage.getItem('bs-theme')
|
||||
if (theme) {
|
||||
document.documentElement.setAttribute('data-bs-theme', theme)
|
||||
if (theme === 'dark') {
|
||||
themecheckbox.checked = true
|
||||
}
|
||||
}
|
||||
if (localStorage.getItem('token')) {
|
||||
fetch(routes.access(), {
|
||||
method: 'POST',
|
||||
body: `{"token": "${localStorage.getItem('token')}"}`
|
||||
}).then(data => data.json()).then(jsondata => {
|
||||
if (jsondata.valid) {
|
||||
document.querySelector('.loginform').classList.remove('active')
|
||||
get_all_users()
|
||||
} else {
|
||||
document.querySelector('.loginform').classList.add('active')
|
||||
}
|
||||
}).catch((error) => {
|
||||
notification(`Ошибка на сервере: ${error}`, "error")
|
||||
})
|
||||
usernameheader.innerHTML = JSON.parse(localStorage.getItem('userdata')).username
|
||||
} else {
|
||||
document.querySelector('.loginform').classList.add('active')
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user