unclean SPARC

This commit is contained in:
STEINNI
2025-08-27 07:03:09 +00:00
commit f308460931
430 changed files with 54426 additions and 0 deletions
@@ -0,0 +1,82 @@
<style>
.admin-access-control > header {
background: url(/app/assets/images/cards/eic_purple_banner.png);
background-position: top;
background-size: contain !important;
background-repeat: no-repeat;
background-color: #5336a3;
border-bottom: 4px solid white !important;
}
.admin-access-control .platform-status { min-width:25vw; }
.admin-access-control .platform-monitor {
display: grid;
justify-items: center;
grid-gap: var(--eicui-base-spacing-m);
}
.admin-access-control .platform-monitor .grants { text-align: center; line-height:150% }
.admin-access-control .active-users-grid .row { grid-template-columns: 1fr 2fr 3fr 2fr 110px; }
.admin-access-control .active-users-grid .actions > div {
display: inline-grid;
grid-gap: var(--eicui-base-spacing-2xs);
grid-auto-flow: column;
}
.admin-access-control .active-users-grid .dataset .cell:nth-child(2) { text-align: center; }
.admin-access-control .active-users-grid .dataset .cell:nth-child(5) { text-align: center; }
.admin-access-control .active-users-grid .dataset .cell:nth-child(6) { text-align: right; }
</style>
<article eiccard media class="admin-access-control">
<header>
<h1>Users Access Management</h1>
<h2>EIC Platform Administration</h2>
</header>
<section>
<div class="cols-2 left">
<div>
<article warning eiccard class="platform-status">
<section class="platform-monitor">Unknown state</section>
<footer>
<div class="cols-2">
<button small primary eicbutton class="start" data-trigger="onStartPlatform"><span>Free access</span></button>
<button small danger eicbutton class="stop" data-trigger="onStopPlatform"><span>Restrict access</span></button>
</div>
</footer>
</article>
<article eiccard class="banned-users">
<header>
<h1>Banned users</h1>
</header>
<section>
<div eicdatagrid class="banned-users-grid" xsmall></div>
</section>
<footer>
<div class="cols-1 right"><button eicbutton primary small disabled><span>Add</span></button></div>
</footer>
</article>
</div>
<div>
<article eiccard class="ws-users">
<header>
<div class="cols-2 right">
<h1>Active user sessions</h1>
<!--<button primary small eicbutton data-trigger="refreshUsers"><span>Refresh</span></button>-->
</div>
</header>
<section>
<div eicdatagrid class="active-users-grid" xsmall></div>
</section>
<footer>
<div class="actions cols-2">
<div></div>
<div class="cols-2">
<button eicbutton class="kick-all" danger small data-trigger="onUsersKick"><span>Kick all</span></button>
<button eicbutton class="message-all" primary small data-trigger="onUsersMessage"><span>Message all</span></button>
</div>
</div>
</footer>
</article>
</div>
</div>
</section>
</article>
@@ -0,0 +1,391 @@
class AdminAccessControlView extends EICDomContent {
constructor() {
super();
Object.assign(this, app.helpers.basicDialogs)
}
refreshRate = 1000;
DOMContentLoaded(options) {
for(let model in options.models) this[model] = options.models[model];
this.components = ui.eicfy(this.el);
this.buttonStart = this.components.find(component => component.el.classList.contains('start'));
this.buttonStop = this.components.find(component => component.el.classList.contains('stop'));
this.gridUsers = new DataGrid(this.find('.active-users-grid'), {
headers: [
{ label: 'Online', type:'markup', filter: 'list', sortable: true},
{ label: 'EU Login', type:'markup', filter: 'text', sortable: true},
{ label: 'Full name', type:'markup', filter: 'text', sortable: true},
{ label: 'Last req.', type:'markup', filter: false, sortable: true},
],
height: '60vh'
});
this.gridBannedUsers = new DataGrid(this.find('.banned-users-grid'), {
headers: [ { label: 'EU Login', type:'markup', filter: 'text'} ],
height: '40vh'
});
this.initEvents();
this.startAutoRefreshUser()
this.startAutoRefreshPlatform()
}
initEvents() {
let triggers = this.components.filter(c => ![ '', undefined ].includes( c.el.dataset.trigger) );
for(let element of triggers) {
if(typeof this[element.el.dataset.trigger] === 'function') {
element.click = this[element.el.dataset.trigger].bind(this)
}
}
}
DOMContentRemoved(){
this.stopAutoRefreshUser()
this.stopAutoRefreshPlatform()
}
DOMContentFocused(){
this.startAutoRefreshUser()
this.startAutoRefreshPlatform()
}
DOMContentBlured(){
this.stopAutoRefreshUser()
this.stopAutoRefreshPlatform()
}
startAutoRefreshPlatform(){
if(this.refreshPlatformTo) return
this.refreshPlatformTo = null
this.refreshPlatformActive = true
this.refreshPlatform()
}
stopAutoRefreshPlatform(){
if(this.refreshPlatformTo){
clearTimeout(this.refreshPlatformTo)
this.refreshPlatformTo = null
}
this.refreshPlatformActive = false
}
refreshPlatform(){
if(this.refreshPlatformTo){
clearTimeout(this.refreshPlatformTo)
this.refreshPlatformTo = null
}
this.platform.getPlatformStatus().then(this.fillPlatformState.bind(this))
if(this.refreshPlatformActive) this.refreshPlatformTo = setTimeout(this.refreshPlatform.bind(this),this.refreshRate)
}
startAutoRefreshUser(){
if(this.refreshUserTo) return
this.refreshUserTo = null
this.refreshUserActive = true
this.refreshUsers()
}
stopAutoRefreshUser(){
if(this.refreshUserTo){
clearTimeout(this.refreshUserTo)
this.refreshUserTo = null
}
this.refreshUserActive = false
}
refreshUsers(event){
if(this.refreshUserTo){
clearTimeout(this.refreshUserTo)
this.refreshUserTo = null
}
this.platform.getUsers().then(this.fillUsers.bind(this))
if(this.refreshUserActive) this.refreshUserTo = setTimeout(this.refreshUsers.bind(this), this.refreshRate)
}
fillUsers(list) {
if(!list) return
// seriously ?
const noKickRoles = ['EIC_Admin', 'EIC_Dev'] // just for display, anti-foot-shoot exists BE side
this.gridUsers.loading = false;
this.gridUsers.clear()
for(let item of list) {
let lastAjax = 3600-item.sessionExpire
lastAjax = lastAjax<60 ? `${lastAjax} sec` : `${Math.floor(lastAjax/60)} min ${lastAjax%60} sec`
let row = this.gridUsers.addRow(item.uid, [
`<span style="font-size:0">${item.busConnected ? 'Yes': 'No'}</span><span ${item.busConnected ? 'success': 'danger'} small class="icon-big-bullet"></span>`,
item.uid,
`<span title="${item.given_name + ' ' + item.family_name}">${item.given_name + ' ' + item.family_name}</span>`,
`<span style="font-size:0">${item.sessionExpire}</span><span>${lastAjax}</span>`
]);
let buttonBar = ui.create(`<div></div>`)
let btnMsg = ui.create(`<button eicbutton class="icon-coaching message" data-uid="${item.uid}" rounded primary xsmall title="send message"></button>`)
btnMsg.addEventListener('click',this.onUserMessage.bind(this))
buttonBar.appendChild(btnMsg);
let intersect = noKickRoles.filter(value => item.userRoles.includes(value));
if(intersect.length==0){
let btnKick = ui.create(`<button eicbutton class="icon-logoff kick" data-uid="${item.uid}" rounded warning xsmall title="Terminate session"></button>`)
btnKick.addEventListener('click',this.onUserKick.bind(this))
buttonBar.appendChild(btnKick);
let btnBan = ui.create(`<button eicbutton class="icon-locked ban" data-uid="${item.uid}" rounded danger xsmall title="Ban from the platform"></button>`)
btnBan.addEventListener('click',this.onUserBan.bind(this))
buttonBar.appendChild(btnBan);
}
row.querySelector('.cell.actions').appendChild(buttonBar);
}
}
fillPlatformState(data) {
let article = new Card(this.find('.platform-status'));
let alert = this.find('.platform-status > section');
if(data) {
this.fillBannedUsers(data.blockedUUIDs)
if(data.platformRestrictions) {
article.severity = 'danger';
alert.innerHTML = `<span danger xlarge class="icon-locked"></span>
<span danger><b>access restricted</b></span>
<span success><b>granted access:</b></span>
<div class="grants">
${data.platformRestrictions.allowedRoles.map(item=>('<span info xsmall eicchip title="Group ' + item + ' allowed"><label>'+item+'</label></span>')).join('')}
${data.platformRestrictions.allowedUUIDs.map(item=>('<span primary xsmall eicchip title="User ' + item + ' allowed">'+item+'</span>')).join('')}
</div>
</div>
`;
ui.eicfy(alert);
//buttonsstop stays visible to allow changing allowed roles & uuids
this.buttonStop.disabled = false;
this.buttonStart.disabled = false;
} else {
article.severity = 'success'
alert.innerHTML = `<span success xlarge class="icon-unlocked"></span>
<span success><b>no restrictions</b></span>`
this.buttonStop.disabled = false;
this.buttonStart.disabled = true;
}
} else {
article.severity = 'warning'
alert.innerHTML = `<span warning xlarge class="icon-bug"></span>
<span warning><b>status report unavailable</b></span>`
this.buttonStart.disabled = true;
this.buttonStop.disabled = true;
}
}
fillDaemonsState(list){
for(let daemon of Object.keys(list)){
let led = this.find(`.status-${daemon}`)
let tmstp = this.find(`.time-${daemon}`)
if(led){
led.innerHTML = `<span ${list[daemon] ? 'success': 'danger'} small class="icon-big-bullet"></span>`
if(list[daemon]){
tmstp.innerHTML = `${list[daemon] ? list[daemon].substr(11,8) : ''}`
ui.show(tmstp, 'inline-flex')
} else {
ui.hide(tmstp)
}
}
}
}
fillBannedUsers(list) {
if(!list) return
this.gridBannedUsers.clear()
for(let uid of list) {
let row = this.gridBannedUsers.addRow(uid, [
uid,
]);
let buttonBar = ui.create(`<div></div>`)
let btnUnban = ui.create(`<button eicbutton class="icon-unlocked unban" data-uid="${uid}" rounded primary xsmall title="Un-ban"></button>`)
btnUnban.addEventListener('click',this.onUserUnban.bind(this))
buttonBar.appendChild(btnUnban);
row.querySelector('.cell.actions').appendChild(buttonBar);
}
}
async onStopPlatform(event) {
event.preventDefault();
event.stopPropagation();
let uid = event.currentTarget.dataset.uid;
let result = await this.openDialog(
await this.loadContent(
'system/admin/platform-control/dialogs/PlatformDownDialog',
{ title: `Restricting platform access` },
{ platform: this.platform }
)
);
if(result) {
this.platform.stopPlatform(result);
}
}
async onStartPlatform(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.confirmDialog({
title: 'Releasing platform restrictions',
message: `<p>You are about to remove restrictions on the EIC platform.</p>
<p>All users will be able to connect again.</p>`,
okLabel: 'Free access',
severity: 'primary',
})
if(result) {
this.platform.startPlatform(result);
}
}
onUsersUpdate(event) { this.fillUsers(event.data.users); }
/**
* Growls a message to a user through bus
*/
async onUserMessage(event) {
event.preventDefault();
event.stopPropagation();
let uid = event.currentTarget.dataset.uid;
let result = await this.openDialog(
await this.loadContent(
'system/admin/platform-control/dialogs/PlatformUserMessageDialog',
{ title: `Warn user ${uid}` },
{ }
)
);
if(result) {
this.platform.growlOne(uid, result);
}
}
/**
* Growls a message to all users through bus
*/
async onUsersMessage(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'system/admin/platform-control/dialogs/PlatformUserMessageDialog',
{ title: `Warn user` },
{ }
)
);
if(result) {
this.platform.growlGlobal(result);
}
}
/**
* Kicks a user though bus
*/
async onUserKick(event) {
event.preventDefault();
event.stopPropagation();
let uid = event.currentTarget.dataset.uid;
let result = await this.confirmDialog({
title: 'Kick user',
message: `<p>You are about to kick user <b>${uid}</b> from the platform.</p>
<p>All of his current activities will be terminated.</p>`,
okLabel: 'Kick',
severity: 'danger',
okPromise: () => { return(this.platform.kick(uid)) }
})
if(result) {
ui.growl.append(`User ${uid} has been kicked !`, 'success', 10000)
if(!this.refreshUserActive) this.refreshUsers()
}
}
/**
* Kicks all users though bus
*/
async onUsersKick(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.confirmDialog({
title: 'Kick all users',
message: `<p>You are about to kick <b>ALL</b> users from the platform.</p>
<p>All of their current activities will be terminated.</p>`,
okLabel: 'Kick',
severity: 'danger',
okPromise: () => { return(this.platform.kickAll()) }
})
if(result) {
ui.growl.append(`All users (except Admins & Devs) have been kicked !`, 'success', 10000)
if(!this.refreshUserActive) this.refreshUsers()
}
}
/**
* Bans a user from the platform though bus
*/
async onUserBan(event) {
event.preventDefault();
event.stopPropagation();
let uid = event.currentTarget.dataset.uid;
let result = await this.confirmDialog({
title: 'Ban user',
message: `<p>You are about to <b>ban</b> user <b>${uid}</b> from the platform.</p>
<p>All of his current activities will be terminated
and he won't be able to login again.</p>`,
okLabel: 'Ban',
severity: 'danger',
okPromise: () => { return(this.platform.ban(uid)) }
})
if(result) {
ui.growl.append(`User ${uid} has been banned !`, 'success', 10000)
if(!this.refreshPlatformActive) this.refreshPlatform()
}
}
/**
* Bans a user from the platform though bus
*/
async onUserUnban(event) {
event.preventDefault();
event.stopPropagation();
let uid = event.currentTarget.dataset.uid;
let result = await this.confirmDialog({
title: 'Unban user',
message: `<p>You are about to allow <b>${uid}</b> back on the platform.</p>`,
okLabel: 'Unban',
severity: 'success',
okPromise: () => { return(this.platform.unban(uid)) }
})
if(result) {
ui.growl.append(`User ${uid} has been unbanned !`, 'success', 10000)
if(!this.refreshPlatformActive) this.refreshPlatform()
}
}
}
app.registerClass('AdminAccessControlView', AdminAccessControlView);
@@ -0,0 +1,10 @@
<style>
.config-snipet{
background-color: #000;
padding: 5px;
width: 100vh;
}
</style>
<div class="system-message-form">
<pre class="config-snipet"><code class="view-config language-json"></code></pre>
</div>
@@ -0,0 +1,27 @@
class PlatformConfigViewDialog extends EICDialogContent {
actions = [
{
label: 'ok',
onclick: this.confirm.bind(this),
severity: 'primary',
role: 'confirm'
}
]
DOMContentLoaded(options) {
ui.eicfy(this.el);
this.codeBox = this.find('.view-config')
let snipet = hljs.highlight(JSON.stringify(options.config, null, 4), { 'language':'json'} );
this.codeBox.innerHTML = snipet.value
}
confirm(event) {
event.stopPropagation();
event.preventDefault();
this.commit();
}
}
app.registerClass('PlatformConfigViewDialog', PlatformConfigViewDialog);
@@ -0,0 +1,18 @@
<style>
.system-message-form .allowed-users{ /* room for the popup-input */
min-height: calc(8*var(--base-font-size));
}
</style>
<div class="system-message-form">
<p danger>You are about to restrict the EIC platform access.</p>
<p>All users will be disconnected and unable to login anymore.</p>
<p>Only specific roles and users selected below will still have access.</p>
<div>
<label>Allowed roles:</label>
<select eicselect name="allowedRoles" multiple></select>
</div>
<div class="allowed-users">
<label>Allowed EU-logins:</label>
<select eicselect name="allowedUUIDs" editable data-type="array" class="" placeholder="Type EUlogin-ID and press enter" />
</div>
</div>
@@ -0,0 +1,72 @@
class PlatformDownDialog extends EICDialogContent {
actions = [
{
label: 'cancel',
onclick: this.cancel.bind(this),
severity: 'secondary',
role: 'cancel'
},
{
label: 'Restrict access',
onclick: this.confirm.bind(this),
severity: 'danger',
role: 'confirm'
}
]
DOMContentLoaded(options) {
let components = ui.eicfy(this.el)
this.RolesSelect = components.find(component => component.el.name == 'allowedRoles')
this.UUIDsSelect = components.find(component => component.el.name == 'allowedUUIDs')
options.platform.getPlatformStatus().then(this.fillSelects.bind(this))
this.form = new Form(this.el);
}
fillSelects(data){
let allowedRoles = []
let allowedUUIDs = []
if(data.platformRestrictions && Array.isArray(data.platformRestrictions.allowedRoles)) {
allowedRoles = data.platformRestrictions.allowedRoles
}
if(data.platformRestrictions && Array.isArray(data.platformRestrictions.allowedUUIDs)) {
allowedUUIDs = data.platformRestrictions.allowedUUIDs
}
if(allowedRoles.indexOf('EIC_Dev')<0) allowedRoles.push('EIC_Dev') // Voluntarily hard-coded, other securities also BE-side
// Would be cool, but css issues & currently can't avoid getting the group item in the values.
// let roleOptions = app.meta.toOptionsRecurse(app.meta.getCollection('user-roles'), allowedRoles, true)
// this.RolesSelect.el.innerHTML = roleOptions ;
let RolesChildren = app.meta.getCollection('user-roles').reduce((acc, item)=>{ acc = [...acc, ...item.children]; return(acc)},[])
let rolesOptions = app.meta.toOptions(RolesChildren, allowedRoles)
rolesOptions.forEach(item => this.RolesSelect.el.append(item))
//having several options selected (in a multiple) doesn't work when appending, but works on setting the value as an Array
this.RolesSelect.value = allowedRoles
this.UUIDsSelect.value = allowedUUIDs
}
cancel(event){
event.stopPropagation()
event.preventDefault()
this.RolesSelect.closeCatalog()
super.cancel(event)
}
confirm(event) {
event.stopPropagation();
event.preventDefault();
this.RolesSelect.closeCatalog()
this.actions.forEach(item=> {item.button.loading=true; item.button.disabled=true} )
if(this.form.validate()) {
this.commit(this.form.value);
}
}
}
app.registerClass('PlatformDownDialog', PlatformDownDialog);
@@ -0,0 +1,32 @@
<div class="system-message-form">
<label>Please provide the message you want to send:</label>
<div>
<label>Predefined messages:</label>
<select eicselect name="predefined"></select>
</div>
<div class="cols-2">
<div>
<label>severity:</label>
<select eicselect name="growlSeverity">
<option value="secondary">secondary</option>
<option value="primary">primary</option>
<option value="danger">danger</option>
<option value="success">success</option>
<option value="warning">warning</option>
<option value="info">info</option>
</select>
</div>
<div>
<label>duration:</label>
<input eicinput type="number" name="growlTime" value="5" />
</div>
</div>
<div>
<label>message:</label>
<textarea eictextarea name="growlMessage" data-path="" class="required" rows="6"></textarea>
</div>
</div>
@@ -0,0 +1,58 @@
class PlatformUserMessageDialog extends EICDialogContent {
actions = [
{
label: 'cancel',
onclick: this.cancel.bind(this),
severity: 'secondary',
role: 'cancel'
},
{
label: 'send',
onclick: this.confirm.bind(this),
severity: 'primary',
role: 'confirm'
}
]
DOMContentLoaded(options) {
let components = ui.eicfy(this.el);
this.messagesSelect = components.find(component => component.el.name == 'predefined')
this.growlSeverity = components.find(component => component.el.name == 'growlSeverity')
this.growlTime = components.find(component => component.el.name == 'growlTime')
this.growlMessage = components.find(component => component.el.name == 'growlMessage')
let messagesSelectOptions = app.meta.toOptions(app.meta.getCollection('user-messages'),[],true)
messagesSelectOptions.forEach(item => this.messagesSelect.el.append(item))
this.messagesSelect.change(this.onMessageChange.bind(this));
this.form = new Form(this.el);
}
confirm(event) {
event.stopPropagation();
event.preventDefault();
this.actions.forEach(item=> {item.button.loading=true; item.button.disabled=true} )
if(this.form.validate()) {
this.commit(this.form.value);
} else {
this.actions.forEach(item=> {item.button.loading=false; item.button.disabled=false} )
}
}
onMessageChange(event) {
event.preventDefault();
event.stopPropagation();
if(this.messagesSelect.value) {
let msgData = app.meta.getCollection('user-messages').find(item=>item.id==this.messagesSelect.value)
this.growlSeverity.value = msgData.severity
this.growlTime.value = msgData.duration
this.growlMessage.value = msgData.message.join('\n')
} else {
this.growlSeverity.value = 'secondary'
this.growlTime.value = 5
this.growlMessage.value = ''
}
}
}
app.registerClass('PlatformUserMessageDialog', PlatformUserMessageDialog);