Add a manage page and add the initial settings to be managed, needs more work

pull/9666/head
Bradley Hilton 8 years ago
parent e9815fd785
commit 361a645ed3
No known key found for this signature in database
GPG Key ID: 0666B2C24C43C358
  1. 7
      packages/rocketchat-i18n/i18n/en.i18n.json
  2. 6
      packages/rocketchat-lib/client/lib/RestApiClient.js
  3. 4
      packages/rocketchat-rocketlets/assets/stylesheets/rocketlets.css
  4. 230
      packages/rocketchat-rocketlets/client/admin/rocketletManage.html
  5. 111
      packages/rocketchat-rocketlets/client/admin/rocketletManage.js
  6. 2
      packages/rocketchat-rocketlets/client/admin/rocketlets.html
  7. 12
      packages/rocketchat-rocketlets/client/admin/rocketlets.js
  8. 4
      packages/rocketchat-rocketlets/client/communication/websockets.js
  9. 15
      packages/rocketchat-rocketlets/client/orchestrator.js
  10. 10
      packages/rocketchat-rocketlets/package.js
  11. 106
      packages/rocketchat-rocketlets/server/communication/rest.js
  12. 2
      packages/rocketchat-rocketlets/server/orchestrator.js

@ -227,6 +227,7 @@
"Audio_message": "Audio message",
"Auth_Token": "Auth Token",
"Author": "Author",
"Author_Information": "Author Information",
"Authorization_URL": "Authorization URL",
"Authorize": "Authorize",
"Auto_Load_Images": "Auto Load Images",
@ -263,6 +264,7 @@
"Back_to_integrations": "Back to integrations",
"Back_to_integration_detail": "Back to the integration detail",
"Back_to_login": "Back to login",
"Back_to_Manage_Rocketlets": "Back to Manage Rocketlets",
"Back_to_permissions": "Back to permissions",
"Backup_codes": "Backup codes",
"Beta_feature_Depends_on_Video_Conference_to_be_enabled": "Beta feature. Depends on Video Conference to be enabled.",
@ -1340,6 +1342,7 @@
"manage-sounds": "Manage Sounds",
"manage-sounds_description": "Permission to manage the server sounds",
"Manage_Rocketlets": "Manage Rocketlets",
"Manage_the_Rocketlet": "Manage the Rocketlet",
"mention-all": "Mention All",
"mention-all_description": "Permission to use the @all mention",
"mute-user": "Mute User",
@ -1525,6 +1528,8 @@
"Restart_the_server": "Restart the server",
"Retry_Count": "Retry Count",
"Rocketlets": "Rocketlets",
"Rocketlet_Information": "Rocketlet Information",
"Rocketlets_Settings": "Rocketlet's Settings",
"Role": "Role",
"Role_Editing": "Role Editing",
"Role_removed": "Role removed",
@ -1726,6 +1731,7 @@
"Success": "Success",
"Success_message": "Success message",
"Sunday": "Sunday",
"Support": "Support",
"Survey": "Survey",
"Survey_instructions": "Rate each question according to your satisfaction, 1 meaning you are completely unsatisfied and 5 meaning you are completely satisfied.",
"Symbols": "Symbols",
@ -1960,6 +1966,7 @@
"WebRTC_Enable_Private": "Enable for Private Channels",
"WebRTC_Servers": "STUN/TURN Servers",
"WebRTC_Servers_Description": "A list of STUN and TURN servers separated by comma.<br/>Username, password and port are allowed in the format `username:password@stun:host:port` or `username:password@turn:host:port`.",
"Website": "Website",
"Wednesday": "Wednesday",
"Welcome": "Welcome <em>%s</em>.",
"Welcome_to_the": "Welcome to the",

@ -3,6 +3,10 @@ RocketChat.API = {
return RocketChat.API._jqueryCall('GET', endpoint, params);
},
post(endpoint, params, body) {
return RocketChat.API._jqueryCall('POST', endpoint, params, body);
},
_jqueryCall(method, endpoint, params, body) {
let query = '';
if (params) {
@ -22,7 +26,7 @@ RocketChat.API = {
'X-User-Id': localStorage['Meteor.userId'],
'X-Auth-Token': localStorage['Meteor.loginToken']
},
data: body,
data: JSON.stringify(body),
success: function _rlGetSuccess(result) {
resolve(result);
},

@ -0,0 +1,4 @@
input.rocketlet-author-name {
width: auto !important;
display: inline-block !important;
}

@ -0,0 +1,230 @@
<template name="rocketletManage">
<div class="main-content-flex">
{{#with rocketlet}}
<section class="page-container page-list page-settings flex-tab-main-content">
<header class="fixed-title">
{{> burger}}
<a href="{{pathFor "rocketlets"}}" title="{{_ "Back_to_Manage_Rocketlets"}}">
<i class="icon-left-open"></i>
</a> &nbsp;
<h2>
<span class="room-title">{{_ "Manage_the_Rocketlet"}}: "{{name}}"</span>
</h2>
</header>
<div class="content background-transparent-dark">
{{#requiresPermission 'manage-rocketlets'}}
<div class="rocket-form">
<div class="section">
<div class="section-title">
<div class="section-title-text">
{{_ "Rocketlet_Information"}}
</div>
<div class="section-title-right">
<button class="button primary collapse">
<span>{{_ "Collapse"}}</span>
</button>
</div>
</div>
<div class="section-content">
<div class="input-line double-col">
<label>{{_ "Name"}}</label>
<div>
<input class="input-monitor" type="text" disabled value="{{name}}">
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Description"}}</label>
<div>
<input class="input-monitor" type="text" disabled value="{{description}}">
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Version"}}</label>
<div>
<input class="input-monitor" type="text" disabled value="{{version}}">
</div>
</div>
<div class="input-line double-col">
<label>{{_ "Author_Information"}}</label>
<div>
<input class="input-monitor rocketlet-author-name" type="text" disabled value="{{author.name}}">
{{#if author.homepage}}<a class="button" target="_blank" href="{{author.homepage}}">{{_ "Website"}}</a>{{/if}}
{{#if author.support}}<a class="button" target="_blank" href="{{author.support}}">{{_ "Support"}}</a>{{/if}}
</div>
</div>
</div>
</div>
<div class="section section-collapsed">
<div class="section-title">
<div class="section-title-text">
{{_ "Rocketlets_Settings"}}
</div>
<div class="section-title-right">
<button class="button save">{{_ "Save"}}</button>
<button class="button primary expand">
<span>{{_ "Expand"}}</span>
</button>
</div>
</div>
<div class="section-content">
{{#each settings}}
<div class="input-line double-col">
<label class="setting-label">{{_ i18nLabel}}</label>
<div class="setting-field">
{{#if $eq type 'string'}}
{{#if multiline}}
<textarea class="input-monitor" name="{{id}}" rows="4" style="height: auto" {{isDisabled}}>{{value}}</textarea>
{{else}}
<input class="input-monitor" type="text" name="{{id}}" value="{{value}}" placeholder="{{placeholder}}" {{isDisabled}}/>
{{/if}}
{{/if}}
{{#if $eq type 'relativeUrl'}}
<input class="input-monitor" type="text" name="{{id}}" value="{{relativeUrl value}}" placeholder="{{placeholder}}" {{isDisabled}} {{isReadonly}}/>
{{/if}}
{{#if $eq type 'password'}}
<input class="input-monitor" type="password" name="{{id}}" value="{{value}}" placeholder="{{placeholder}}" {{isDisabled}}/>
{{/if}}
{{#if $eq type 'int'}}
<input class="input-monitor" type="number" name="{{id}}" value="{{value}}" placeholder="{{placeholder}}" {{isDisabled}}/>
{{/if}}
{{#if $eq type 'boolean'}}
<label><input class="input-monitor" type="radio" name="{{id}}" value="1" checked="{{$eq value true}}" {{isDisabled}}/> {{_ "True"}}</label>
<label><input class="input-monitor" type="radio" name="{{id}}" value="0" checked="{{$eq value false}}" {{isDisabled}}/> {{_ "False"}}</label>
{{/if}}
{{#if $eq type 'select'}}
<div class="select-arrow">
<i class="icon-down-open secondary-font-color"></i>
</div>
<select class="input-monitor" name="{{id}}" {{isDisabled}}>
{{#each values}}
<option value="{{key}}" selected="{{selectedOption ../id key}}">{{_ i18nLabel}}</option>
{{/each}}
</select>
{{/if}}
{{#if $eq type 'language'}}
<div class="select-arrow">
<i class="icon-down-open secondary-font-color"></i>
</div>
<select class="input-monitor" name="{{id}}" {{isDisabled}}>
{{#each languages}}
<option value="{{key}}" selected="{{appLanguage key}}" dir="auto">{{name}}</option>
{{/each}}
</select>
{{/if}}
{{#if $eq type 'color'}}
<div class="horizontal">
{{#if $eq editor 'color'}}
<div class="flex-grow-1">
<input class="input-monitor colorpicker-input" type="text" name="{{id}}" value="{{value}}" autocomplete="off" {{isDisabled}}/>
<span class="colorpicker-swatch border-component-color" style="background-color: {{value}}"></span>
</div>
{{/if}}
{{#if $eq editor 'expression'}}
<div class="flex-grow-1">
<input class="input-monitor" type="text" name="{{id}}" value="{{value}}" {{isDisabled}}/>
</div>
{{/if}}
<div class="color-editor">
<div class="select-arrow">
<i class="icon-down-open secondary-font-color"></i>
</div>
<select name="color-editor">
{{#each allowedTypes}}
<option value="{{.}}" selected="{{$eq ../editor .}}">{{_ .}}</option>
{{/each}}
</select>
</div>
</div>
<div class="settings-description">Variable name: {{getColorVariable id}}</div>
{{/if}}
{{#if $eq type 'font'}}
<input class="input-monitor" type="text" name="{{id}}" value="{{value}}" {{isDisabled}}/>
{{/if}}
{{#if $eq type 'code'}}
{{#if isDisabled.disabled}}
{{> CodeMirror name=id options=(getEditorOptions true) code=(i18nDefaultValue) }}
{{else}}
<div class="code-mirror-box" data-editor-id="{{id}}">
<div class="title">
{{label}}
</div>
{{> CodeMirror name=id options=getEditorOptions code=value }}
{{setEditorOnBlur id}}
<div class="buttons">
<button class="button primary button-fullscreen">Full Screen</button>
<button class="button primary button-restore">Exit Full Screen</button>
</div>
</div>
{{/if}}
{{/if}}
{{#if $eq type 'action'}}
{{#if hasChanges section}}
<span style="line-height: 40px" class="secondary-font-color">{{_ "Save_to_enable_this_action"}}</span>
{{else}}
<button type="button" class="button primary action" data-setting="{{id}}" data-action="{{value}}" {{isDisabled}}>{{_ actionText}}</button>
{{/if}}
{{/if}}
{{#if $eq type 'asset'}}
{{#if value.url}}
<div class="settings-file-preview">
<div class="preview" style="background-image:url({{value.url}}?_dc={{random}});"></div>
<div class="action">
<button type="button" class="button danger delete-asset"><i class="icon-trash secondary-font-color"></i>{{_ 'Delete'}}</button>
</div>
</div>
{{else}}
<div class="settings-file-preview">
<div class="preview no-file background-transparent-light secondary-font-color"><i class="icon-upload secondary-font-color"></i></div>
<div class="action">
<div class="button primary">{{_ 'Select_file'}}
<input type="file" accept="{{assetAccept fileConstraints}}" />
</div>
</div>
</div>
{{/if}}
{{/if}}
{{#if $eq type 'roomPick'}}
<div>
{{> inputAutocomplete settings=autocompleteRoom id=id name=id class="search autocomplete" autocomplete="off" disabled=isDisabled.disabled}}
<ul class="selected-rooms">
{{#each selectedRooms}}
<li class="remove-room" data-setting={{../id}}>{{name}} <i class="icon-cancel secondary-font-color"></i></li>
{{/each}}
</ul>
</div>
{{/if}}
{{#if i18nDescription}}
<div class="settings-description">{{{parseDescription i18nDescription}}}</div>
{{/if}}
{{#if i18nAlert}}
<div class="settings-alert pending-color pending-background pending-border"><i class="icon-attention secondary-font-color"></i>{{{_ i18nAlert}}}</div>
{{/if}}
</div>
</div>
{{/each}}
</div>
</div>
</div>
{{/requiresPermission}}
</div>
</section>
{{/with}}
{{#with flexData}}
{{> flexTabBar}}
{{/with}}
</div>
</template>

@ -0,0 +1,111 @@
Template.rocketletManage.onCreated(function() {
const instance = this;
this.ready = new ReactiveVar(false);
this.rocketlet = new ReactiveVar({});
this.settings = new ReactiveVar({});
const id = FlowRouter.getParam('rocketletId');
console.log(id);
const got = { info: false, settings: false };
RocketChat.API.get(`rocketlets/${ id }`).then((result) => {
instance.rocketlet.set(result.rocketlet);
console.log(result.rocketlet);
got.info = true;
if (got.info && got.settings) {
this.ready.set(true);
}
});
RocketChat.API.get(`rocketlets/${ id }/settings`).then((result) => {
Object.keys(result.settings).forEach((k) => {
result.settings[k].oldValue = result.settings[k].value;
});
instance.settings.set(result.settings);
console.log(instance.settings.get());
got.settings = true;
if (got.info && got.settings) {
this.ready.set(true);
}
});
});
Template.rocketletManage.helpers({
isReady() {
if (Template.instance().ready != null) {
return Template.instance().ready.get();
}
return false;
},
rocketlet() {
return Template.instance().rocketlet.get();
},
settings() {
return Object.values(Template.instance().settings.get());
},
parseDescription(i18nDescription) {
const item = RocketChat.Markdown.parseNotEscaped({ html: t(i18nDescription) });
item.tokens.forEach((t) => item.html = item.html.replace(t.token, t.text));
return item.html;
}
});
Template.rocketletManage.events({
'click .expand': (e) => {
$(e.currentTarget).closest('.section').removeClass('section-collapsed');
$(e.currentTarget).closest('button').removeClass('expand').addClass('collapse').find('span').text(TAPi18n.__('Collapse'));
$('.CodeMirror').each((index, codeMirror) => codeMirror.CodeMirror.refresh());
},
'click .collapse': (e) => {
$(e.currentTarget).closest('.section').addClass('section-collapsed');
$(e.currentTarget).closest('button').addClass('expand').removeClass('collapse').find('span').text(TAPi18n.__('Expand'));
},
'click .save': (e, t) => {
const toSave = [];
Object.keys(t.settings.get()).forEach((k) => {
const setting = t.settings.get()[k];
if (setting.hasChanged) {
toSave.push(setting);
}
});
if (toSave.length === 0) {
console.log('Nothing to save..');
return;
}
const rocketletId = FlowRouter.getParam('rocketletId');
RocketChat.API.post(`rocketlets/${ rocketletId }/settings`, undefined, { settings: toSave }).then((result) => {
console.log('Updating results:', result);
});
},
'change .input-monitor, keyup .input-monitor': _.throttle(function(e, t) {
let value = _.trim($(e.target).val());
switch (this.type) {
case 'int':
value = parseInt(value);
break;
case 'boolean':
value = value === '1';
}
const setting = t.settings.get()[this.id];
setting.value = value;
if (setting.oldValue !== setting.value) {
t.settings.get()[this.id].hasChanged = true;
}
}, 500)
});

@ -11,7 +11,7 @@
{{#requiresPermission 'manage-rocketlets'}}
{{#each rocketlets}}
<ul class="sound-info row-link">
<li>{{name}}&nbsp;<i class="icon-play-circled"></i></li>
<li>{{name}}&nbsp;<i class="icon-wrench manage"></i></li>
</ul>
{{/each}}
{{/requiresPermission}}

@ -20,3 +20,15 @@ Template.rocketlets.helpers({
return Template.instance().rocketlets.get();
}
});
Template.rocketlets.events({
'click .manage'() {
const rl = this;
if (rl && rl.id) {
FlowRouter.go(`/admin/rocketlets/${ rl.id }`);
} else {
// show an error ? I don't think this should ever happen
}
}
});

@ -11,8 +11,8 @@ export class RocketletWebsocketReceiver {
}
onRocketletAdded(rocketletId) {
RocketChat.API.get(`rocketlets/${ rocketletId }`).then((result) => {
this.orch.parseAndLoadLanguages(result.rocketlet.languages);
RocketChat.API.get(`rocketlets/${ rocketletId }/languages`).then((result) => {
this.orch.parseAndLoadLanguages(result.languages);
});
}

@ -5,7 +5,13 @@ class RocketletClientOrchestrator {
this.ws = new RocketletWebsocketReceiver(this);
this._addAdminMenuOption();
setTimeout(() => this._loadLanguages(), 500);
const loadLangs = setInterval(() => {
if (Meteor.user()) {
clearInterval(loadLangs);
this._loadLanguages();
}
}, 50);
}
getWsListener() {
@ -56,3 +62,10 @@ FlowRouter.route('/admin/rocketlets', {
BlazeLayout.render('main', { center: 'rocketlets' });
}
});
FlowRouter.route('/admin/rocketlets/:rocketletId', {
name: 'rocketlet-manage',
action() {
BlazeLayout.render('main', { center: 'rocketletManage' });
}
});

@ -11,7 +11,7 @@ Package.onUse(function(api) {
'templating'
]);
api.use(['reactive-var', 'kadira:flow-router'], 'client');
api.use(['reactive-var', 'kadira:flow-router', 'underscore'], 'client');
api.addFiles('lib/Rocketlets.js', ['client', 'server']);
@ -65,7 +65,9 @@ Package.onUse(function(api) {
// Client Admin Management
api.addFiles([
'client/admin/rocketlets.html',
'client/admin/rocketlets.js'
'client/admin/rocketlets.js',
'client/admin/rocketletManage.html',
'client/admin/rocketletManage.js'
], 'client');
api.addFiles('assets/stylesheets/rocketlets.css', 'client');
@ -79,6 +81,6 @@ Package.onUse(function(api) {
Npm.depends({
'busboy': '0.2.13',
'temporary-rocketlets-server': '0.1.26',
'temporary-rocketlets-ts-definition': '0.6.26'
'temporary-rocketlets-server': '0.1.27',
'temporary-rocketlets-ts-definition': '0.6.27'
});

@ -76,7 +76,6 @@ export class RocketletsRestApi {
if (prl) {
const info = prl.getInfo();
info.languages = prl.getStorageItem().languageFiles;
return { success: true, rocketlet: info };
} else {
@ -95,5 +94,110 @@ export class RocketletsRestApi {
return { success: false, item };
}
});
this.api.addRoute(':id/languages', { authRequired: true }, {
get() {
console.log(`Getting ${ this.urlParams.id }'s languages..`);
const prl = manager.getOneById(this.urlParams.id);
if (prl) {
const languages = prl.getStorageItem().languageFiles || {};
return { success: true, languages };
} else {
return RocketChat.API.v1.notFound(`No Rocketlet found by the id of: ${ this.urlParams.id }`);
}
}
});
this.api.addRoute(':id/settings', { authRequired: true }, {
get() {
console.log(`Getting ${ this.urlParams.id }'s settings..`);
const prl = manager.getOneById(this.urlParams.id);
if (prl) {
const settings = Object.assign({}, prl.getStorageItem().settings);
Object.keys(settings).forEach((k) => {
if (settings[k].hidden) {
delete settings[k];
}
});
return { success: true, settings };
} else {
return RocketChat.API.v1.notFound(`No Rocketlet found by the id of: ${ this.urlParams.id }`);
}
},
post() {
console.log(`Updating ${ this.urlParams.id }'s settings..`);
if (!this.bodyParams || !this.bodyParams.settings) {
return RocketChat.API.v1.failure('The settings to update must be present.');
}
const prl = manager.getOneById(this.urlParams.id);
if (!prl) {
return RocketChat.API.v1.notFound(`No Rocketlet found by the id of: ${ this.urlParams.id }`);
}
const settings = prl.getStorageItem().settings;
const updated = [];
this.bodyParams.settings.forEach((s) => {
if (settings[s.id]) {
Promise.await(manager.getSettingsManager().updateRocketletSetting(this.urlParams.id, s));
// Updating?
updated.push(s);
}
});
return { success: true, updated };
}
});
this.api.addRoute(':id/settings/:settingId', { authRequired: true }, {
get() {
console.log(`Getting the Rocketlet ${ this.urlParams.id }'s setting ${ this.urlParams.settingId }`);
try {
const setting = manager.getSettingsManager().getRocketletSetting(this.urlParams.id, this.urlParams.settingId);
return {
success: true,
setting
};
} catch (e) {
if (e.message.includes('No setting found')) {
return RocketChat.API.v1.notFound(`No Setting found on the Rocketlet by the id of: "${ this.urlParams.settingId }"`);
} else if (e.message.includes('No Rocketlet found')) {
return RocketChat.API.v1.notFound(`No Rocketlet found by the id of: ${ this.urlParams.id }`);
} else {
return RocketChat.API.v1.failure(e.message);
}
}
},
post() {
console.log(`Updating the Rocketlet ${ this.urlParams.id }'s setting ${ this.urlParams.settingId }`);
if (!this.bodyParams.setting) {
return RocketChat.API.v1.failure('Setting to update to must be present on the posted body.');
}
try {
Promise.await(manager.getSettingsManager().updateRocketletSetting(this.urlParams.id, this.bodyParams.setting));
return { success: true };
} catch (e) {
if (e.message.includes('No setting found')) {
return RocketChat.API.v1.notFound(`No Setting found on the Rocketlet by the id of: "${ this.urlParams.settingId }"`);
} else if (e.message.includes('No Rocketlet found')) {
return RocketChat.API.v1.notFound(`No Rocketlet found by the id of: ${ this.urlParams.id }`);
} else {
return RocketChat.API.v1.failure(e.message);
}
}
}
});
}
}

@ -64,6 +64,6 @@ Meteor.startup(function _rocketletServerOrchestrator() {
global.Rocketlets = new RocketletServerOrchestrator();
global.Rocketlets.getManager().load()
.then(() => console.log('...done! :)'))
.then(() => console.log('...done! ;)'))
.catch((err) => console.warn('...failed!', err));
});

Loading…
Cancel
Save