diff --git a/.eslintrc.js b/.eslintrc.js index 8cc36d30..587057d2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,7 +2,6 @@ module.exports = { "env": { "node": true, - "browser": true, "commonjs": true, "es6": true, }, @@ -21,6 +20,18 @@ module.exports = { "no-invalid-this": "off", }, }, + { + "files": ["packages/master/web/**/*.jsx"], + "env": { + "browser": true, + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "jsx": true, + }, + }, + }, ], "rules": { @@ -44,6 +55,8 @@ module.exports = { { "arrays": "always-multiline", "objects": "always-multiline", + "imports": "always-multiline", + "exports": "always-multiline", "functions": "only-multiline", }, ], @@ -229,7 +242,7 @@ module.exports = { "semi": "error", "semi-spacing": "error", "semi-style": "off", - "sort-imports": "error", + "sort-imports": "off", "sort-keys": "off", "sort-vars": "error", "space-before-blocks": "warn", diff --git a/packages/master/web/components/data-table.jsx b/packages/master/web/components/data-table.jsx index 7c3239cf..b43d3b53 100644 --- a/packages/master/web/components/data-table.jsx +++ b/packages/master/web/components/data-table.jsx @@ -10,7 +10,7 @@ const { Panel } = Collapse; /** * Display data in table. Allows for editing data by clicking rows in the table. * Basic usage: - * + * * DataFunction - String, corresponds to export from util/wslink. Same as ws message name. * columns - Array of objects, optional, same as . Note the lowercase c * Editable - Boolean - Allow editing @@ -25,7 +25,7 @@ class DataTable extends Component { this.state = { pagination: { pageSize: 100, - current: 1 + current: 1, }, filters: this.props.Filters, sorter: {}, @@ -41,16 +41,16 @@ class DataTable extends Component { async getData(pagination, filters, sorter) { /* - pagination = { - pageSize: 10, // items per page - current: 1, // page number - total: 123, // Total amount of items - } - filters = { - ReportId: 123 - } - sorter = {} - */ + pagination = { + pageSize: 10, // items per page + current: 1, // page number + total: 123, // Total amount of items + } + filters = { + ReportId: 123 + } + sorter = {} + */ pagination = pagination || this.state.pagination; filters = filters || this.state.filters; sorter = sorter || this.state.sorter; @@ -77,27 +77,28 @@ class DataTable extends Component { } showEditModal(record, index) { - if (!this.state.showEditModal) + if (!this.state.showEditModal) { this.setState({ record: record, showEditModal: true, }); + } } + hideEditModal() { this.setState({ showEditModal: false, inserting: false, - record: {} + record: {}, }); } + async onEditFinish(values) { console.log("Finished editing", values); if (this.state.inserting) { // Create new record - var status = {}; await this.props.AddRecord.insert(values); } else { // Edit existing record, capable of doing sparse updates - var status = {}; await this.props.Editable.insert(values); } this.setState({ @@ -108,6 +109,7 @@ class DataTable extends Component { // Force refresh of data this.getData(); } + async deleteRecord(record) { delete record.key; // Delete "null" values as they seem to cause deletes to fail silently when the value is null and field type is float @@ -130,15 +132,16 @@ class DataTable extends Component { // Force refresh of data this.getData(); } + render() { let tableProps = { ...this.props.TableProps || {}, onRow: (record, rowIndex) => { /* - To allow for extending functionality, we take a TableProps argument from props and pass it through to the Table component. - Since we are also using onRow internaly here, we handle the externally provided events ourselves by passing along the event - object. This way the end user isn't affected by our event handlers. - */ + To allow for extending functionality, we take a TableProps argument from props and pass it through to the Table component. + Since we are also using onRow internaly here, we handle the externally provided events ourselves by passing along the event + object. This way the end user isn't affected by our event handlers. + */ let externalOnRow = { ...(this.props.TableProps && this.props.TableProps.onRow && this.props.TableProps.onRow(record, rowIndex)), }; @@ -163,7 +166,7 @@ class DataTable extends Component { if (externalOnRow.onMouseLeave) externalOnRow.onMouseLeave(event); }, }; - } + }, }; let columns = this.props.columns || this.inferColumns(this.state.tableData); if (this.props.DeleteRecord && !columns.find(x => x.dataIndex === "notInUse")) columns.push({ @@ -179,7 +182,7 @@ class DataTable extends Component { this.deleteRecord(record); }} > - Slett permanent + Slett permanent } trigger="click" @@ -187,7 +190,7 @@ class DataTable extends Component { - + , }); return <> @@ -195,27 +198,25 @@ class DataTable extends Component {
{ - return { - ...x, // Add a 4th parameter to the stock render function with all of our table data - render: x.render ? (...originalParams) => x.render(...originalParams, this.state.tableData) : undefined - }; - })} + columns={columns.map(x => ({ + ...x, // Add a 4th parameter to the stock render function with all of our table data + render: x.render ? (...originalParams) => x.render(...originalParams, this.state.tableData) : undefined, + }))} dataSource={this.state.tableData} pagination={this.state.pagination.total > 10 ? this.state.pagination : false} onChange={this.getData.bind(this)} /> {this.props.AddRecord && } @@ -243,8 +244,8 @@ class DataTable extends Component { ( (this.state.inserting && this.props.AddRecord && this.props.AddRecord.fields) // Use externally supplied form input fields - || this.props.columns.filter && this.props.columns.filter(x => x.dataIndex !== "notInUse") // Generate input fields from external columns - || this.inferColumns(this.state.tableData)).map(col => { // Generate input fields from autogenerated columns (only works with existing data) + || this.props.columns.filter && this.props.columns.filter(x => x.dataIndex !== "notInUse") // Generate input fields from external columns + || this.inferColumns(this.state.tableData)).map(col => { // Generate input fields from autogenerated columns (only works with existing data) console.log(this.state.record); return {col.renderEdit && col.renderEdit(this.state.record[col.dataIndex], this.state.record, undefined /* Non spec compliant, is supposed to be index of record */, this.state.tableData) || ; } + inferColumns(data) { // Look at the data and find columns and how to display them. Can be overridden by props.Columns if more customization is needed let columns = []; diff --git a/packages/master/web/layout/index.jsx b/packages/master/web/layout/index.jsx index 6685dae3..76f6ce24 100644 --- a/packages/master/web/layout/index.jsx +++ b/packages/master/web/layout/index.jsx @@ -30,7 +30,7 @@ class SiteLayout extends Component { setTags: [], location, siteSettings: {}, - factoryModel: [] + factoryModel: [], }; } @@ -153,9 +153,9 @@ class SiteLayout extends Component {
- Clusterio 2.0 web interface https://github.com/clusterio + Clusterio 2.0 web interface https://github.com/clusterio
diff --git a/packages/master/web/pages/dashboard.jsx b/packages/master/web/pages/dashboard.jsx index 7862eca7..4089dae5 100644 --- a/packages/master/web/pages/dashboard.jsx +++ b/packages/master/web/pages/dashboard.jsx @@ -1,10 +1,7 @@ import React, {Component} from "react"; export class Dashboard extends Component { - constructor(props){ - super(props); - } render(){ return

Dashboard

; } -} \ No newline at end of file +} diff --git a/packages/master/web/pages/instances/instance.jsx b/packages/master/web/pages/instances/instance.jsx index 83aa6b2b..7d85ab16 100644 --- a/packages/master/web/pages/instances/instance.jsx +++ b/packages/master/web/pages/instances/instance.jsx @@ -31,9 +31,11 @@ export class InstanceView extends Component { }; this.messagesEndRef = React.createRef(); } + navigate(url) { this.props.history.push(url); } + async componentDidMount() { let id = this.props.match.params.id; @@ -52,6 +54,7 @@ export class InstanceView extends Component { // this.messagesEndRef.current.scrollIntoView({ behavior: 'smooth' }) }; } + async getData() { let id = this.props.match.params.id; @@ -66,9 +69,10 @@ export class InstanceView extends Component { let config = await getInstanceConfig({ instance_id: Number(id) }); console.log(config); this.setState({ - config + config, }); } + async start() { this.setState({ starting: true, @@ -80,9 +84,10 @@ export class InstanceView extends Component { }); } catch (e) { notify("Error starting instance", "error", e.message); - return this.setState({ + this.setState({ starting: false, }); + return; } notify("Instance started"); this.setState({ @@ -90,6 +95,7 @@ export class InstanceView extends Component { }); this.getData(); } + async stop() { this.setState({ stopping: true, @@ -100,9 +106,10 @@ export class InstanceView extends Component { }); } catch (e) { notify("Error stopping instance", "error", e.message); - return this.setState({ + this.setState({ stopping: false, }); + return; } notify("Instance stopped"); this.setState({ @@ -110,6 +117,7 @@ export class InstanceView extends Component { }); this.getData(); } + async createSave() { this.setState({ creatingSave: true, @@ -120,15 +128,17 @@ export class InstanceView extends Component { }); } catch (e) { notify("Error creating save", "error", e.message); - return this.setState({ + this.setState({ creatingSave: false, }); + return; } notify("Save created", "success"); this.setState({ creatingSave: false, }); } + async onConfigEditFinish(values) { console.log("Completed config edit with", values); for (let k in values) { @@ -145,10 +155,11 @@ export class InstanceView extends Component { } notify("Set configuration fields", "success"); this.setState({ - editingConfig: false + editingConfig: false, }); this.getData(); } + render() { console.log(this.state); let { instance, slaves } = this.state; @@ -162,26 +173,26 @@ export class InstanceView extends Component { type="primary" onClick={this.start.bind(this)} > - Start server + Start server } {instance.status !== "stopped" && } {instance.status !== "running" && } { let result = await assignInstance({ instance_id: instance.id, slave_id: newSlaveId }); @@ -198,7 +209,7 @@ export class InstanceView extends Component { type={instance.status === "unassigned" ? "primary" : "default"} > - Delete this instance permanently + Delete this instance permanently } trigger="click" @@ -224,16 +235,16 @@ export class InstanceView extends Component {
; } + getInstanceConfigAsKvObject(config) { let object = {}; for (let group of config.serialized_config.groups) { - for (let fieldName in group.fields) { + for (let fieldName of Object.keys(group.fields)) { object[group.name + "." + fieldName] = group.fields[fieldName]; } } return object; } + renderConfig(config) { const layout = { labelCol: { @@ -291,7 +304,7 @@ export class InstanceView extends Component { {this.state.editingConfig ? : @@ -306,10 +319,10 @@ export class InstanceView extends Component { // orientation="left" >{group.name} {/* {Object.keys(group.fields).map(field => <> - {field}: - {JSON.stringify(group.fields[field])} -
- )} */} + {field}: + {JSON.stringify(group.fields[field])} +
+ )} */} {console.log(group)} {Object.keys(group.fields).map(field => )} @@ -344,15 +357,16 @@ class ObjectInput extends Component { super(props); console.log(this.props); /* - value: { - auto_pause: false, - tags: ["clusterio"] - }, - id: "factorio.settings", - onChange: f() - */ + value: { + auto_pause: false, + tags: ["clusterio"] + }, + id: "factorio.settings", + onChange: f() + */ this.state = {}; } + onChange(e, key) { if (e.target.type === "checkbox") { // if (e.target.checked !== undefined) { @@ -368,6 +382,7 @@ class ObjectInput extends Component { this.props.onChange(value); } } + render() { let { value } = this.props; let ret = []; @@ -418,10 +433,8 @@ class ObjectInput extends Component { return {ret}; } } + class ConfigFormItem extends Component { - constructor(props) { - super(props); - } render() { let { field, group, editingConfig } = this.props; return

Instances

@@ -18,7 +16,7 @@ export class InstancesTable extends Component { AddRecord={{ fields: [{ dataIndex: "name", - title: "Name" + title: "Name", }], insert: async args => { let instanceConfig = new config.InstanceConfig(); @@ -28,17 +26,15 @@ export class InstancesTable extends Component { let serialized_config = instanceConfig.serialize(); let response = await createInstance(serialized_config); console.log("Created instance", response); - } + }, }} DataFunction={listInstances} TableProps={{ - onRow: (record, rowIndex) => { - return { - onClick: event => { - this.navigate(`/instances/${record.id}/view`); - } - }; - } + onRow: (record, rowIndex) => ({ + onClick: event => { + this.navigate(`/instances/${record.id}/view`); + }, + }), }} /> ; diff --git a/packages/master/web/pages/permissions/permissionsTable.jsx b/packages/master/web/pages/permissions/permissionsTable.jsx index cb24533c..a282b504 100644 --- a/packages/master/web/pages/permissions/permissionsTable.jsx +++ b/packages/master/web/pages/permissions/permissionsTable.jsx @@ -4,25 +4,21 @@ import { listSlaves, listInstances, listPermissions } from "../../util/wslink"; import DataTable from "../../components/data-table"; export class PermissionsTable extends Component { - constructor(props) { - super(props); - } navigate(url) { this.props.history.push(url); } + render() { return

Permissions

{ - return { - onClick: event => { - this.navigate(`/permissions/${record.id}/view`); - } - }; - } + onRow: (record, rowIndex) => ({ + onClick: event => { + this.navigate(`/permissions/${record.id}/view`); + }, + }), }} />
; diff --git a/packages/master/web/pages/roles/role.jsx b/packages/master/web/pages/roles/role.jsx index b022ff46..66e3adc6 100644 --- a/packages/master/web/pages/roles/role.jsx +++ b/packages/master/web/pages/roles/role.jsx @@ -31,16 +31,19 @@ export class RoleView extends Component { constructor(props) { super(props); this.state = { - role: {} + role: {}, }; this.messagesEndRef = React.createRef(); } + navigate(url) { this.props.history.push(url); } + async componentDidMount() { await this.getData(); } + async getData() { let id = this.props.match.params.id; @@ -52,12 +55,13 @@ export class RoleView extends Component { permissions, }); } + async handleChange(targetKeys) { this.setState({ role: { ...this.state.role, - permissions: targetKeys - } + permissions: targetKeys, + }, }); let response = await updateRole({ ...this.state.role, @@ -65,6 +69,7 @@ export class RoleView extends Component { }); console.log("Updated user roles", response); } + render() { console.log(this.state); let { role, permissions } = this.state; @@ -80,7 +85,7 @@ export class RoleView extends Component { this.navigate("/roles"); }} > - Delete this role permanently + Delete this role permanently } trigger="click" @@ -93,11 +98,11 @@ export class RoleView extends Component {

Assign roles

Roles

@@ -17,25 +15,23 @@ export class RolesTable extends Component { AddRecord={{ fields: [{ dataIndex: "name", - title: "Name" + title: "Name", },{ dataIndex: "description", - title: "Description" + title: "Description", }], insert: async args => { let response = await createRole({name: args.name, description: args.description}); console.log("Created role",response); - } + }, }} DataFunction={listRoles} TableProps={{ - onRow: (record, rowIndex) => { - return { - onClick: event => { - this.navigate(`/roles/${record.id}/view`); - } - }; - } + onRow: (record, rowIndex) => ({ + onClick: event => { + this.navigate(`/roles/${record.id}/view`); + }, + }), }} /> ; diff --git a/packages/master/web/pages/settings/settings.jsx b/packages/master/web/pages/settings/settings.jsx index 9e42a831..2b1460b7 100644 --- a/packages/master/web/pages/settings/settings.jsx +++ b/packages/master/web/pages/settings/settings.jsx @@ -20,12 +20,14 @@ export class Settings extends Component { constructor(props) { super(props); this.state = { - role: {} + role: {}, }; } + navigate(url) { this.props.history.push(url); } + render() { console.log(this.state); return @@ -39,7 +41,7 @@ export class Settings extends Component { }} onFinish={values => { console.log("Saving settings",values); - for(let key of Object.keys(values)){ + for (let key of Object.keys(values)){ localStorage.setItem(key, values[key].replace("https://", "wss://").replace("http://", "ws://")); } // Force refresh to apply changes @@ -52,7 +54,7 @@ export class Settings extends Component { rules={[ { required: true, - message: 'Please input master url, ex ws://localhost:8080 or wss://localhost:8443/ . For SSL connections, ensure you have a valid certificate.', + message: "Please input master url, ex ws://localhost:8080 or wss://localhost:8443/ . For SSL connections, ensure you have a valid certificate.", }, ]} > @@ -64,7 +66,7 @@ export class Settings extends Component { rules={[ { required: true, - message: 'Please input your user auth token from config-control.json', + message: "Please input your user auth token from config-control.json", }, ]} > @@ -72,7 +74,7 @@ export class Settings extends Component {
diff --git a/packages/master/web/pages/slaves/slavesTable.jsx b/packages/master/web/pages/slaves/slavesTable.jsx index 38856bf2..c46f3096 100644 --- a/packages/master/web/pages/slaves/slavesTable.jsx +++ b/packages/master/web/pages/slaves/slavesTable.jsx @@ -4,25 +4,21 @@ import { listSlaves, listInstances } from "../../util/wslink"; import DataTable from "../../components/data-table"; export class SlavesTable extends Component { - constructor(props) { - super(props); - } navigate(url) { this.props.history.push(url); } + render() { return

Slaves

{ - return { - onClick: event => { - this.navigate(`/slaves/${record.id}/view`); - } - }; - } + onRow: (record, rowIndex) => ({ + onClick: event => { + this.navigate(`/slaves/${record.id}/view`); + }, + }), }} />
; diff --git a/packages/master/web/pages/users/user.jsx b/packages/master/web/pages/users/user.jsx index ad674df7..40739bdc 100644 --- a/packages/master/web/pages/users/user.jsx +++ b/packages/master/web/pages/users/user.jsx @@ -16,16 +16,19 @@ export class UserView extends Component { constructor(props) { super(props); this.state = { - user: {} + user: {}, }; this.messagesEndRef = React.createRef(); } + navigate(url) { this.props.history.push(url); } + async componentDidMount() { await this.getData(); } + async getData() { let name = this.props.match.params.id; @@ -37,12 +40,13 @@ export class UserView extends Component { roles, }); } + async handleChange(targetKeys) { this.setState({ user: { ...this.state.user, - roles: targetKeys - } + roles: targetKeys, + }, }); let response = await setRoles({ name: this.state.user.name, @@ -50,6 +54,7 @@ export class UserView extends Component { }); console.log("Updated user roles",response); }; + render() { console.log(this.state); let { user, roles } = this.state; @@ -76,7 +81,7 @@ export class UserView extends Component { this.navigate("/users"); }} > - Delete this user from the cluster + Delete this user from the cluster } trigger="click" diff --git a/packages/master/web/pages/users/usersTable.jsx b/packages/master/web/pages/users/usersTable.jsx index 7a25158a..154d70a1 100644 --- a/packages/master/web/pages/users/usersTable.jsx +++ b/packages/master/web/pages/users/usersTable.jsx @@ -4,12 +4,10 @@ import { listSlaves, listInstances, listUsers, createUser } from "../../util/wsl import DataTable from "../../components/data-table"; export class UsersTable extends Component { - constructor(props) { - super(props); - } navigate(url) { this.props.history.push(url); } + render() { return

Slaves

@@ -17,22 +15,20 @@ export class UsersTable extends Component { AddRecord={{ fields: [{ dataIndex: "name", - title: "Name" + title: "Name", }], insert: async args => { let response = await createUser({name: args.name}); console.log("Created user",response); - } + }, }} DataFunction={listUsers} TableProps={{ - onRow: (record, rowIndex) => { - return { - onClick: event => { - this.navigate(`/users/${record.name}/view`); - } - }; - } + onRow: (record, rowIndex) => ({ + onClick: event => { + this.navigate(`/users/${record.name}/view`); + }, + }), }} />
; diff --git a/packages/master/web/util/notify.jsx b/packages/master/web/util/notify.jsx index 034f03b1..11805738 100644 --- a/packages/master/web/util/notify.jsx +++ b/packages/master/web/util/notify.jsx @@ -1,11 +1,6 @@ import { notification } from "antd"; -/** - * - * @param {string} message - * @param {("info"|"error"|"success"|"warning"|"warn")} type - */ -export default function notify(message, type = "info", description) { +export default function notify(message, type = "info", description = undefined) { notification[type]({ message: typeof message === "string" ? message : "ERROR: See console", description, diff --git a/packages/master/web/util/websocket.jsx b/packages/master/web/util/websocket.jsx index 6a05c142..e5daa360 100644 --- a/packages/master/web/util/websocket.jsx +++ b/packages/master/web/util/websocket.jsx @@ -63,9 +63,9 @@ export class Control extends link.Link { link.attachAllMessages(this); /** - * Mapping of plugin names to their instance for loaded plugins. - * @type {Map} - */ + * Mapping of plugin names to their instance for loaded plugins. + * @type {Map} + */ this.plugins = controlPlugins; for (let controlPlugin of controlPlugins.values()) { plugin.attachPluginMessages(this, controlPlugin.info, controlPlugin); @@ -75,7 +75,9 @@ export class Control extends link.Link { async instanceOutputEventHandler(message) { let { instance_id, output } = message.data; console.log(formatOutput(output)); - window.instanceOutputEventHandler && window.instanceOutputEventHandler({instance_id, output}); + if (window.instanceOutputEventHandler) { + window.instanceOutputEventHandler({instance_id, output}); + } } async debugWsMessageEventHandler(message) { diff --git a/packages/master/web/util/wslink.jsx b/packages/master/web/util/wslink.jsx index 97f35561..f3981da5 100644 --- a/packages/master/web/util/wslink.jsx +++ b/packages/master/web/util/wslink.jsx @@ -27,38 +27,38 @@ let listUsers = createListFunction("listUsers", "name"); let listRoles = createListFunction("listRoles"); let listPermissions = createListFunction("listPermissions", "name"); -let startInstance = async function ({ instance_id, save = null }) { +async function startInstance({ instance_id, save = null }) { return await link.messages.startInstance.send(control, { instance_id, save }); }; -let stopInstance = async function ({ instance_id }) { +async function stopInstance({ instance_id }) { return await link.messages.stopInstance.send(control, { instance_id }); }; -let createInstance = async function (serialized_config) { +async function createInstance(serialized_config) { return await link.messages.createInstance.send(control, { serialized_config }); }; -let deleteInstance = async function ({ instance_id }) { +async function deleteInstance({ instance_id }) { return await link.messages.deleteInstance.send(control, { instance_id }); }; -let assignInstance = async function ({ instance_id, slave_id }) { +async function assignInstance({ instance_id, slave_id }) { return await link.messages.assignInstanceCommand.send(control, { instance_id, slave_id, }); }; -let createSave = async function ({ instance_id }) { +async function createSave({ instance_id }) { return await link.messages.createSave.send(control, { instance_id }); }; -let getInstanceConfig = async function ({ instance_id }) { +async function getInstanceConfig({ instance_id }) { return await link.messages.getInstanceConfig.send(control, { instance_id }); }; -let setInstanceConfigField = async function ({ instance_id, field, value }) { +async function setInstanceConfigField({ instance_id, field, value }) { return await link.messages.setInstanceConfigField.send(control, { instance_id, field, value: typeof value === "object" ? JSON.stringify(value) : value, }); }; -let setInstanceConfigProp = async function ({ instance_id, field, prop, value }) { +async function setInstanceConfigProp({ instance_id, field, prop, value }) { return await link.messages.setInstanceConfigProp.send(control, { instance_id, field, @@ -66,32 +66,32 @@ let setInstanceConfigProp = async function ({ instance_id, field, prop, value }) value, // JSON Object }); }; -let sendRcon = async function ({ instance_id, command }) { +async function sendRcon({ instance_id, command }) { return await link.messages.sendRcon.send(control, { instance_id, command }); }; -let setInstanceOutputSubscriptions = async function ({ instance_id }) { +async function setInstanceOutputSubscriptions({ instance_id }) { return await link.messages.setInstanceOutputSubscriptions.send(control, { instance_ids: [instance_id] }); }; -let createUser = async function ({ name }) { +async function createUser({ name }) { return await link.messages.createUser.send(control, { name }); }; -let deleteUser = async function ({ name }) { +async function deleteUser({ name }) { return await link.messages.deleteUser.send(control, { name }); }; -let setRoles = async function ({ name, roles }) { +async function setRoles({ name, roles }) { return await link.messages.updateUserRoles.send(control, { name, roles, }); }; -let createRole = async function ({ name, description, permissions = [] }) { +async function createRole({ name, description, permissions = [] }) { return await link.messages.createRole.send(control, { name, description, permissions }); }; -let updateRole = async function ({ id, name, description, permissions }) { +async function updateRole({ id, name, description, permissions }) { return await link.messages.updateRole.send(control, { - id, name, description, permissions + id, name, description, permissions, }); }; @@ -120,15 +120,15 @@ export { }; function createListFunction(name, key) { - return async () => { - return (await link.messages[name].send(control)).list.map(addKey(key || "id")); - }; + return async () => ( + (await link.messages[name].send(control)).list.map(addKey(key || "id")) + ); } function addKey(key) { return function (el) { return { ...el, - key: el[key] + key: el[key], }; }; }