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
+42
View File
@@ -0,0 +1,42 @@
<article eiccard class="fasttracks dashboard">
<header>
<h1>Bypass</h1>
<h2>Global monitoring</h2>
</header>
<section>
<div class="tabs-extended">
<section>
<menu eictab>
<li>Tokens</li>
<li>History</li>
<li class="settings">Settings</li>
</menu>
</section>
<!--
<section>
<button eicbutton rounded basic primary small icon="icon-cog" title="Manage settings"></button>
</section>-->
</div>
<article eiccard class="tokens content">
<section>
<div class="cols-2 right middle">
<div class="metrics">
<div class="assigned-past"><span>0</span><label>assigned <span class="year"></span></label></div>
<div class="assigned-current"><span>0</span><label>assigned <span class="year"></span></label></div>
<div class="consumed-past"><span success>0</span><label success>consumed <span class="year"></span></label></div>
<div class="consumed-current"><span success>0</span><label success>consumed <span class="year"></span></label></div>
</div>
<button eicbutton primary class="grant-token">Grant a token</button>
</div>
<div eicdatagrid class="global-granted-tokens" footer="hidden"></div>
</section>
</article>
<article eiccard class="history content">
<section>
<div eicdatagrid class="history-tokens"></div>
</section>
</article>
<article eiccard class="config content"></article>
</section>
</article>
+365
View File
@@ -0,0 +1,365 @@
class BypassAdminDashboard extends EICDomContent {
DOMContentLoaded(options) {
ui.eicfy(this.el);
this.profile = options.profile;
for(let model in options.models) this[model] = options.models[model];
this.tabs = new Tab();
this.tabs.addTabs(this.findAll('.tabs-extended menu li'), this.findAll('.content'))
this.gridTokens = new DataGrid(this.find('.global-granted-tokens'), {
headers: [
{ label: 'PIC', type: 'markup', filter: 'text', sortable: true},
{ label: 'Company name', filter: 'text', sortable: true},
{ label: 'Token granted',type: 'date', filter: 'text', sortable: true},
{ label: 'Scheme', filter: 'list', sortable: true},
{ label: 'Entity', filter: 'text', sortable: true},
{ label: 'Status', filter: 'list', sortable: true},
{ label: 'Project', filter: 'text', sortable: true},
{ label: 'Short proposal submission', type: 'date', filter: 'text', sortable: true}
],
height: 'calc(100vh - 472px)',
actions: []
});
this.btTokenDl = new Button(null, {icon: 'icon-download', severity: 'secondary', rounded: true, size: 'small', hint: 'Download complete list as CSV'})
this.btTokenDl.addEventListener('click', this.downloadTokensList.bind(this));
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenDl.el);
this.btTokenRefresh = new Button(null, {icon: 'icon-refresh', severity: 'primary', rounded: true, size: 'small', hint: 'Refresh the list'})
this.btTokenRefresh.addEventListener('click', this.refreshTokensList.bind(this));
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenRefresh.el);
this.gridTokens.onRowFiltered = this.refreshMetrics.bind(this);
this.gridHistory = new DataGrid(this.find('.history-tokens'), {
headers: [
{ label: 'Date', type: 'dateTime', filter: 'text', sortable: true},
{ label: 'PIC', filter: 'text', sortable: true},
{ label: 'Company name', filter: 'text', sortable: true},
{ label: 'Action', filter: 'list', sortable: true},
{ label: 'Managed by', filter: 'text', sortable: true},
],
height: 'calc(100vh - 420px)',
actions: []
});
let btGrant = this.find('.grant-token');
if(this.company.hasPrivilege('grant')) {
btGrant.addEventListener('click', this.onTokenGrant.bind(this));
} else {
ui.hide(btGrant);
}
if(this.tokens.hasPrivilege('list')) { this.refreshTokensList(); }
if(this.users) {
this.loadContent(
'projects/bypass/BypassAdminManagementContent',
{ title: 'Accelerator configuration', subtitle: 'Users access & token management' },
{ profile: this.profile, models: { "users": this.users, "tokens": this.tokens } }
).then(view => this.setupUserManagement(view));
} else {
ui.hide(this.find('li.settings'));
}
}
setupUserManagement(view) {
this.userManagement = view;
this.find('.content.config').append(view.el);
}
fill() {
let year = new Date().getFullYear();
let awardedPast = this.tokens.awardedTokens(year - 1);
awardedPast.forEach(item => item.ref = 'assigned-past');
let awardedCurrent = this.tokens.awardedTokens(year);
awardedCurrent.forEach(item => item.ref = 'assigned-current');
let consumedPast = this.tokens.consumedTokens(year - 1);
consumedPast.forEach(item => item.ref = 'consumed-past');
let consumedCurrent = this.tokens.consumedTokens(year);
consumedCurrent.forEach(item => item.ref = 'consumed-current');
let history = this.tokens.history();
this.find('.metrics .assigned-current .year').innerText = year;
this.find('.metrics .consumed-current .year').innerText = year;
this.find('.metrics .assigned-past .year').innerText = year - 1;
this.find('.metrics .consumed-past .year').innerText = year - 1;
this.gridHistory.loading = false;
this.gridTokens.loading = false;
this.btTokenRefresh.loading = false;
this.gridTokens.clear();
let awarded = awardedCurrent.concat(awardedPast);
awarded.sort((a,b) => a.itemData.granted < b.itemData.granted ? 1: -1);
for (const item of awarded) {
let priority = '';
switch(item.itemData.status) {
case 'consumed':
priority = 'success';
break;
}
let row = this.gridTokens.addRow(item.itemData.id, [
`<a href="#" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" title="View token details">${item.itemData.enterprise.pic}</a>`,
item.itemData.enterprise.legalname,
item.itemData.granted,
app.meta.toString('accelerator-tracks', item.itemData.track),
app.meta.toString('accelerator-tracks', item.itemData.domain),
`<span ${priority}>${item.itemData.status}</span>`,
item.itemData.project ? item.itemData.project.acronym: '',
item.itemData.project ? item.itemData.project.submissionDate: ''
], true);
let tokenLink = row.querySelector('.cell:nth-child(2) a')
tokenLink.addEventListener('click', this.onTokenView.bind(this));
row.classList.add(item.ref);
// checking if token can be revoked (meaning status is allocated and user has privilege for that action)
if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (!item.itemData.isAssignedToShortProposal)) {
let buttonBar = ui.create(`<div class="cols-2"></div>`)
let revokeButton = ui.create(`<button eicbutton xsmall danger title="revoke this token" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}" >Revoke</button>`);
revokeButton.addEventListener('click', this.onTokenRevoke.bind(this));
buttonBar.appendChild(revokeButton);
let useButton = ui.create(`<button eicbutton xsmall info title="Activate the coaching" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}">Consume</button>`);
useButton.addEventListener('click', this.onTokenConsume.bind(this));
buttonBar.appendChild(useButton);
row.querySelector('.cell.actions').appendChild(buttonBar);
} else if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (item.itemData.isAssignedToShortProposal)){
row.querySelector('.cell.actions').appendChild(ui.create(`<span>Consuming...</span>`));
}
}
this.gridTokens.updateFilters();
// filling the history list
this.gridHistory.clear();
for (const item of history) {
let priority = ''
switch(item.action) {
case 'revoked': priority = 'danger'; break;
case 'consumed': priority = 'success'; break;
}
let row = this.gridHistory.addRow(item.date, [
item.date,
item.pic,
item.legalname,
`<span ${priority}>${item.action}</span>`,
`${item.actor.firstname ? item.actor.firstname[0] + '. ': ''}${item.actor.lastname}`
], true);
}
this.gridHistory.updateFilters();
this.refreshMetrics();
}
refreshMetrics() {
let rows = Array.from(this.gridTokens.filteredRows);
let assignedPast = rows.filter(item => item.classList.contains('assigned-past'));
let assignedCurrent = rows.filter(item => item.classList.contains('assigned-current'));
let consumedPast = rows.filter(item => item.classList.contains('consumed-past'));
let consumedCurrent = rows.filter(item => item.classList.contains('consumed-current'));
this.find('.metrics .assigned-past span').innerText = assignedPast.length;
this.find('.metrics .assigned-current span').innerText = assignedCurrent.length;
this.find('.metrics .consumed-past span').innerText = consumedPast.length;
this.find('.metrics .consumed-current span').innerText = consumedCurrent.length;
}
refreshTokensList(event) {
if(event) {
event.preventDefault();
event.stopPropagation();
}
this.btTokenRefresh.loading = true;
this.gridTokens.loading = true;
this.gridHistory.loading = true;
this.tokens.list().then(this.fill.bind(this));
}
downloadTokensList(event) {
if(event) {
event.preventDefault();
event.stopPropagation();
}
this.btTokenDl.loading = true;
this.tokens.export({filter: { includePersons: true }}).then(this.buildCSV.bind(this));
}
buildCSV(payload) {
let options = {
headers: [
'PIC',
'Organisation Name',
'Granted on',
'Scheme',
'Entity',
'Program',
'Status',
'Project',
'Submission date',
'Contact name',
'Contact gender',
'Contact position',
'Contact email',
'Contact phone',
'SPOC name',
'SPOC email',
],
filename: 'EIC-Bypass',
delimiter: ';'
}
let data = [];
for(let token of payload.tokens) {
let row = [
token.enterprise.pic,
token.enterprise.legalname,
token.granted,
app.meta.toString('accelerator-tracks', token.track),
app.meta.toString('accelerator-tracks', token.domain),
token.program || "",
token.history[0].status,
token.project ? token.project.acronym: '',
token.project ? token.project.submissiondate: '',
token.shortProposal ? token.shortProposal.contact.lastname + ' ' + token.shortProposal.contact.firstname: '',
token.shortProposal ? token.shortProposal.contact.gender: '',
token.shortProposal ? token.shortProposal.contact.position: '',
token.shortProposal ? token.shortProposal.contact.email: '',
token.shortProposal ? token.shortProposal.contact.phone: '',
token.spoc ? token.spoc.lastname + ' ' + token.spoc.firstname: '',
token.spoc ? token.spoc.email: '',
]
data.push(row);
}
app.helpers.translator.toCSV( data, options );
this.btTokenDl.loading = false;
}
async onTokenGrant(event) {
event.preventDefault();
event.stopPropagation();
let grantResult = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantDialog',
{ title: 'Grant a token' },
{ models: { 'company': this.company, 'tokens': this.tokens }, profile: this.profile }
)
);
if(grantResult) {
ui.growl.append('Token granted', 'success');
let ConsumeResult = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
{ title: 'Activate the coaching for...', subtitle: `${this.company.itemData.legalname} (${this.company.itemData.pic})` },
{
models: { users: this.users, token: this.tokens },
profile: this.profile,
tokenId: this.company.itemData.token.id,
pic: this.company.itemData.pic,
legalname: this.company.itemData.legalname,
wizard: true,
}
)
);
}
this.refreshTokensList();
}
async onTokenRevoke(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenRevokeDialog',
{ title: 'Revoke a token for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
{
model: this.company,
profile: this.profile,
pic: event.currentTarget.dataset.pic,
track: event.currentTarget.dataset.track,
domain: event.currentTarget.dataset.domain,
}
)
);
if(result) {
ui.growl.append('Token revoked', 'success');
this.refreshTokensList();
}
}
async onTokenView(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
{ title: 'Token granted to ', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
{
models: { users: this.users, token: this.tokens },
mode: 'read',
profile: this.profile,
tokenId: event.currentTarget.dataset.id,
pic: event.currentTarget.dataset.pic,
legalname: event.currentTarget.dataset.legalname,
wizard: false,
}
)
);
if(result) {
ui.growl.append('Token consumed', 'success');
this.refreshTokensList();
}
}
async onTokenConsume(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
{ title: 'Activate the coaching for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
{
models: { users: this.users, token: this.tokens },
profile: this.profile,
tokenId: event.currentTarget.dataset.id,
pic: event.currentTarget.dataset.pic,
legalname: event.currentTarget.dataset.legalname,
wizard: false,
}
)
);
if(result) {
ui.growl.append('Token consumed', 'success');
this.refreshTokensList();
}
}
}
app.registerClass('BypassAdminDashboard', BypassAdminDashboard);
@@ -0,0 +1,42 @@
<div>
<section>
<article eiccard collapsable>
<header>
<h1>Users Access</h1>
<h2></h2>
</header>
<section>
<div eicdatagrid class="users-list"></div>
</section>
<footer>
<div class="cols-1 right"><button eicbutton primary small class="user-add">add user...</button></div>
</footer>
</article>
</section>
<section class="cols-2">
<article eiccard collapsable>
<header>
<h1>EIC Fast Track Settings</h1>
<h2>Yearly token assignment per domain</h2>
</header>
<section>
<div eicdatagrid class="kic-domains-list" footer="hidden"></div>
</section>
<footer>
<div class="cols-1 right"><button class="kic-edit" eicbutton primary small>update...</button></div>
</footer>
</article>
<article eiccard collapsable>
<header>
<h1>EIC Plug In Settings</h1>
<h2>Yearly token assignment per domain</h2>
</header>
<section>
<div eicdatagrid class="plugin-domains-list" footer="hidden"></div>
</section>
<footer>
<div class="cols-1 right"><button class="plugin-edit" eicbutton primary small>update...</button></div>
</footer>
</article>
</section>
</div>
@@ -0,0 +1,104 @@
class BypassAdminManagementContent extends BypassBaseManagementContent {
DOMContentLoaded(options) {
super.DOMContentLoaded(options);
let components = ui.eicfy(this.el);
this.gridKics = new DataGrid(this.find('.kic-domains-list'), {
headers: [ { label: 'Thematic'}, { label: 'Tokens' }, { label: 'Remaining' } ],
height: '320px'
});
this.gridPlugins = new DataGrid(this.find('.plugin-domains-list'), {
headers: [ { label: 'Country'}, { label: 'Tokens'}, { label: 'Remaining' } ],
height: '320px'
});
this.pluginEditButton = components.find(component => component.el.classList.contains('plugin-edit'));
if(this.tokens.hasPrivilege('setSettings')) {
this.pluginEditButton.addEventListener('click', this.onPluginEdit.bind(this));
} else {
ui.hide(this.pluginEditButton);
}
this.kicEditButton = components.find(component => component.el.classList.contains('kic-edit'));
if(this.tokens.hasPrivilege('setSettings')) {
this.kicEditButton.addEventListener('click', this.onKicEdit.bind(this));
} else {
ui.hide(this.kicEditButton);
}
this.gridKics.loading = true;
this.tokens.getSettings('d7xAg5kIhQYeDMB1H6eXnBg')
.then(this.fillKics.bind(this));
this.gridPlugins.loading = true;
this.tokens.getSettings('daTai94ymStyRbQWybH3eDw')
.then(this.fillPlugins.bind(this));
}
fillKics(payload) {
this.gridKics.clear();
this.gridKics.loading = false;
for(let item of payload.domains) {
this.gridKics.addRow(item.domain, [app.meta.toString('accelerator-tracks', item.domain), item.tokens, item.remaining])
}
this.gridKics.sort(1, 'asc');
}
fillPlugins(payload) {
this.gridPlugins.clear();
this.gridPlugins.loading = false;
for(let item of payload.domains) {
this.gridPlugins.addRow(item.domain, [app.meta.toString('accelerator-tracks', item.domain), item.tokens, item.remaining])
}
this.gridPlugins.sort(1, 'asc');
}
async onKicEdit(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTrackSetupDialog',
{ title: 'Fast-Track Yearly token attribution', subtitle: 'Set up the yearly token attribution per domain' },
{ model: this.tokens, track: 'd7xAg5kIhQYeDMB1H6eXnBg' }
)
);
if(result) {
ui.growl.append('Settings updated', 'success');
this.gridKics.loading = true;
this.tokens.getSettings('d7xAg5kIhQYeDMB1H6eXnBg')
.then(this.fillKics.bind(this));
}
}
async onPluginEdit(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTrackSetupDialog',
{ title: 'Plugins Yearly token attribution', subtitle: 'Set up the yearly token attribution per country' },
{ model: this.tokens, track: 'daTai94ymStyRbQWybH3eDw' }
)
);
if(result) {
ui.growl.append('Settings updated', 'success');
this.gridPlugins.loading = true;
this.tokens.getSettings('daTai94ymStyRbQWybH3eDw')
.then(this.fillPlugins.bind(this));
}
}
}
app.registerClass('BypassAdminManagementContent', BypassAdminManagementContent);
@@ -0,0 +1,117 @@
class BypassBaseManagementContent extends EICDomContent {
constructor() {
super();
Object.assign(this, app.helpers.basicDialogs)
}
DOMContentLoaded(options) {
this.profile = options.profile;
for(let model in options.models) this[model] = options.models[model];
this.users.getRoles(); // We'll need them later, but quite slow
this.gridUsers = new DataGrid(this.find('.users-list'), {
headers: [
{ label: 'Login', filter: 'text', sortable: true},
{ label: 'Email', filter: 'text', sortable: true},
{ label: 'Fullname', filter: 'text', sortable: true},
{ label: 'Role', filter: 'list', sortable: true},
{ label: 'Scheme', filter: 'list', sortable: true},
{ label: 'Domain', filter: 'list', sortable: true},
],
height: 'calc(100vh - 620px)',
actions: []
});
this.btUsersRefresh = new Button(null, {icon: 'icon-refresh', severity: 'primary', rounded: true, size: 'small', hint: 'Refresh the list'})
this.btUsersRefresh.addEventListener('click', this.refreshUsersList.bind(this));
this.gridUsers.el.querySelector('header .cell.actions').append(this.btUsersRefresh.el);
this.userAddButton = this.find('.user-add');
if(this.users.hasPrivilege('create')) {
this.userAddButton.addEventListener('click', this.onUserCreate.bind(this));
} else {
ui.hide(this.userAddButton);
}
this.gridUsers.loading = true;
this.users.list().then(this.fillUsers.bind(this));
}
fillUsers() {
this.gridUsers.clear();
this.gridUsers.loading = false;
this.btUsersRefresh.loading = false;
for (const item of this.users.collection) {
let row = this.gridUsers.addRow(item.uid, [
item.uid,
item.email,
`${item.firstname} ${item.lastname}`,
item.BProle,
item.track.label,
item.domain.label,
], true);
if(this.users.hasPrivilege('revoke') && (item.uid != app.User.identity.uuid )) {
let button = ui.create(`<button eicbutton xsmall danger title="revoke this user" data-uid="${item.uid}">revoke</button>`);
button.addEventListener('click', this.onUserRevoke.bind(this));
row.querySelector('.cell.actions').appendChild(button);
}
}
this.gridUsers.updateFilters();
}
refreshUsersList(event) {
if(event) {
event.preventDefault();
event.stopPropagation();
}
this.btUsersRefresh.loading = true;
this.gridUsers.loading = true;
this.users.list().then(this.fillUsers.bind(this));
}
async onUserCreate(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(await this.loadContent('projects/bypass/dialogs/BypassUserProfileDialog',{
title: 'Add a user',
subtitle: 'Please first enter the user UUID then define role and scopes of activity'
}, {
model: this.users,
mode: 'create',
profile: this.profile
}));
if(result) {
ui.growl.append('User added', 'success');
this.refreshUsersList();
}
}
async onUserRevoke(event) {
event.preventDefault();
event.stopPropagation();
let uid = event.currentTarget.dataset.uid;
let user = this.users.getUid(uid);
let result = await this.confirmDialog({
title: 'Revoke a user',
message: `Please be aware that you are about to revoke the user "<strong>${user.firstname} ${user.lastname}</strong>"<br>
This is permanent !<br>
Are you positive ?`,
okLabel: 'Revoke user',
okPromise:() => { return(this.users.revoke(uid)) },
})
if(result) {
ui.growl.append('User revoked', 'success');
this.refreshUsersList();
}
}
}
app.registerClass('BypassBaseManagementContent', BypassBaseManagementContent);
+43
View File
@@ -0,0 +1,43 @@
<article eiccard class="fasttracks dashboard">
<header>
<h1>EIC Fast Track</h1>
<h2>Monitoring for entity/programme <b info>${app.meta.toString('accelerator-tracks', profile.domain)}</b></h2>
</header>
<section>
<div class="tabs-extended">
<section>
<menu eictab>
<li>Tokens</li>
<li>History</li>
<li class="settings">Settings</li>
</menu>
</section>
</div>
<article eiccard class="tokens content">
<section>
<div class="cols-2 right middle">
<div class="metrics">
<div class="available"><span>0</span><label>available</label></div>
<div class="assigned-past"><span>0</span><label>assigned <span class="year"></span></label></div>
<div class="assigned-current"><span>0</span><label>assigned <span class="year"></span></label></div>
<div class="consumed-past"><span success>0</span><label success>consumed <span class="year"></span></label></div>
<div class="consumed-current"><span success>0</span><label success>consumed <span class="year"></span></label></div>
</div>
<button eicbutton primary class="grant-token">Grant a token</button>
</div>
<div eicdatagrid class="granted-tokens" footer="hidden"></div>
</section>
</article>
<article eiccard class="history content">
<section>
<div eicdatagrid class="history-tokens"></div>
</section>
</article>
<article eiccard class="config content">
<section>
<div eicdatagrid class="users" footer="hidden"></div>
</section>
</article>
</section>
</article>
+359
View File
@@ -0,0 +1,359 @@
class BypassKICDashboard extends EICDomContent {
DOMContentLoaded(options) {
ui.eicfy(this.el);
this.profile = options.profile;
for(let model in options.models) this[model] = options.models[model];
this.tabs = new Tab();
this.tabs.addTabs(this.findAll('menu li'), this.findAll('.content'))
this.gridTokens = new DataGrid(this.find('.granted-tokens'), {
headers: [
{ label: 'PIC', type:'markup', filter: 'text', sortable: true},
{ label: 'Company name', filter: 'text', sortable: true},
{ label: 'Entity/programme', filter: 'list', sortable: true},
{ label: 'Token granted', type: 'date', filter: 'text', sortable: true},
{ label: 'Status', filter: 'list', sortable: true},
{ label: 'Project', filter: 'text', sortable: true},
{ label: 'Short proposal submission', type: 'date', filter: 'text', sortable: true}
],
height: 'calc(100vh - 472px)',
actions: []
});
this.btTokenDl = new Button(null, {icon: 'icon-download', severity: 'secondary', rounded: true, size: 'small', hint: 'Download complete list as CSV'})
this.btTokenDl.addEventListener('click', this.downloadTokensList.bind(this));
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenDl.el);
this.btTokenRefresh = new Button(null, {icon: 'icon-refresh', severity: 'primary', rounded: true, size: 'small', hint: 'Refresh the list'})
this.btTokenRefresh.addEventListener('click', this.refreshTokensList.bind(this));
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenRefresh.el);
this.gridTokens.onRowFiltered = this.refreshMetrics.bind(this);
this.gridHistory = new DataGrid(this.find('.history-tokens'), {
headers: [
{ label: 'Date', type: 'dateTime', filter: 'text', sortable: true},
{ label: 'PIC', filter: 'text', sortable: true},
{ label: 'Company name', filter: 'text', sortable: true},
{ label: 'Action', filter: 'list', sortable: true},
{ label: 'Managed by', filter: 'text', sortable: true},
],
height: 'calc(100vh - 420px)',
actions: []
});
let btGrant = this.find('.grant-token');
if(this.company.hasPrivilege('grant')) {
btGrant.addEventListener('click', this.onTokenGrant.bind(this));
} else {
ui.hide(btGrant);
}
if(this.tokens.hasPrivilege('list')) { this.refreshTokensList(); }
if(this.users) {
this.loadContent(
'projects/bypass/BypassKICManagementContent',
{ title: 'Accelerator configuration', subtitle: 'Users access & token management' },
{ profile: this.profile, models: { "users": this.users } }
).then(view => this.setupUserManagement(view))
} else {
ui.hide(this.find('li.settings'));
}
}
setupUserManagement(view) {
this.userManagement = view;
this.find('.content.config').append(view.el);
}
fill() {
let year = new Date().getFullYear();
let awardedPast = this.tokens.awardedTokens(year - 1);
awardedPast.forEach(item => item.ref = 'assigned-past');
let awardedCurrent = this.tokens.awardedTokens(year);
awardedCurrent.forEach(item => item.ref = 'assigned-current');
let consumedPast = this.tokens.consumedTokens(year - 1);
consumedPast.forEach(item => item.ref = 'consumed-past');
let consumedCurrent = this.tokens.consumedTokens(year);
consumedCurrent.forEach(item => item.ref = 'consumed-current');
let history = this.tokens.history();
this.find('.metrics .available span').innerText = this.tokens.freeTokens - awardedCurrent.length;
this.find('.metrics .assigned-current .year').innerText = year;
this.find('.metrics .consumed-current .year').innerText = year;
this.find('.metrics .assigned-past .year').innerText = year - 1;
this.find('.metrics .consumed-past .year').innerText = year - 1;
this.gridHistory.loading = false;
this.gridTokens.loading = false;
this.btTokenRefresh.loading = false;
this.gridTokens.clear();
let awarded = awardedCurrent.concat(awardedPast);
awarded.sort((a,b) => a.itemData.granted < b.itemData.granted ? 1: -1);
for (const item of awarded) {
let priority = ''
switch(item.itemData.status) {
case 'consumed':
priority = 'success';
break;
}
let row = this.gridTokens.addRow(item.itemData.id, [
`<a href="#" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" title="View token details">${item.itemData.enterprise.pic}</a>`,
item.itemData.enterprise.legalname,
app.meta.toString('accelerator-tracks', item.itemData.domain),
item.itemData.granted,
`<span ${priority}>${item.itemData.status}</span>`,
item.itemData.project ? item.itemData.project.acronym: '',
item.itemData.project ? item.itemData.project.submissionDate: ''
], true);
let tokenLink = row.querySelector('.cell:nth-child(2) a')
tokenLink.addEventListener('click', this.onTokenView.bind(this));
row.classList.add(item.ref);
// checking if token can be revoked (meaning status is allocated and user has privilege for that action)
if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (!item.itemData.isAssignedToShortProposal)) {
let buttonBar = ui.create(`<div class="cols-2"></div>`)
let revokeButton = ui.create(`<button eicbutton xsmall danger title="revoke this token" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}" >Revoke</button>`);
revokeButton.addEventListener('click', this.onTokenRevoke.bind(this));
buttonBar.appendChild(revokeButton);
let useButton = ui.create(`<button eicbutton xsmall info title="Activate the coaching" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}" >Consume</button>`);
useButton.addEventListener('click', this.onTokenConsume.bind(this));
buttonBar.appendChild(useButton);
row.querySelector('.cell.actions').appendChild(buttonBar);
} else if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (item.itemData.isAssignedToShortProposal)){
row.querySelector('.cell.actions').appendChild(ui.create(`<span>Consuming...</span>`));
}
}
this.gridTokens.updateFilters();
// filling the history list
this.gridHistory.clear();
for (const item of history) {
let priority = ''
switch(item.action) {
case 'revoked': priority = 'danger'; break;
case 'consumed': priority = 'success'; break;
}
let row = this.gridHistory.addRow(item.date, [
item.date,
item.pic,
item.legalname,
`<span ${priority}>${item.action}</span>`,
`${item.actor.firstname ? item.actor.firstname[0] + '. ': ''}${item.actor.lastname}`
], true);
}
this.gridHistory.updateFilters();
this.refreshMetrics();
}
refreshMetrics() {
let rows = Array.from(this.gridTokens.filteredRows);
let assignedPast = rows.filter(item => item.classList.contains('assigned-past'));
let assignedCurrent = rows.filter(item => item.classList.contains('assigned-current'));
let consumedPast = rows.filter(item => item.classList.contains('consumed-past'));
let consumedCurrent = rows.filter(item => item.classList.contains('consumed-current'));
this.find('.metrics .assigned-past span').innerText = assignedPast.length;
this.find('.metrics .assigned-current span').innerText = assignedCurrent.length;
this.find('.metrics .consumed-past span').innerText = consumedPast.length;
this.find('.metrics .consumed-current span').innerText = consumedCurrent.length;
}
refreshTokensList(event) {
if(event) {
event.preventDefault();
event.stopPropagation();
}
this.btTokenRefresh.loading = true;
this.gridTokens.loading = true;
this.gridHistory.loading = true;
this.tokens.list().then(this.fill.bind(this));
}
downloadTokensList(event) {
if(event) {
event.preventDefault();
event.stopPropagation();
}
this.btTokenDl.loading = true;
this.tokens.export({filter: { includePersons: true }}).then(this.buildCSV.bind(this));
}
buildCSV(payload) {
let options = {
headers: [
'PIC',
'Organisation Name',
'Granted on',
'Scheme',
'Entity',
'Status',
'Project',
'Submission date',
'Contact name',
'Contact gender',
'Contact position',
'Contact email',
'Contact phone',
'SPOC name',
'SPOC email',
],
filename: 'EIC-Bypass',
delimiter: ';'
}
let data = [];
for(let token of payload.tokens) {
let row = [
token.enterprise.pic,
token.enterprise.legalname,
token.granted,
app.meta.toString('accelerator-tracks', token.track),
app.meta.toString('accelerator-tracks', token.domain),
token.history[0].status,
token.project ? token.project.acronym: '',
token.project ? token.project.submissiondate: '',
token.shortProposal ? token.shortProposal.contact.lastname + ' ' + token.shortProposal.contact.firstname: '',
token.shortProposal ? token.shortProposal.contact.gender: '',
token.shortProposal ? token.shortProposal.contact.position: '',
token.shortProposal ? token.shortProposal.contact.email: '',
token.shortProposal ? token.shortProposal.contact.phone: '',
token.spoc ? token.spoc.lastname + ' ' + token.spoc.firstname: '',
token.spoc ? token.spoc.email: '',
]
data.push(row);
}
app.helpers.translator.toCSV( data, options );
this.btTokenDl.loading = false;
}
async onTokenGrant(event) {
event.preventDefault();
event.stopPropagation();
let grantResult = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantDialog',
{ title: 'Grant a token' },
{ models: { 'company': this.company, 'tokens': this.tokens }, profile: this.profile }
)
);
if(grantResult) {
ui.growl.append('Token granted', 'success');
let ConsumeResult = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantPropaDialog',
{ title: 'Activate the coaching for...', subtitle: `${this.company.itemData.legalname} (${this.company.itemData.pic})` },
{
models: { users: this.users, token: this.tokens },
profile: this.profile,
tokenId: this.company.itemData.token.id,
pic: this.company.pic,
legalname: this.company.itemData.legalname,
wizard: true,
}
)
);
}
this.refreshTokensList();
}
async onTokenRevoke(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenRevokeDialog',
{ title: 'Revoke a token for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
{
model: this.company,
profile: this.profile,
pic: event.currentTarget.dataset.pic,
track: event.currentTarget.dataset.track,
domain: event.currentTarget.dataset.domain,
}
)
);
if(result) {
ui.growl.append('Token revoked', 'success');
this.refreshTokensList();
}
}
async onTokenView(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
{ title: 'Token granted to ', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
{
models: { users: this.users, token: this.tokens },
mode: 'read',
profile: this.profile,
tokenId: event.currentTarget.dataset.id,
pic: event.currentTarget.dataset.pic,
legalname: event.currentTarget.dataset.legalname,
wizard: false,
}
)
);
if(result) {
ui.growl.append('Token consumed', 'success');
this.refreshTokensList();
}
}
async onTokenConsume(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
{ title: 'Activate the coaching for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
{
models: { users: this.users, token: this.tokens },
profile: this.profile,
tokenId: event.currentTarget.dataset.id,
pic: event.currentTarget.dataset.pic,
legalname: event.currentTarget.dataset.legalname,
wizard: false,
}
)
);
if(result) {
ui.growl.append('Token consumed', 'success');
this.refreshTokensList();
}
}
}
app.registerClass('BypassKICDashboard', BypassKICDashboard);
@@ -0,0 +1,16 @@
<div>
<section>
<article eiccard collapsable>
<header>
<h1>Users Access</h1>
<h2></h2>
</header>
<section>
<div eicdatagrid class="users-list"></div>
</section>
<footer>
<div class="cols-1 right"><button eicbutton primary small class="user-add">add user...</button></div>
</footer>
</article>
</section>
</div>
@@ -0,0 +1,7 @@
class BypassKICManagementContent extends BypassBaseManagementContent {
DOMContentLoaded(options) { super.DOMContentLoaded(options); }
}
app.registerClass('BypassKICManagementContent', BypassKICManagementContent);
+42
View File
@@ -0,0 +1,42 @@
<article eiccard class="fasttracks dashboard">
<header>
<h1>EIC Plug In</h1>
<h2>Monitoring for country <b info>${app.meta.toString('accelerator-tracks', profile.domain)}</b></h2>
</header>
<section>
<div class="tabs-extended">
<section>
<menu eictab>
<li>Tokens</li>
<li>History</li>
<li class="settings">Settings</li>
</menu>
</section>
</div>
<article eiccard class="tokens content">
<section>
<div class="cols-2 right middle">
<div class="metrics">
<div class="available"><span>0</span><label>available</label></div>
<div class="assigned-past"><span>0</span><label>assigned <span class="year"></span></label></div>
<div class="assigned-current"><span>0</span><label>assigned <span class="year"></span></label></div>
<div class="consumed-past"><span success>0</span><label success>consumed <span class="year"></span></label></div>
<div class="consumed-current"><span success>0</span><label success>consumed <span class="year"></span></label></div>
</div>
<button eicbutton primary class="grant-token">Grant a token</button>
</div>
<div eicdatagrid class="granted-tokens" footer="hidden"></div>
</section>
</article>
<article eiccard class="history content">
<section>
<div eicdatagrid class="history-tokens"></div>
</section>
</article>
<article eiccard class="config content">
<section>
<div eicdatagrid class="users" footer="hidden"></div>
</section>
</article>
</section>
</article>
+364
View File
@@ -0,0 +1,364 @@
class BypassPluginDashboard extends EICDomContent {
DOMContentLoaded(options) {
ui.eicfy(this.el);
this.profile = options.profile;
for(let model in options.models) this[model] = options.models[model];
this.tabs = new Tab();
this.tabs.addTabs(this.findAll('menu li'), this.findAll('.content'))
this.gridTokens = new DataGrid(this.find('.granted-tokens'), {
headers: [
{ label: 'PIC', type: 'markup', filter: 'text', sortable: true},
{ label: 'Company name', filter: 'text', sortable: true},
{ label: 'Country', filter: 'list', sortable: true},
{ label: 'Token granted', type: 'date', filter: 'text', sortable: true},
{ label: 'Status', filter: 'list', sortable: true},
{ label: 'Project', filter: 'text', sortable: true},
{ label: 'Short proposal submission', type: 'date', filter: 'text', sortable: true},
],
height: 'calc(100vh - 472px)',
actions: []
});
this.btTokenDl = new Button(null, {icon: 'icon-download', severity: 'secondary', rounded: true, size: 'small', hint: 'Download complete list as CSV'})
this.btTokenDl.addEventListener('click', this.downloadTokensList.bind(this));
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenDl.el);
this.btTokenRefresh = new Button(null, {icon: 'icon-refresh', severity: 'primary', rounded: true, size: 'small', hint: 'Refresh the list'})
this.btTokenRefresh.addEventListener('click', this.refreshTokensList.bind(this));
this.gridTokens.el.querySelector('header .cell.actions').append(this.btTokenRefresh.el);
this.gridTokens.onRowFiltered = this.refreshMetrics.bind(this);
this.gridHistory = new DataGrid(this.find('.history-tokens'), {
headers: [
{ label: 'Date', type: 'dateTime', filter: 'text', sortable: true},
{ label: 'PIC', filter: 'text', sortable: true},
{ label: 'Company name', filter: 'text', sortable: true},
{ label: 'Action', filter: 'list', sortable: true},
{ label: 'Managed by', filter: 'text', sortable: true},
],
height: 'calc(100vh - 420px)',
actions: []
});
let btGrant = this.find('.grant-token');
if(this.company.hasPrivilege('grant')) {
btGrant.addEventListener('click', this.onTokenGrant.bind(this));
} else {
ui.hide(btGrant);
}
if(this.tokens.hasPrivilege('list')) { this.refreshTokensList(); }
if(this.users) {
this.loadContent(
'projects/bypass/BypassPluginManagementContent',
{ title: 'Accelerator configuration', subtitle: 'Users access & token management' },
{ profile: this.profile, models: { users: this.users, tokens: this.tokens } }
).then(view => this.setupUserManagement(view))
} else {
ui.hide(this.find('li.settings'));
}
}
setupUserManagement(view) {
this.userManagement = view;
this.find('.content.config').append(view.el);
}
fill() {
let year = new Date().getFullYear();
let awardedPast = this.tokens.awardedTokens(year - 1);
awardedPast.forEach(item => item.ref = 'assigned-past');
let awardedCurrent = this.tokens.awardedTokens(year);
awardedCurrent.forEach(item => item.ref = 'assigned-current');
let consumedPast = this.tokens.consumedTokens(year - 1);
consumedPast.forEach(item => item.ref = 'consumed-past');
let consumedCurrent = this.tokens.consumedTokens(year);
consumedCurrent.forEach(item => item.ref = 'consumed-current');
let history = this.tokens.history();
this.find('.metrics .available span').innerText = this.tokens.freeTokens - awardedCurrent.length;
this.find('.metrics .assigned-current .year').innerText = year;
this.find('.metrics .consumed-current .year').innerText = year;
this.find('.metrics .assigned-past .year').innerText = year - 1;
this.find('.metrics .consumed-past .year').innerText = year - 1;
this.gridHistory.loading = false;
this.gridTokens.loading = false;
this.btTokenRefresh.loading = false;
this.gridTokens.clear();
let awarded = awardedCurrent.concat(awardedPast);
awarded.sort((a,b) => a.itemData.granted < b.itemData.granted ? 1: -1);
for (const item of awarded) {
let priority = ''
switch(item.itemData.status) {
case 'consumed':
priority = 'success';
break;
}
let row = this.gridTokens.addRow(item.itemData.id, [
`<a href="#" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" title="View token details">${item.itemData.enterprise.pic}</a>`,
item.itemData.enterprise.legalname,
app.meta.toString('accelerator-tracks', item.itemData.domain),
item.itemData.granted,
`<span ${priority}>${item.itemData.status}</span>`,
item.itemData.project ? item.itemData.project.acronym: '',
item.itemData.project ? item.itemData.project.submissionDate: ''
], true);
let tokenLink = row.querySelector('.cell:nth-child(2) a')
tokenLink.addEventListener('click', this.onTokenView.bind(this));
row.classList.add(item.ref);
// checking if token can be revoked (meaning status is allocated and user has privilege for that action)
if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (!item.itemData.isAssignedToShortProposal)) {
let buttonBar = ui.create(`<div class="cols-2"></div>`)
let revokeButton = ui.create(`<button eicbutton xsmall danger title="revoke this token" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}" >Revoke</button>`);
revokeButton.addEventListener('click', this.onTokenRevoke.bind(this));
buttonBar.appendChild(revokeButton);
let useButton = ui.create(`<button eicbutton xsmall info title="Activate the coaching" data-id="${item.itemData.id}" data-pic="${item.itemData.enterprise.pic}" data-legalname="${item.itemData.enterprise.legalname}" data-track="${item.itemData.track}" data-domain="${item.itemData.domain}" >Consume</button>`);
useButton.addEventListener('click', this.onTokenConsume.bind(this));
buttonBar.appendChild(useButton);
row.querySelector('.cell.actions').appendChild(buttonBar);
} else if((item.itemData.status == "allocated") && this.company.hasPrivilege('revoke') && (item.itemData.isAssignedToShortProposal)){
row.querySelector('.cell.actions').appendChild(ui.create(`<span>Consuming...</span>`));
}
}
this.gridTokens.updateFilters();
// filling the history list
this.gridHistory.clear();
for (const item of history) {
let priority = ''
switch(item.action) {
case 'revoked': priority = 'danger'; break;
case 'consumed': priority = 'success'; break;
}
let row = this.gridHistory.addRow(item.date, [
item.date,
item.pic,
item.legalname,
`<span ${priority}>${item.action}</span>`,
`${item.actor.firstname ? item.actor.firstname[0] + '. ': ''}${item.actor.lastname}`
], true);
}
this.gridHistory.updateFilters();
this.refreshMetrics();
}
refreshMetrics() {
let rows = Array.from(this.gridTokens.filteredRows);
let assignedPast = rows.filter(item => item.classList.contains('assigned-past'));
let assignedCurrent = rows.filter(item => item.classList.contains('assigned-current'));
let consumedPast = rows.filter(item => item.classList.contains('consumed-past'));
let consumedCurrent = rows.filter(item => item.classList.contains('consumed-current'));
this.find('.metrics .assigned-past span').innerText = assignedPast.length;
this.find('.metrics .assigned-current span').innerText = assignedCurrent.length;
this.find('.metrics .consumed-past span').innerText = consumedPast.length;
this.find('.metrics .consumed-current span').innerText = consumedCurrent.length;
}
refreshTokensList(event) {
if(event) {
event.preventDefault();
event.stopPropagation();
}
this.btTokenRefresh.loading = true;
this.gridTokens.loading = true;
this.gridHistory.loading = true;
this.tokens.list().then(this.fill.bind(this));
}
downloadTokensList(event) {
if(event) {
event.preventDefault();
event.stopPropagation();
}
this.btTokenDl.loading = true;
this.tokens.export({filter: { includePersons: true }}).then(this.buildCSV.bind(this));
}
buildCSV(payload) {
let options = {
headers: [
'PIC',
'Organisation Name',
'Granted on',
'Scheme',
'Entity',
'Program',
'Status',
'Project',
'Submission date',
'Contact name',
'Contact gender',
'Contact position',
'Contact email',
'Contact phone',
'SPOC name',
'SPOC email',
],
filename: 'EIC-Bypass',
delimiter: ';'
}
let data = [];
for(let token of payload.tokens) {
let row = [
token.enterprise.pic,
token.enterprise.legalname,
token.granted,
app.meta.toString('accelerator-tracks', token.track),
app.meta.toString('accelerator-tracks', token.domain),
token.program || '',
token.history[0].status,
token.project ? token.project.acronym: '',
token.project ? token.project.submissiondate: '',
token.shortProposal ? token.shortProposal.contact.lastname + ' ' + token.shortProposal.contact.firstname: '',
token.shortProposal ? token.shortProposal.contact.gender: '',
token.shortProposal ? token.shortProposal.contact.position: '',
token.shortProposal ? token.shortProposal.contact.email: '',
token.shortProposal ? token.shortProposal.contact.phone: '',
token.spoc ? token.spoc.lastname + ' ' + token.spoc.firstname: '',
token.spoc ? token.spoc.email: '',
]
data.push(row);
}
app.helpers.translator.toCSV( data, options );
this.btTokenDl.loading = false;
}
async onTokenGrant(event) {
event.preventDefault();
event.stopPropagation();
let grantResult = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantDialog',
{ title: 'Grant a token' },
{ models: { 'company': this.company, 'tokens': this.tokens }, profile: this.profile }
)
);
if(grantResult) {
ui.growl.append('Token granted', 'success');
let ConsumeResult = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantPropaDialog',
{ title: 'Activate the coaching for...', subtitle: `${this.company.itemData.legalname} (${this.company.itemData.pic})` },
{
models: { users: this.users, token: this.tokens },
mode: 'edit',
profile: this.profile,
tokenId: this.company.itemData.token.id,
pic: this.company.pic,
legalname: this.company.itemData.legalname,
wizard: true,
}
)
);
}
this.refreshTokensList();
}
async onTokenRevoke(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenRevokeDialog',
{ title: 'Revoke a token for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
{
model: this.company,
profile: this.profile,
pic: event.currentTarget.dataset.pic,
track: event.currentTarget.dataset.track,
domain: event.currentTarget.dataset.domain,
}
)
);
if(result) {
ui.growl.append('Token revoked', 'success');
this.refreshTokensList();
}
}
async onTokenView(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
{ title: 'Token granted to ', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
{
models: { users: this.users, token: this.tokens },
mode: 'read',
profile: this.profile,
tokenId: event.currentTarget.dataset.id,
pic: event.currentTarget.dataset.pic,
legalname: event.currentTarget.dataset.legalname,
wizard: false,
}
)
);
if(result) {
ui.growl.append('Token consumed', 'success');
this.refreshTokensList();
}
}
async onTokenConsume(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassTokenGrantProposalDialog',
{ title: 'Activate the coaching for...', subtitle: `${event.currentTarget.dataset.legalname} (${event.currentTarget.dataset.pic})` },
{
models: { users: this.users, token: this.tokens },
mode: 'edit',
profile: this.profile,
tokenId: event.currentTarget.dataset.id,
pic: event.currentTarget.dataset.pic,
legalname: event.currentTarget.dataset.legalname,
wizard: false,
}
)
);
if(result) {
ui.growl.append('Token consumed', 'success');
this.refreshTokensList();
}
}
}
app.registerClass('BypassPluginDashboard', BypassPluginDashboard);
@@ -0,0 +1,30 @@
<div>
<section>
<article eiccard collapsable>
<header>
<h1>Users Access</h1>
<h2></h2>
</header>
<section>
<div eicdatagrid class="users-list"></div>
</section>
<footer>
<div class="cols-1 right"><button eicbutton primary small class="user-add">add user...</button></div>
</footer>
</article>
</section>
<section>
<article eiccard collapsable class="programmes-article">
<header>
<h1>Programme Settings</h1>
<h2>Programmes managed by your country</h2>
</header>
<section>
<div eicdatagrid class="programmes-list" footer="hidden"></div>
</section>
<footer>
<div class="cols-1 right"><button class="programme-create" eicbutton primary small>add programme...</button></div>
</footer>
</article>
</section>
</div>
@@ -0,0 +1,98 @@
class BypassPluginManagementContent extends BypassBaseManagementContent {
DOMContentLoaded(options) {
super.DOMContentLoaded(options);
Object.assign(this, app.helpers.basicDialogs)
let components = ui.eicfy(this.el);
this.programsArticle = components.find(component => component.el.classList.contains('programmes-article'));
this.programsGrid = new DataGrid(this.find('.programmes-list'), {
headers: [ { label: 'Program'} ],
height: '320px',
actions: []
});
let programmeBt = this.find('.programme-create');
if(this.tokens.hasPrivilege('addProgram')) {
programmeBt.addEventListener('click', this.onProgramAdd.bind(this));
} else {
ui.hide(programmeBt);
}
this.programsGrid.loading = true;
this.tokens.getPrograms(this.profile.track, this.profile.domain)
.then(this.fillPrograms.bind(this));
}
fillPrograms(payload) {
this.programsGrid.clear();
this.programsGrid.loading = false;
for(let item of payload) {
let row = this.programsGrid.addRow(0, [ item ]);
// checking if programme can be deleted
if(this.tokens.hasPrivilege('removeProgram')) {
let button = ui.create(`<button eicbutton small danger title="delete this programme" data-label="${item}" data-track="${this.profile.track}" data-domain="${this.profile.domain}" >delete</button>`);
button.addEventListener('click', this.onProgramDelete.bind(this));
row.querySelector('.cell.actions').appendChild(button);
}
}
}
async onProgramAdd(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.openDialog(
await this.loadContent(
'projects/bypass/dialogs/BypassProgramAddDialog',
{ title: 'Add a programme' },
{ model: this.tokens, profile: this.profile }
)
);
if(result) {
ui.growl.append('Programme added', 'success');
this.programsGrid.loading = true;
this.tokens.getPrograms(this.profile.track, this.profile.domain)
.then((payload) => {
this.fillPrograms(payload);
this.programsArticle.expand();
})
}
}
async onProgramDelete(event) {
event.preventDefault();
event.stopPropagation();
let result = await this.confirmDialog({
title: 'Delete this programme',
message: `Please be aware that you are about to delete the programme "<strong>${event.target.dataset.label}</strong>"<br>
Are you positive ?`,
okLabel: 'Delete',
okPromise:() => {
let payload = {
track: event.target.dataset.track,
domain: event.target.dataset.domain,
program: event.target.dataset.label
}
// this promise should always return a result (awaited above)
// either built here or from the model action.
return(this.tokens.removeProgram(payload))
}
});
if(result) {
this.programsGrid.loading = true;
this.tokens.getPrograms(this.profile.track, this.profile.domain)
.then(this.fillPrograms.bind(this));
}
}
}
app.registerClass('BypassPluginManagementContent', BypassPluginManagementContent);
@@ -0,0 +1,8 @@
<div class="fasttracks ">
<section class="program">
<input type="hidden" name="track" value="" />
<input type="hidden" name="domain" value="" />
<label>Programme name</label>
<input type="text" class="required" eicinput name="program" value="" />
</section>
</div>
@@ -0,0 +1,54 @@
class BypassProgramAddDialog extends EICDialogContent {
actions = [
{
label: 'Cancel',
onclick: this.cancel.bind(this),
severity: 'secondary',
role: 'cancel'
},
{
label: 'Create programme',
onclick: this.add.bind(this),
severity: 'primary',
role: 'add',
disabled: false
}
]
constructor(options) { super(options); }
DOMContentLoaded(options) {
this.model = options.model;
this.profile = options.profile;
let components = ui.eicfy(this.el);
this.form = new Form(this.find('.program'));
this.find('input[name="track"]').value = this.profile.track;
this.find('input[name="domain"]').value = this.profile.domain;
}
add() {
if(this.form.validate()) {
this.actions.find(o => o.role == 'add').button.loading = true;
this.actions.find(o => o.role == 'cancel').button.disabled = true;
this.model.addProgram(this.form.value)
.then( this.onSuccess.bind(this), this.onFailed.bind(this) )
}
}
onSuccess() {
this.actions.find(o => o.role == 'add').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.commit(true);
}
onFailed() {
this.actions.find(o => o.role == 'add').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.abort();
}
}
app.registerClass('BypassProgramAddDialog',BypassProgramAddDialog);
@@ -0,0 +1,34 @@
<div class="fasttracks ">
<section class="cols-2 right top search">
<input eicinput type="search" class="required validate-number" data-path="query" hint="The PIC number of a company is at least 8 digit long number" placeholder="Enter a PIC number..." />
<button eicbutton primary icon="icon-search" class="search"></button>
</section>
<section class="result">
</section>
<section class="explanation">
<input type="hidden" data-path="pic" value="" />
<alert eicalert info>In order to complete your request, please fill in the following</alert>
<p><span class="company"></span> should get access to the Accelerator under this specific scheme:</p>
<section class="cols-2">
<div>
<label>Scheme</label>
<select eicselect data-path="track" class="required"></select>
</div>
<div>
<label>Entity</label>
<select eicselect data-path="domain" class="required"></select>
</div>
</section>
<section>
<textarea name="justification" eictextarea class="required"
placeholder="Please explain why you are proposing this company for the EIC Accelerator"></textarea>
<div class="programme">
<label>Related programme</label>
<select data-path="programme" eicselect placeholder="Please select a programme..."></select>
</div>
<input eiccheckbox type="checkbox" data-path="justification.independentExpert" data-type="ignore" class="required" label="You confirm that you have relied on independent external experts to perform the project review" value="yes" />
<input eiccheckbox type="checkbox" data-path="justification.rulesCompliant" data-type="ignore" class="required" label="You confirm that you have complied with all the conditions and requirements set out for the scheme" value="yes" />
</section>
</section>
</div>
+216
View File
@@ -0,0 +1,216 @@
class BypassTokenGrantDialog extends EICDialogContent {
actions = [
{
label: 'Cancel',
onclick: this.cancel.bind(this),
severity: 'secondary',
role: 'cancel'
},
{
label: 'Grant token',
onclick: this.grant.bind(this),
severity: 'primary',
role: 'grant',
disabled: true
}
]
status = '';
DOMContentLoaded(options) {
for(let model in options.models) this[model] = options.models[model];
this.profile = options.profile;
let components = ui.eicfy(this.el);
this.searchbar = this.find('.search');
this.result = this.find('.result');
this.query = components.find(component => component.el.dataset.path == 'query');
this.query.onQuery = this.onSearch.bind(this);
this.trackSelect = components.find(component => component.el.dataset.path == 'track');
this.domainSelect = components.find(component => component.el.dataset.path == 'domain');
this.programmeSelect = components.find(component => component.el.dataset.path == 'programme');
this.searchButton = components.find(component => component.el.classList.contains('search'));
ui.hide(this.find('.programme'));
this.programmeSelect.el.classList.remove('required');
let tracks = app.meta.toOptions(app.meta.getCollection('accelerator-tracks'), this.profile.track, true);
tracks.forEach(item => this.trackSelect.el.append(item));
if(this.profile.track) {
this.fillDomain(this.profile.track, this.profile.domain);
//NIKE : Quickpatch because <option selected> is broken by the famous "couldn't care less update" in eicui
this.trackSelect.value = this.profile.track
this.trackSelect.disabled = true;
} else {
this.trackSelect.change(this.onTrackChange.bind(this));
}
if(this.profile.domain) {
//NIKE : Quickpatch because <option selected> is broken by the famous "couldn't care less update" in eicui
this.domainSelect.value = this.profile.domain
this.domainSelect.disabled = true;
// MFA: added this as it seems when using SPOCs, programmes were not handled
this.fillProgramme(this.trackSelect.value, this.domainSelect.value);
} else {
this.domainSelect.change(this.onDomainChange.bind(this));
}
this.form = new Form(this.find('.explanation'));
ui.hide(this.result)
ui.hide(this.form.el)
this.searchButton.el.addEventListener('click', this.onSearch.bind(this));
}
onTrackChange(event) {
event.preventDefault();
event.stopPropagation();
if(this.trackSelect.value) {
this.fillDomain(this.trackSelect.value);
} else {
this.domainSelect.empty();
}
this.fillProgramme(this.trackSelect.value, this.domainSelect.value);
}
onDomainChange(event) {
event.preventDefault();
event.stopPropagation();
this.fillProgramme(this.trackSelect.value, this.domainSelect.value);
}
fillDomain(track, domain) {
let domains = track ? app.meta.toOptions(app.meta.getItem('accelerator-tracks', track).children, domain, true): [];
this.domainSelect.empty();
domains.forEach(item => this.domainSelect.el.append(item));
}
fillProgramme(track, domain) {
ui.hide(this.find('.programme'));
this.programmeSelect.empty();
this.programmeSelect.el.classList.remove('required');
this.programmeSelect.el.dataset.type = 'ignore';
if(track == 'daTai94ymStyRbQWybH3eDw') {
if(domain) {
ui.lock();
this.tokens.getPrograms(track, domain)
.then(payload => {
ui.unlock();
if(payload.length > 0) {
ui.show(this.find('.programme'));
this.programmeSelect.el.classList.add('required');
this.programmeSelect.el.dataset.type = 'text';
this.programmeSelect.el.append(ui.create('<option value="">(select a program)</option>'))
for(let program of payload) {
this.programmeSelect.el.append(ui.create(`<option value="${program}">${program}</option>`))
}
}
});
}
}
}
onSearch(event) {
if(event) {
event.stopPropagation();
event.preventDefault();
}
this.query.value = this.query.value.trim();
if(this.query.value != '') {
ui.hide(this.result);
this.searchButton.loading = true;
this.company.search(this.query.value)
.then(this.onResult.bind(this), this.onEmptyResult.bind(this));
}
}
onResult(result) {
ui.show(this.result);
this.searchButton.loading = false;
let hasToken = result.token && Object.keys(result.token).length > 0;
let status = hasToken ? 'danger': 'success';
let message = hasToken ?
'This company is already benefitting of the EIC Accelerator':
'This company is elligible for the EIC Accelerator';
this.result.innerHTML = `<div class="company" ${status} class="icon-success">
<div><span class="legalname" primary>${result.legalname}</span></div>
<div class="cols-3 top">
<div><label>PIC</label><span class="pic">${result.pic}</span></div>
<div><label>Country</label><span class="domain">${result.country || '(unknown)'}</span></div>
<div><label>Location</label><span class="domain">${result.city || '(unknown)'}</span></div>
</div>
</div>
<div eicalert ${status} success>${message}</div>`;
this.actions.find(o => o.role == 'grant').button.disabled = true;
// resolving user interaction
if(!hasToken) {
//this.find('[data-path="pic"]').value
this.legalname = result.legalname;
this.status = 'selecting'
this.find('span.company').innerHTML = `${result.legalname} (${result.pic})`;
this.actions.find(o => o.role == 'grant').button.disabled = false;
}
}
onEmptyResult() {
ui.show(this.result);
this.searchButton.loading = false;
this.result.innerHTML = `<div eicalert danger>Sorry, this PIC is not registered</div>`;
this.status = 'selecting';
this.actions.find(o => o.role == 'grant').button.disabled = true;
}
onSelect() {
this.find('[data-path="pic"]').value = this.query.value;
ui.hide(this.searchbar);
ui.hide(this.result);
ui.show(this.form.el);
this.status = 'completing';
}
grant() {
switch(this.status) {
case 'selecting':
this.onSelect();
break;
case 'completing':
if(this.form.validate()) {
this.actions.find(o => o.role == 'grant').button.loading = true;
this.actions.find(o => o.role == 'cancel').button.disabled = true;
this.company.grant(this.company.itemData.pic, this.form.value)
.then( this.onGrantSuccess.bind(this), this.onGrantFailed.bind(this))
}
break;
}
}
onGrantSuccess() {
this.actions.find(o => o.role == 'grant').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.commit(true);
}
onGrantFailed() {
this.actions.find(o => o.role == 'grant').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.abort();
}
}
app.registerClass('BypassTokenGrantDialog',BypassTokenGrantDialog);
@@ -0,0 +1,155 @@
<style>
.fasttracks-propa {
padding: var(--eicui-base-spacing-xl);
transition: all 0.5s;
}
.fasttracks-propa.loading {
padding: var(--eicui-base-spacing-xl);
transition: all 0.5s;
overflow: hidden;
}
.fasttracks-propa .spinner {
text-align: center;
display: none;
animation: spin 1s infinite linear;
}
.fasttracks-propa .content { display: block; }
.fasttracks-propa.loading .spinner { display: block; }
.fasttracks-propa.loading .content { display: none; }
.fasttracks-propa .fields-group { min-height: 490px; }
.fasttracks-propa .fields-group.read { min-height: 380px; }
</style>
<div class="fasttracks-propa loading">
<div large class="spinner icon-spinner">
</div>
<div class="content">
<input eicinput type="hidden" name="pic" data-path="organisation" value="" />
<div eicalert success class="wizard-message">A token has been successfully granted to <span class="company"></span></div>
<p class="instructions">In order to immediately activate the coaching, please fill-in the following informations :</p>
<div class="cols-2 left">
<div class="tabs-extended vertical">
<menu eictab>
<li data-target="token">Token</li>
<li data-target="enterprise">Company <span xxsmall eicbadge danger data-target="enterprise"></span></li>
<li data-target="proposal">Proposal <span xxsmall eicbadge danger data-target="proposal"></span></li>
<li data-target="contact">Contact person <span xxsmall eicbadge danger data-target="contact"></span></li>
</menu>
</div>
<div class="sections">
<article eiccard class="fields-group" data-target="token">
<section class="cols-2">
<div>
<label>granted</label>
<input eicinput name="granted" data-path="token" data-type="ignore" disabled />
</div>
<div>
<label>status</label>
<input eicinput name="status" data-path="" data-type="ignore" disabled />
</div>
<div>
<label>Scheme</label>
<input eicinput name="track" data-path="token" data-type="ignore" disabled />
</div>
<div>
<label>Domain</label>
<input eicinput name="domain" data-path="token" data-type="ignore" disabled />
</div>
</section>
</article>
<article eiccard class="fields-group" data-target="enterprise">
<section>
<div class="cols-2">
<div>
<label>PIC</label>
<input eicinput name="pic" data-path="token.enterprise" data-type="ignore" disabled />
</div>
<div>
<label>Legal name</label>
<input eicinput name="legalname" data-path="token.enterprise" data-type="ignore" disabled />
</div>
</div>
<div>
<label>Type of entity</label>
<select eicselect name="companyType" data-path="organisation" class="required">
<option value=""></option>
<option value="sme">SME</option>
<option value="midcap">Small mid-cap</option>
<option value="person">Natural person</option>
</select>
</div>
</section>
</article>
<article eiccard class="fields-group" data-target="proposal">
<section>
<div class="cols-2">
<div>
<label>Acronym</label>
<input eicinput name="acronym" data-path="proposal" data-type="text" maxlen="50" class="required" />
</div>
<div>
<label>Submitted</label>
<input eicinput name="submissiondate" data-path="token.project" data-type="ignore" disabled />
</div>
</div>
<div>
<label>Full Title</label>
<input eicinput name="title" data-path="proposal" data-type="text" maxlen="200" class="required" />
</div>
<div>
<label>Abstract</label>
<textarea eictextarea name="abstract" data-path="proposal" data-type="text" maxlen="1000" class="required"></textarea>
</div>
</section>
</article>
<article eiccard class="fields-group" data-target="contact">
<section>
<div class="lookup">
<label>Please identify the contact person by the EU-Login</label>
<div class="cols-2 right center">
<input eicinput type="search" name="euLoginId" data-path="contact" class="required" value="" placeholder="Enter member's EU Login ID or email and press enter"/>
<button eicbutton icon="icon-search" class="user-search" primary ></button>
</div>
</div>
<div class="cols-3">
<div>
<label>First name</label>
<input eicinput disabled name="firstname" data-path="contact" data-type="text" class="required" />
</div>
<div>
<label>Last name</label>
<input eicinput disabled name="lastname" data-path="contact" data-type="text" class="required" />
</div>
<div>
<label>Gender</label>
<select eicselect name="gender" data-path="contact" data-type="text" class="required">
<option></option>
<option value="F">Female</option>
<option value="M">Male</option>
<option value="I">I prefer not to declare</option>
</select>
</div>
</div>
<div class="cols-2">
<div>
<label>email</label>
<input eicinput disabled type="email" name="email" data-path="contact" data-type="text" />
</div>
<div>
<label>Phone</label>
<input eicinput type="tel" name="phone" data-path="contact" data-type="text" maxlen="100" />
</div>
</div>
<div>
<label>Position</label>
<select eicselect lookup name="position" data-path="contact" data-type="text" class="required"></select>
</div>
</section>
</article>
</div>
</div>
</div>
</div>
@@ -0,0 +1,220 @@
class BypassTokenGrantProposalDialog extends EICDialogContent {
actions = [
{
label: 'Cancel',
onclick: this.cancel.bind(this),
severity: 'secondary',
disabled: false,
role: 'cancel'
},
{
label: 'Assign token to this proposal',
onclick: this.consume.bind(this),
severity: 'primary',
disabled: false,
role: 'consume'
}
]
status = '';
mode = 'edit';
validationInterval = 1000;
DOMContentLoaded(options) {
console.log('==BypassTokenGrantProposalDialog==>', options)
this.models = options.models;
this.mode = options.mode || this.mode;
this.enableWizard = options.wizard;
this.tokenId = options.tokenId;
this.components = ui.eicfy(this.el);
this.find('.fasttracks-propa').classList.add(this.mode)
this.tabs = new Tab();
this.tabs.addTabs(this.findAll('.tabs-extended menu li'), this.findAll('.fields-group'))
let position = this.find('select[name="position"]');
app.meta.toOptions('organisation-functions', null, true).forEach(item => position.append(item));
this.find('[name="pic"]').value = options.pic;
this.euid = this.components.find(component => component.el.name == 'euLoginId');
this.firstname = this.components.find(component => component.el.name == 'firstname');
this.lastname = this.components.find(component => component.el.name == 'lastname');
this.email = this.components.find(component => component.el.name == 'email');
this.userSearchButton = this.components.find(component => component.el.classList.contains('user-search'));
this.cards = this.components.filter(component => component.el.classList.contains('fields-group'))
this.globalForm = new Form(this.el);
this.miniForms = Array.from(this.cards.map(card=>card.el).map(el => new Form(el)))
this.euid.onQuery = this.onUserSearch.bind(this)
this.userSearchButton.el.addEventListener('click', this.onUserSearch.bind(this))
}
DOMContentFocused() {
this.actions.forEach(o => o.button.disabled = true);
this.models.token.viewToken(this.tokenId)
.then(payload => { this.setMode(this.mode, payload) });
}
setMode(mode, payload) {
this.find('.fasttracks-propa').classList.remove('loading');
// cleanup
payload.token.granted = payload.token.granted ? ui.format.dateTime(payload.token.granted): ''
payload.token.track = app.meta.toString('accelerator-tracks', payload.token.track)
payload.token.domain = app.meta.toString('accelerator-tracks', payload.token.domain)
payload.status = payload.token.history[0].status;
if(!payload.token.project) payload.token.project = {}
payload.token.project.submissiondate = payload.token.project.submissiondate ? ui.format.dateTime(payload.token.project.submissiondate): ''
let fields = this.globalForm.scan();
for(let field of fields) {
let component = this.el2component(field.el.dataset.path, field.el.name);
if(component) component.disabled = this.mode == 'read' || field.el.hasAttribute('disabled');
let item = this.findField(payload, field.el.dataset.path, field.el.name)
if(item) {
if(component.el.type == 'checkbox'){
component.el.checked = item == true;
}
else
component.value = item;
}
}
switch(mode) {
case 'read':
ui.hide(this.find('.wizard-message'));
ui.hide(this.find('.lookup'));
ui.hide(this.find('.instructions'));
ui.hide(this.actions.find(o => o.role == 'consume').button.el);
this.actions.find(o => o.role == 'cancel').label = 'Close';
break;
case 'edit':
if(!this.enableWizard)
ui.hide(this.find('.wizard-message'))
else
this.actions.find(o => o.role == 'cancel').label = 'Skip';
this.tabs.selectByIndex(1);
this.validationCheck = setInterval(this.validateTabs.bind(this), this.validationInterval);
break;
}
this.actions.forEach(o => o.button.disabled = false);
}
el2component(path, name) {
let item = this.components
.find(o =>
o.el.name == name &&
( !path || (path && o.el.dataset.path == path))
)
return item;
}
/**
* Retrieves a data field value based on path an name
* @param {*} path
* @param {*} name
* @returns
*/
findField(payload, path, name) {
let levels = path && path != '' && path !== undefined ? path.split('.'): [];
let tree = payload;
for(let level of levels)
if(tree.hasOwnProperty(level)) tree = tree[level];
if(tree.hasOwnProperty(name)) return tree[name];
}
onUserSearch(event) {
this.userSearchButton.loading = true;
this.models.users.search(this.euid.value).then(
(userList => {
this.userSearchButton.loading = false;
if(userList.length==0) return
// TODO better manage if several (take most complete?)
let user = userList[0];
this.euid.value = user.uid;
this.firstname.value = user.given_name;
this.lastname.value = user.family_name;
this.email.value = user.email;
}),
(err) => {
this.userSearchButton.loading = false;
}
);
}
validateTabs() {
let valid = true;
for(let form of this.miniForms) {
let report = form.validate(true)
let badge = this.components.find(item => item.el.hasAttribute('eicbadge') && item.el.dataset.target == form.el.dataset.target)
if(badge) {
badge.value = report.issues;
}
valid = valid && report.valid;
}
this.actions.find(o => o.role == 'consume').button.disabled = !valid;
}
consume() {
if(this.globalForm.validate()) {
this.actions.find(o => o.role == 'consume').button.loading = true;
this.actions.find(o => o.role == 'cancel').button.disabled = true;
this.models.token.consumeToken(this.globalForm.value)
.then( this.onConsumeSuccess.bind(this), this.onConsumeFailed.bind(this))
}
}
onConsumeSuccess() {
this.actions.find(o => o.role == 'consume').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.commit(true);
}
onConsumeFailed() {
this.actions.find(o => o.role == 'consume').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.abort();
}
DOMContentResized() {
super.DOMContentResized();
let device = this.parentContainer.getAttribute('device');
//let tabs = this.find('.tabs-extended');
switch(device) {
case 'desktop':
this.tabs.triggers.forEach(item => ui.show(item));
let current = this.tabs.getSelected();
let target = current.dataset.target;
this.findAll('article.fields-group').forEach(content => { if(content.dataset.target != target) ui.hide(content) });
break;
default:
this.tabs.triggers.forEach(item => ui.hide(item));
this.findAll('article.fields-group').forEach(content => { ui.show(content) })
break;
}
}
}
app.registerClass('BypassTokenGrantProposalDialog',BypassTokenGrantProposalDialog);
@@ -0,0 +1,14 @@
<div class="fasttracks ">
<section class="explanation">
<alert eicalert danger>Please be aware that you are about to revoke an awarded token.</alert>
</section>
<section class="agreement">
<input type="hidden" name="track" value="" />
<input type="hidden" name="domain" value="" />
<textarea data-path="justification" eictextarea maxlen="200" class="required"
hint="No more than 200 characters please."
placeholder="Please explain why you are removing this company for the EIC Accelerator"></textarea>
<input eiccheckbox type="checkbox" name="agreed" data-type="ignore" value="yes" label="I understand" />
</section>
</div>
@@ -0,0 +1,58 @@
class BypassTokenRevokeDialog extends EICDialogContent {
actions = [
{
label: 'Cancel',
onclick: this.cancel.bind(this),
severity: 'secondary',
role: 'cancel'
},
{
label: 'Revoke token',
onclick: this.revoke.bind(this),
severity: 'danger',
role: 'revoke',
disabled: true
}
]
constructor(options) { super(options); }
DOMContentLoaded(options) {
this.model = options.model;
let components = ui.eicfy(this.el);
this.form = new Form(this.find('.agreement'));
this.pic = options.pic;
this.find('input[name="track"]').value = options.track;
this.find('input[name="domain"]').value = options.domain;
this.optin = this.find('input[name="agreed"]');
this.optin.addEventListener('change',this.onAgreement.bind(this));
}
onAgreement() { this.actions.find(o => o.role == 'revoke').button.disabled = !this.optin.checked; }
revoke() {
if(this.form.validate()) {
this.actions.find(o => o.role == 'revoke').button.loading = true;
this.actions.find(o => o.role == 'cancel').button.disabled = true;
this.model.revoke(this.pic, this.form.value)
.then( this.commit.bind(this), this.abort.bind(this) )
}
}
onRevokeSuccess() {
this.actions.find(o => o.role == 'revoke').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.commit(true);
}
onRevokeFailed() {
this.actions.find(o => o.role == 'revoke').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.abort();
}
}
app.registerClass('BypassTokenRevokeDialog',BypassTokenRevokeDialog);
@@ -0,0 +1,9 @@
<div class="fasttracks settings">
<section class="explanation">
<alert eicalert danger>Please be aware that your changes will take place immediately.</alert>
</section>
<section class="domains">
<input type="hidden" data-path="track" value="${track}" />
<div eicdatagrid footer="hidden"></div>
</section>
</div>
@@ -0,0 +1,80 @@
class BypassTrackSetupDialog extends EICDialogContent {
actions = [
{
label: 'Cancel',
onclick: this.cancel.bind(this),
severity: 'secondary',
role: 'cancel'
},
{
label: 'Save',
onclick: this.save.bind(this),
severity: 'primary',
role: 'save'
}
]
constructor(options) { super(options); }
DOMContentLoaded(options) {
this.model = options.model;
this.track = options.track;
this.domains = options.domains;
this.grid = new DataGrid(this.find('.domains [eicdatagrid]'), {
headers: [ { label: 'Domain'}, { label: 'Tokens', type: 'markup' }, { label: 'Min.' } ],
height: '320px'
});
this.grid.clear();
this.grid.loading = true;
this.model.getSettings(this.track)
.then(this.fill.bind(this));
}
fill(payload) {
this.grid.clear();
this.grid.loading = false;
let index = 0;
for(let item of payload.domains) {
this.grid.addRow(item.domain, [
app.meta.toString('accelerator-tracks', item.domain),
`<input type="hidden" data-path="domains[${index}].domain" value="${item.domain}" />
<input eicinput type="number" min="${item.tokens - item.remaining}" data-path="domains[${index}].tokens" value="${item.tokens}" />`,
item.tokens - item.remaining
]);
index++;
}
this.grid.sort(1, 'asc');
this.form = new Form(this.find('.domains'));
let components = ui.eicfy(this.el);
}
save() {
if(this.form.validate()) {
this.actions.find(o => o.role == 'save').button.loading = true;
this.actions.find(o => o.role == 'cancel').button.disabled = true;
this.model.setSettings(this.form.value)
.then(this.onSuccess.bind(this))
.catch(this.onFailed.bind(this));
}
}
onSuccess() {
this.actions.find(o => o.role == 'save').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.commit(true);
}
onFailed() {
this.actions.find(o => o.role == 'save').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
//this.abort();
}
}
app.registerClass('BypassTrackSetupDialog',BypassTrackSetupDialog);
@@ -0,0 +1,51 @@
<div class="user-dialog">
<div>
<label>EU login or email</label>
<div class="cols-2 right top">
<input eicinput type="search" data-path="euid" class="required" value="" />
<button eicbutton icon="icon-search" class="user-search" primary ></button>
</div>
</div>
<div>
<label>Email</label>
<input eicinput disabled value="" class="required email" data-path="email"/>
</div>
<div class="cols-2">
<div>
<label>Firstname</label>
<input eicinput disabled value="" class="required" data-path="firstname"/>
</div>
<div>
<label>Lastname</label>
<input eicinput disabled value="" class="required" data-path="lastname"/>
</div>
</div>
<div>
<label>Gender</label>
<select eicselect class="required" data-path="gender">
<option></option>
<option value="M">Male</option>
<option value="F">Female</option>
<option value="I">Rather not say</option>
</select>
</div>
<div hidden class="roleSelector">
<label>Role</label>
<select eicselect class="required" class="required" data-path="role">
<option></option>
</select>
</div>
<div class="cols-3">
<div hidden class="trackSelector">
<label>Track</label>
<select eicselect data-path="track"></select>
</div>
<div hidden class="domainSelector">
<label>Domain</label>
<select eicselect data-path="domain"></select>
</div>
</div>
</div>
@@ -0,0 +1,252 @@
class BypassUserProfileDialog extends EICDialogContent {
actions = [
{
label: 'Cancel',
onclick: this.cancel.bind(this),
severity: 'secondary',
role: 'cancel'
},
{
label: 'Add user',
onclick: this.create.bind(this),
severity: 'primary',
role: 'add',
disabled: true
},
]
async DOMContentLoaded(options) {
this.mode = options.mode ? options.mode: 'create';
this.model = options.model;
this.profile = options.profile;
let components = ui.eicfy(this.el);
this.euid = components.find(component => component.el.dataset.path == 'euid');
this.firstname = components.find(component => component.el.dataset.path == 'firstname');
this.lastname = components.find(component => component.el.dataset.path == 'lastname');
this.email = components.find(component => component.el.dataset.path == 'email');
this.genderSelect = components.find(component => component.el.dataset.path == 'gender');
this.roleSelect = components.find(component => component.el.dataset.path == 'role');
this.roleSelect.block = this.find('.roleSelector')
this.trackSelect = components.find(component => component.el.dataset.path == 'track');
this.trackSelect.block = this.find('.trackSelector')
this.domainSelect = components.find(component => component.el.dataset.path == 'domain');
this.domainSelect.block = this.find('.domainSelector');
this.userSearcButton = components.find(component => component.el.classList.contains('user-search'));
this.genderSelect.change(this.changeRoleAvailable.bind(this));
this.roleSelect.change(this.onRoleChange.bind(this));
this.roleSelect.disabled = true;
this.trackSelect.change(this.onTrackChange.bind(this));
this.trackSelect.disabled = true;
this.domainSelect.change(this.onDomainChange.bind(this));
this.domainSelect.disabled = true;
this.form = new Form(this.find('.user-dialog'));
let tracks = app.meta.toOptions(app.meta.getCollection('accelerator-tracks'), null, true);
tracks.forEach(item => this.trackSelect.el.append(item));
this.roleDelegations = {}
this.roleDelegations = await app.Assets.loadJson({name:'global/bypass/roleDelegations.json'})
this.userSearcButton.el.addEventListener('click', this.onUserSearch.bind(this));
}
changeRoleAvailable() {
if( this.form.validateField(this.euid).valid &&
this.form.validateField(this.firstname).valid &&
this.form.validateField(this.lastname).valid &&
this.form.validateField(this.email).valid &&
this.form.validateField(this.genderSelect).valid
) {
this.roleSelect.disabled = false;
this.changeAddAvailable();
let roles4select = this.getDelegationRules().map(x=>x.role);
let options = app.meta.toOptions(roles4select.map(x=>({id:x, label:x})), '', true)
this.roleSelect.empty();
options.forEach(item => this.roleSelect.el.append(item));
ui.show(this.roleSelect.block);
} else {
this.hideSelect(this.roleSelect);
}
}
changeAddAvailable() {
if(this.form.validate() && this.validateDelegation()) {
this.actions.find(o => o.role == 'add').button.disabled = false;
} else {
this.actions.find(o => o.role == 'add').button.disabled = true;
}
}
getDelegationRules() {
/* In delegations, which of my roles shall I use as master... (no role hierarchy)
For now priority is top-down in the roleDelegation masters order.
Bad, not 100% reliable, but in our case, there should be no conflicts (mutually exclusive in delegations)
*/
let delegationsRules = [];
for(let roleDeleg of this.roleDelegations){
if(app.User.hasRole(roleDeleg.master)) {
delegationsRules = roleDeleg.surrogates;
break;
}
}
return(delegationsRules)
}
hideSelect(sel) {
sel.disabled = true;
sel.value = '';
ui.hide(sel.block);
}
ruleSelect(rule, sel, inheritValue) {
if(rule=='inherit') { // Forced to my own value
sel.disabled = true;
sel.value = inheritValue;
ui.show(sel.block);
} else if(rule=='set') { // Can choose
sel.disabled = false;
ui.show(sel.block);
} else if(rule=='ignore'){ // Not applicable
this.hideSelect(sel);
} else { // Forced to fixed value
sel.disabled = true;
sel.value = rule;
ui.show(sel.block);
}
}
validateDelegation(){
let rules = this.getDelegationRules().find(x => x.role==this.roleSelect.value);
if(!rules) return(false)
const validateSel = (rule, sel, inheritValue) => {
let ok = false;
if(rule=='inherit') ok = (sel.value == inheritValue)
else if(rule=='set') ok = Boolean(sel.value)
else if(rule=='ignore') ok = (true)
else ok = (sel.value == rule)
if(ok) sel.el.nextSibling.classList.remove('validation-failed');
else sel.el.nextSibling.classList.add('validation-failed');
return(ok)
}
return( validateSel(rules.track, this.trackSelect, this.profile.track) &&
validateSel(rules.domain, this.domainSelect, this.profile.domain)
)
}
onRoleChange(event) {
if(event) {
event.preventDefault();
event.stopPropagation();
}
// pas de role: pas de track, pas de track : pas de domaine, pas de domaine: pas de palais, etc
if(!this.roleSelect.value){
this.hideSelect(this.trackSelect);
this.hideSelect(this.domainSelect);
return
}
let rules = this.getDelegationRules().find(x => x.role==this.roleSelect.value);
if(!rules) {
console.warn('Delegation not found for role '+this.roleSelect.value);
this.hideSelect(this.trackSelect);
this.hideSelect(this.domainSelect);
return
}
this.ruleSelect(rules.track, this.trackSelect, this.profile.track);
this.onTrackChange();
this.ruleSelect(rules.domain, this.domainSelect, this.profile.domain);
this.changeAddAvailable();
}
onUserSearch(event) {
this.userSearcButton.loading = true;
this.model.search(this.euid.value).then(
(userList => {
this.userSearcButton.loading = false;
if(userList.length==0) return
// TODO better manage if several (take most complete?)
let user = userList[0];
this.euid.value = user.uid;
this.firstname.value = user.given_name;
this.lastname.value = user.family_name;
this.email.value = user.email;
this.changeRoleAvailable();
}),
(err) => {
this.userSearcButton.loading = false;
}
);
}
onTrackChange(event) {
if(event){
event.preventDefault();
event.stopPropagation();
}
if(this.trackSelect.value) {
this.fillDomain(this.trackSelect.value);
} else {
this.domainSelect.empty();
this.hideSelect(this.domainSelect);
}
this.changeAddAvailable();
}
onDomainChange(event) {
event.preventDefault();
event.stopPropagation();
this.changeAddAvailable();
}
fillDomain(track, domain) {
let domains = track ? app.meta.toOptions(app.meta.getItem('accelerator-tracks', track).children, domain, true): [];
this.domainSelect.empty();
domains.forEach(item => this.domainSelect.el.append(item));
}
DOMContentFocused() {
}
create(event) {
if(this.form.validate() && this.validateDelegation()) {
this.actions.find(o => o.role == 'add').button.loading = true;
this.actions.find(o => o.role == 'cancel').button.disabled = true;
this.model.create(this.form.value)
.then( this.onSuccess.bind(this), this.onFailed.bind(this))
}
}
onSuccess() {
this.actions.find(o => o.role == 'add').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.commit(true);
}
onFailed() {
this.actions.find(o => o.role == 'add').button.loading = false;
this.actions.find(o => o.role == 'cancel').button.disabled = false;
this.abort();
}
}
app.registerClass('BypassUserProfileDialog', BypassUserProfileDialog);