install app screen working
This commit is contained in:
parent
4ec0e7e50e
commit
aeb3a6d0c2
5 changed files with 172 additions and 420 deletions
|
@ -172,7 +172,7 @@ module.exports = {
|
|||
*/
|
||||
adminKeys: [
|
||||
|
||||
"[sproing@localhost:3000/ieibXenySscYFjdntu0mFglpvQLSrbBiy0V+wzizaGA=]"
|
||||
// "[sproing@localhost:3000/ieibXenySscYFjdntu0mFglpvQLSrbBiy0V+wzizaGA=]"
|
||||
|
||||
],
|
||||
|
||||
|
|
|
@ -2,26 +2,10 @@
|
|||
|
||||
define([
|
||||
'jquery',
|
||||
'/common/toolbar.js',
|
||||
'/components/nthen/index.js',
|
||||
'/common/sframe-common.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
|
||||
'/common/inner/sidebar-layout.js',
|
||||
'/customize/messages.js',
|
||||
'/common/common-signing-keys.js',
|
||||
'/common/hyperscript.js',
|
||||
'/common/clipboard.js',
|
||||
'json.sortify',
|
||||
'/customize/application_config.js',
|
||||
'/api/config',
|
||||
'/api/instance',
|
||||
'/lib/datepicker/flatpickr.js',
|
||||
'configscreen.js',
|
||||
|
||||
|
||||
|
||||
'/common/hyperscript.js',
|
||||
'css!/lib/datepicker/flatpickr.min.css',
|
||||
|
@ -30,42 +14,18 @@ define([
|
|||
'less!/admin/app-admin.less',
|
||||
], function(
|
||||
$,
|
||||
Toolbar,
|
||||
nThen,
|
||||
SFCommon,
|
||||
UI,
|
||||
UIElements,
|
||||
Util,
|
||||
Hash,
|
||||
Sidebar,
|
||||
Messages,
|
||||
Keys,
|
||||
h,
|
||||
Clipboard,
|
||||
Sortify,
|
||||
AppConfig,
|
||||
ApiConfig,
|
||||
Instance,
|
||||
Flatpickr,
|
||||
ConfigScreen
|
||||
|
||||
) {
|
||||
|
||||
|
||||
//XXX
|
||||
Messages.admin_appSelection = 'App configuration saved'
|
||||
Messages.admin_appsTitle = "Choose your applications"
|
||||
Messages.admin_appsHint = "Choose which apps are available to users on your instance."
|
||||
Messages.admin_cat_apps = "Apps"
|
||||
|
||||
var APP = window.APP = {};
|
||||
|
||||
var Nacl = window.nacl;
|
||||
var common;
|
||||
var sFrameChan;
|
||||
// SFCommon.create(waitFor(function(c) { APP.common = common = c; }));
|
||||
|
||||
// sFrameChan = common.getSframeChannel();
|
||||
// sFrameChan.onReady(waitFor());
|
||||
|
||||
const blocks = Sidebar.blocks;
|
||||
const grid = blocks.block([], 'cp-admin-customize-apps-grid');
|
||||
|
||||
|
@ -83,74 +43,17 @@ define([
|
|||
}
|
||||
|
||||
allApps.forEach(app => {
|
||||
// 'width: 50%; height: 80px; margin: 5px'
|
||||
// width:200px;height:20px;float:left;border:1px solid red
|
||||
let appBlock = h('div', {style: 'width:50%;height:20px;float:left;border:1px solid red'}, {class: 'inactive-app', id: `${app}-block`}, app)
|
||||
$(appBlock).addClass('cp-app-drive-element-grid')
|
||||
$(appBlock).addClass('cp-app-drive-element-row')
|
||||
$(appBlock).addClass('cp-app-drive-new-doc')
|
||||
|
||||
|
||||
$(grid).append(appBlock);
|
||||
$(appBlock).on('click', () => select(app))
|
||||
});
|
||||
|
||||
|
||||
var save = blocks.activeButton('primary', '', Messages.settings_save, function (done) {
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['DISABLE_APPS', availableApps]
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
$input.val('');
|
||||
console.error(e, response);
|
||||
done(false);
|
||||
return;
|
||||
}
|
||||
flushCache();
|
||||
done(true);
|
||||
UI.log(Messages._getKey('ui_saved', [Messages.admin_appSelection]));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
let form = blocks.form([
|
||||
grid
|
||||
], blocks.nav([save]));
|
||||
|
||||
return grid
|
||||
|
||||
var elem = document.createElement('div');
|
||||
elem.setAttribute('id', 'cp-loading');
|
||||
|
||||
let frame = h('div.configscreen', {style: 'width: 70%; height: 75%; background-color: white'}, form)
|
||||
|
||||
elem.append(frame)
|
||||
|
||||
built = true;
|
||||
var intr;
|
||||
var append = function () {
|
||||
if (!document.body) { return; }
|
||||
clearInterval(intr);
|
||||
document.body.appendChild(elem);
|
||||
};
|
||||
intr = setInterval(append, 100);
|
||||
append();
|
||||
|
||||
|
||||
return function () {
|
||||
built = true;
|
||||
var intr;
|
||||
var append = function () {
|
||||
if (!document.body) { return; }
|
||||
clearInterval(intr);
|
||||
document.body.appendChild(elem);
|
||||
};
|
||||
intr = setInterval(append, 100);
|
||||
append();
|
||||
};
|
||||
|
||||
return [availableApps, grid]
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
define([
|
||||
'jquery',
|
||||
'/common/toolbar.js',
|
||||
'/components/nthen/index.js',
|
||||
'/common/sframe-common.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/inner/sidebar-layout.js',
|
||||
'/customize/messages.js',
|
||||
'/common/common-signing-keys.js',
|
||||
'/common/hyperscript.js',
|
||||
'/common/clipboard.js',
|
||||
'json.sortify',
|
||||
'/customize/application_config.js',
|
||||
'/api/config',
|
||||
'/api/instance',
|
||||
'/lib/datepicker/flatpickr.js',
|
||||
'/common/hyperscript.js',
|
||||
'css!/lib/datepicker/flatpickr.min.css',
|
||||
'/customize/messages.js',
|
||||
'/customize/loading.js',
|
||||
|
||||
|
||||
|
||||
'/customize/messages.js',
|
||||
// '/install/configscreen.css',
|
||||
'less!/customize/src/less2/include/loading.less'
|
||||
], function ($,
|
||||
Toolbar,
|
||||
nThen,
|
||||
SFCommon,
|
||||
UI,
|
||||
UIElements,
|
||||
Util,
|
||||
Hash,
|
||||
Sidebar,
|
||||
Messages,
|
||||
Keys,
|
||||
h,
|
||||
Clipboard,
|
||||
Sortify,
|
||||
AppConfig,
|
||||
ApiConfig,
|
||||
Instance,
|
||||
Flatpickr,
|
||||
Messages,
|
||||
Loading) {
|
||||
|
||||
|
||||
//XXX
|
||||
Messages.admin_appSelection = 'App configuration saved'
|
||||
Messages.admin_appsTitle = "Choose your applications"
|
||||
Messages.admin_appsHint = "Choose which apps are available to users on your instance."
|
||||
Messages.admin_cat_apps = "Apps"
|
||||
|
||||
var APP = window.APP = {};
|
||||
|
||||
var Nacl = window.nacl;
|
||||
var common;
|
||||
var sFrameChan;
|
||||
|
||||
var Apps = {}
|
||||
|
||||
Apps.showScreen = function (form) {
|
||||
var LOADING = 'cp-loading';
|
||||
|
||||
var loadingText = "config.loadingText";
|
||||
var todo = function () {
|
||||
var $loading = $('#' + LOADING);
|
||||
// Show the loading screen
|
||||
$loading.css('display', '');
|
||||
$loading.removeClass('cp-loading-hidden');
|
||||
$loading.removeClass('cp-loading-transparent');
|
||||
if (config.newProgress) {
|
||||
var progress = h('div.cp-loading-progress', [
|
||||
h('p.cp-loading-progress-list'),
|
||||
h('p.cp-loading-progress-container')
|
||||
]);
|
||||
$loading.find('.cp-loading-spinner-container').after(progress);
|
||||
}
|
||||
if (!$loading.find('.cp-loading-progress').length) {
|
||||
// Add spinner
|
||||
$('.cp-loading-spinner-container').show();
|
||||
}
|
||||
// Add loading text
|
||||
if (loadingText) {
|
||||
$('#' + LOADING).find('#cp-loading-message').show().text(loadingText);
|
||||
} else {
|
||||
$('#' + LOADING).find('#cp-loading-message').hide().text('');
|
||||
}
|
||||
};
|
||||
if ($('#' + LOADING).length) {
|
||||
todo();
|
||||
} else {
|
||||
Loading();
|
||||
todo();
|
||||
}
|
||||
|
||||
$('html').toggleClass('cp-loading-noscroll', true);
|
||||
// Remove the inner placeholder (iframe)
|
||||
$('#placeholder').remove();
|
||||
// var elem = document.createElement('div');
|
||||
// elem.setAttribute('id', 'cp-loading');
|
||||
|
||||
// let frame = h('div.configscreen', {style: 'width: 70%; height: 75%; background-color: white'}, form)
|
||||
|
||||
// elem.append(frame)
|
||||
|
||||
// return function () {
|
||||
// built = true;
|
||||
// var intr;
|
||||
// var append = function () {
|
||||
// if (!document.body) { return; }
|
||||
// clearInterval(intr);
|
||||
// document.body.appendChild(elem);
|
||||
// };
|
||||
// intr = setInterval(append, 100);
|
||||
// append();
|
||||
|
||||
// }
|
||||
|
||||
};
|
||||
|
||||
return Apps
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
// bg #e7e7e7
|
||||
// blue #0087FF
|
||||
// text #3F4141
|
||||
define([
|
||||
'jquery',
|
||||
'/common/toolbar.js',
|
||||
'/components/nthen/index.js',
|
||||
'/common/sframe-common.js',
|
||||
'/common/common-interface.js',
|
||||
'/common/common-ui-elements.js',
|
||||
'/common/common-util.js',
|
||||
'/common/common-hash.js',
|
||||
'/common/inner/sidebar-layout.js',
|
||||
'/customize/messages.js',
|
||||
'/common/common-signing-keys.js',
|
||||
'/common/hyperscript.js',
|
||||
'/common/clipboard.js',
|
||||
'json.sortify',
|
||||
'/customize/application_config.js',
|
||||
'/api/config',
|
||||
'/api/instance',
|
||||
'/lib/datepicker/flatpickr.js',
|
||||
'/common/hyperscript.js',
|
||||
'css!/lib/datepicker/flatpickr.min.css',
|
||||
'/customize/messages.js',
|
||||
|
||||
|
||||
'/customize/messages.js',
|
||||
// '/install/configscreen.css',
|
||||
'less!/customize/src/less2/include/loading.less'
|
||||
], function ($,
|
||||
Toolbar,
|
||||
nThen,
|
||||
SFCommon,
|
||||
UI,
|
||||
UIElements,
|
||||
Util,
|
||||
Hash,
|
||||
Sidebar,
|
||||
Messages,
|
||||
Keys,
|
||||
h,
|
||||
Clipboard,
|
||||
Sortify,
|
||||
AppConfig,
|
||||
ApiConfig,
|
||||
Instance,
|
||||
Flatpickr,
|
||||
Messages) {
|
||||
|
||||
|
||||
//XXX
|
||||
Messages.admin_appSelection = 'App configuration saved'
|
||||
Messages.admin_appsTitle = "Choose your applications"
|
||||
Messages.admin_appsHint = "Choose which apps are available to users on your instance."
|
||||
Messages.admin_cat_apps = "Apps"
|
||||
|
||||
var APP = window.APP = {};
|
||||
|
||||
var Nacl = window.nacl;
|
||||
var common;
|
||||
var sFrameChan;
|
||||
// SFCommon.create(waitFor(function(c) { APP.common = common = c; }));
|
||||
|
||||
// sFrameChan = common.getSframeChannel();
|
||||
// sFrameChan.onReady(waitFor());
|
||||
|
||||
const blocks = Sidebar.blocks;
|
||||
const grid = blocks.block([], 'cp-admin-customize-apps-grid');
|
||||
|
||||
const allApps = ['pad', 'code', 'kanban', 'slide', 'sheet', 'form', 'whiteboard', 'diagram'];
|
||||
const availableApps = []
|
||||
|
||||
function select(app) {
|
||||
if (availableApps.indexOf(app) === -1) {
|
||||
availableApps.push(app);
|
||||
$(`#${app}-block`).attr('class', 'active-app')
|
||||
} else {
|
||||
availableApps.splice(availableApps.indexOf(app), 1)
|
||||
$(`#${app}-block`).attr('class', 'inactive-app')
|
||||
}
|
||||
}
|
||||
|
||||
allApps.forEach(app => {
|
||||
// 'width: 50%; height: 80px; margin: 5px'
|
||||
// width:200px;height:20px;float:left;border:1px solid red
|
||||
let appBlock = h('div', {style: 'width:50%;height:20px;float:left;border:1px solid red'}, {class: 'inactive-app', id: `${app}-block`}, app)
|
||||
$(appBlock).addClass('cp-app-drive-element-grid')
|
||||
$(appBlock).addClass('cp-app-drive-element-row')
|
||||
$(appBlock).addClass('cp-app-drive-new-doc')
|
||||
|
||||
|
||||
$(grid).append(appBlock);
|
||||
$(appBlock).on('click', () => select(app))
|
||||
});
|
||||
|
||||
var save = blocks.activeButton('primary', '', Messages.settings_save, function (done) {
|
||||
sFrameChan.query('Q_ADMIN_RPC', {
|
||||
cmd: 'ADMIN_DECREE',
|
||||
data: ['DISABLE_APPS', availableApps]
|
||||
}, function (e, response) {
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
$input.val('');
|
||||
console.error(e, response);
|
||||
done(false);
|
||||
return;
|
||||
}
|
||||
flushCache();
|
||||
done(true);
|
||||
UI.log(Messages._getKey('ui_saved', [Messages.admin_appSelection]));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
let form = blocks.form([
|
||||
grid
|
||||
], blocks.nav([save]));
|
||||
|
||||
var elem = document.createElement('div');
|
||||
elem.setAttribute('id', 'cp-loading');
|
||||
|
||||
let frame = h('div.configscreen', {style: 'width: 70%; height: 75%; background-color: white'}, form)
|
||||
|
||||
elem.append(frame)
|
||||
|
||||
var built = false;
|
||||
|
||||
|
||||
return function () {
|
||||
built = true;
|
||||
var intr;
|
||||
var append = function () {
|
||||
if (!document.body) { return; }
|
||||
clearInterval(intr);
|
||||
document.body.appendChild(elem);
|
||||
};
|
||||
intr = setInterval(append, 100);
|
||||
append();
|
||||
};
|
||||
});
|
||||
|
|
@ -29,11 +29,11 @@ define([
|
|||
Messages.admin_appsTitle = "Choose your applications"
|
||||
Messages.admin_appsHint = "Choose which apps are available to users on your instance."
|
||||
Messages.admin_cat_apps = "Apps"
|
||||
// if (LocalStore.isLoggedIn()) {
|
||||
// // already logged in, redirect to drive
|
||||
// document.location.href = '/drive/';
|
||||
// return;
|
||||
// }
|
||||
if (LocalStore.isLoggedIn()) {
|
||||
// already logged in, redirect to drive
|
||||
document.location.href = '/drive/';
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// text and password input fields
|
||||
|
@ -62,15 +62,28 @@ define([
|
|||
}
|
||||
}
|
||||
|
||||
var bloop = function (sendAdminDecree) {
|
||||
var showScreen = function (sendAdminDecree) {
|
||||
|
||||
const blocks = Sidebar.blocks;
|
||||
var grid = AppConfigScreen
|
||||
var availableApps = AppConfigScreen[0]
|
||||
var grid = AppConfigScreen[1]
|
||||
|
||||
var save = blocks.activeButton('primary', '', Messages.settings_save, function (done) {
|
||||
console.log('hello!');
|
||||
console.log(sendAdminDecree)
|
||||
sendAdminDecree()
|
||||
sendAdminDecree('DISABLE_APPS', availableApps, function (e, response) {
|
||||
|
||||
if (e || response.error) {
|
||||
UI.warn(Messages.error);
|
||||
$input.val('');
|
||||
console.error(e, response);
|
||||
done(false);
|
||||
return;
|
||||
}
|
||||
// flushCache();
|
||||
done(true);
|
||||
UI.log(Messages._getKey('ui_saved', [Messages.admin_appSelection]));
|
||||
window.location.href = '/drive/';
|
||||
|
||||
})
|
||||
UI.log('Messages._getKey(, [Messages.admin_appSelection])');
|
||||
});
|
||||
|
||||
|
@ -85,43 +98,156 @@ define([
|
|||
elem.append(frame)
|
||||
|
||||
built = true;
|
||||
var intr;
|
||||
var append = function () {
|
||||
if (!document.body) { return; }
|
||||
clearInterval(intr);
|
||||
document.body.appendChild(elem);
|
||||
};
|
||||
intr = setInterval(append, 100);
|
||||
append();
|
||||
var intr;
|
||||
var append = function () {
|
||||
if (!document.body) { return; }
|
||||
clearInterval(intr);
|
||||
document.body.appendChild(elem);
|
||||
};
|
||||
intr = setInterval(append, 100);
|
||||
append();
|
||||
|
||||
|
||||
// return function () {
|
||||
// built = true;
|
||||
// var intr;
|
||||
// var append = function () {
|
||||
// if (!document.body) { return; }
|
||||
// clearInterval(intr);
|
||||
// document.body.appendChild(elem);
|
||||
// };
|
||||
// intr = setInterval(append, 100);
|
||||
// append();
|
||||
// };
|
||||
}
|
||||
|
||||
var registerClick = function () {
|
||||
// AppConfigScreen
|
||||
// console.log(AppConfigScreen)
|
||||
// var sendAdminDecree = 'bleeop'
|
||||
let sendAdminDecree = function (command, data, callback) {
|
||||
console.log('belp')
|
||||
// var params = ['ADMIN_DECREE', [command, data]];
|
||||
// rpc.send('ADMIN', params, callback)
|
||||
};
|
||||
bloop(sendAdminDecree)
|
||||
|
||||
// AppConfigScreen
|
||||
// console.log(AppConfigScreen)
|
||||
// console.log(AppConfigScreen)
|
||||
var uname = $uname.val().trim();
|
||||
// trim whitespace surrounding the username since it is otherwise included in key derivation
|
||||
// most people won't realize that its presence is significant
|
||||
$uname.val(uname);
|
||||
|
||||
var passwd = $passwd.val();
|
||||
var confirmPassword = $confirm.val();
|
||||
|
||||
if (!token) { token = $token.val().trim(); }
|
||||
|
||||
var shouldImport = false;
|
||||
var doesAccept;
|
||||
try {
|
||||
// if this throws there's either a horrible bug (which someone will report)
|
||||
// or the instance admins did not configure a terms page.
|
||||
doesAccept = true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (Cred.isEmail(uname) && !I_REALLY_WANT_TO_USE_MY_EMAIL_FOR_MY_USERNAME) {
|
||||
var emailWarning = [
|
||||
Messages.register_emailWarning0,
|
||||
br(), br(),
|
||||
Messages.register_emailWarning1,
|
||||
br(), br(),
|
||||
Messages.register_emailWarning2,
|
||||
br(), br(),
|
||||
Messages.register_emailWarning3,
|
||||
];
|
||||
|
||||
Feedback.send("EMAIL_USERNAME_WARNING", true);
|
||||
|
||||
return void UI.confirm(emailWarning, function (yes) {
|
||||
if (!yes) { return; }
|
||||
I_REALLY_WANT_TO_USE_MY_EMAIL_FOR_MY_USERNAME = true;
|
||||
registerClick();
|
||||
});
|
||||
}
|
||||
|
||||
/* basic validation */
|
||||
if (!Cred.isLongEnoughPassword(passwd)) {
|
||||
var warning = Messages._getKey('register_passwordTooShort', [
|
||||
Cred.MINIMUM_PASSWORD_LENGTH
|
||||
]);
|
||||
return void UI.alert(warning);
|
||||
}
|
||||
|
||||
if (passwd !== confirmPassword) { // do their passwords match?
|
||||
return void UI.alert(Messages.register_passwordsDontMatch);
|
||||
}
|
||||
|
||||
if (Pages.customURLs.terms && !doesAccept) { // do they accept the terms of service? (if they exist)
|
||||
return void UI.alert(Messages.register_mustAcceptTerms);
|
||||
}
|
||||
|
||||
let startOnboarding = function (network, proxy) {
|
||||
Rpc.create(network, proxy.edPrivate, proxy.edPublic, function (e, rpc) {
|
||||
if (e) {
|
||||
// TODO: handle error
|
||||
return;
|
||||
}
|
||||
|
||||
let sendAdminDecree = function (command, data, callback) {
|
||||
var params = ['ADMIN_DECREE', [command, data]];
|
||||
rpc.send('ADMIN', params, callback)
|
||||
};
|
||||
|
||||
showScreen(sendAdminDecree)
|
||||
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
setTimeout(function () {
|
||||
var span = h('span', [
|
||||
h('h2', [
|
||||
h('i.fa.fa-warning'),
|
||||
' ',
|
||||
Messages.register_warning,
|
||||
]),
|
||||
Messages.register_warning_note
|
||||
]);
|
||||
|
||||
UI.confirm(span,
|
||||
function (yes) {
|
||||
if (!yes) { return; }
|
||||
|
||||
Login.loginOrRegisterUI({
|
||||
uname,
|
||||
passwd,
|
||||
isRegister: true,
|
||||
onOTP: UI.getOTPScreen,
|
||||
shouldImport,
|
||||
cb: function (data) {
|
||||
var proxy = data.proxy;
|
||||
if (!proxy || !proxy.edPublic) { UI.alert(Messages.error); return true; }
|
||||
|
||||
Rpc.createAnonymous(data.network, function (e, call) {
|
||||
if (e) { UI.alert(Messages.error); return console.error(e); }
|
||||
var anon_rpc = call;
|
||||
|
||||
anon_rpc.send('ADD_FIRST_ADMIN', {
|
||||
token: token,
|
||||
edPublic: proxy.edPublic
|
||||
}, function (e) {
|
||||
if (e) { UI.alert(Messages.error); return console.error(e); }
|
||||
// bloop(sendAdminDecree)
|
||||
|
||||
startOnboarding(data.network, proxy);
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}, {
|
||||
ok: Messages.register_writtenPassword,
|
||||
cancel: Messages.register_cancel,
|
||||
/* If we're certain that we aren't using these "*Class" APIs
|
||||
anywhere else then we can deprecate them and make this a
|
||||
custom modal in common-interface (or here). */
|
||||
cancelClass: 'btn.btn-cancel.btn-register',
|
||||
okClass: 'btn.btn-danger.btn-register',
|
||||
reverseOrder: true,
|
||||
done: function ($dialog) {
|
||||
$dialog.find('> div').addClass('half');
|
||||
},
|
||||
});
|
||||
}, 150);
|
||||
|
||||
// let sendAdminDecree = function (command, data, callback) {
|
||||
|
||||
// };
|
||||
// bloop(sendAdminDecree)
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in a new issue