mirror of
https://github.com/discourse/discourse.git
synced 2025-02-05 02:00:45 +08:00
590b3e11fb
Re-opening of #30096. It was reverted because it was missing a sidebar link to Webhooks in the admin panel.
319 lines
10 KiB
Plaintext
319 lines
10 KiB
Plaintext
import Component from "@glimmer/component";
|
|
import { cached, tracked } from "@glimmer/tracking";
|
|
import { concat, fn, hash } from "@ember/helper";
|
|
import { action } from "@ember/object";
|
|
import { service } from "@ember/service";
|
|
import { eq } from "truth-helpers";
|
|
import BackButton from "discourse/components/back-button";
|
|
import ConditionalLoadingSection from "discourse/components/conditional-loading-section";
|
|
import DButton from "discourse/components/d-button";
|
|
import Form from "discourse/components/form";
|
|
import { ajax } from "discourse/lib/ajax";
|
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
import { i18n } from "discourse-i18n";
|
|
import ApiKeyUrlsModal from "admin/components/modal/api-key-urls";
|
|
import EmailGroupUserChooser from "select-kit/components/email-group-user-chooser";
|
|
import DTooltip from "float-kit/components/d-tooltip";
|
|
|
|
export default class AdminConfigAreasApiKeysNew extends Component {
|
|
@service router;
|
|
@service modal;
|
|
@service store;
|
|
|
|
@tracked username;
|
|
@tracked loadingScopes = false;
|
|
@tracked scopes = null;
|
|
|
|
userModes = [
|
|
{ id: "all", name: i18n("admin.api.all_users") },
|
|
{ id: "single", name: i18n("admin.api.single_user") },
|
|
];
|
|
|
|
scopeModes = [
|
|
{ id: "global", name: i18n("admin.api.scopes.global") },
|
|
{ id: "read_only", name: i18n("admin.api.scopes.read_only") },
|
|
{ id: "granular", name: i18n("admin.api.scopes.granular") },
|
|
];
|
|
|
|
globalScopes = null;
|
|
|
|
constructor() {
|
|
super(...arguments);
|
|
this.#loadScopes();
|
|
}
|
|
|
|
@cached
|
|
get formData() {
|
|
let scopes = Object.keys(this.scopes).reduce((result, resource) => {
|
|
result[resource] = this.scopes[resource].map((scope) => {
|
|
const params = scope.params
|
|
? scope.params.reduce((acc, param) => {
|
|
acc[param] = undefined;
|
|
return acc;
|
|
}, {})
|
|
: {};
|
|
|
|
return {
|
|
key: scope.key,
|
|
enabled: undefined,
|
|
urls: scope.urls,
|
|
...(params && { params }),
|
|
};
|
|
});
|
|
return result;
|
|
}, {});
|
|
|
|
return {
|
|
user_mode: "all",
|
|
scope_mode: "global",
|
|
scopes,
|
|
};
|
|
}
|
|
|
|
@action
|
|
updateUsername(field, selected) {
|
|
this.username = selected[0];
|
|
field.set(this.username);
|
|
}
|
|
|
|
@action
|
|
async save(data) {
|
|
const payload = { description: data.description };
|
|
|
|
if (data.user_mode === "single") {
|
|
payload.username = data.user;
|
|
}
|
|
|
|
if (data.scope_mode === "granular") {
|
|
payload.scopes = this.#selectedScopes(data.scopes);
|
|
} else if (data.scope_mode === "read_only") {
|
|
payload.scopes = this.globalScopes.filter(
|
|
(scope) => scope.key === "read"
|
|
);
|
|
}
|
|
|
|
try {
|
|
await this.store.createRecord("api-key").save(payload);
|
|
this.router.transitionTo("adminApiKeys");
|
|
} catch (error) {
|
|
popupAjaxError(error);
|
|
}
|
|
}
|
|
|
|
#selectedScopes(scopes) {
|
|
const enabledScopes = [];
|
|
|
|
for (const [resource, resourceScopes] of Object.entries(scopes)) {
|
|
enabledScopes.push(
|
|
resourceScopes
|
|
.filter((s) => s.enabled)
|
|
.map((s) => {
|
|
return {
|
|
scope_id: `${resource}:${s.key}`,
|
|
key: s.key,
|
|
name: s.key,
|
|
params: Object.keys(s.params),
|
|
...s.params,
|
|
};
|
|
})
|
|
);
|
|
}
|
|
|
|
return enabledScopes.flat();
|
|
}
|
|
|
|
@action
|
|
async showURLs(urls) {
|
|
await this.modal.show(ApiKeyUrlsModal, {
|
|
model: { urls },
|
|
});
|
|
}
|
|
|
|
async #loadScopes() {
|
|
try {
|
|
this.loadingScopes = true;
|
|
const data = await ajax("/admin/api/keys/scopes.json");
|
|
|
|
this.globalScopes = data.scopes.global;
|
|
delete data.scopes.global;
|
|
|
|
this.scopes = data.scopes;
|
|
} catch (error) {
|
|
popupAjaxError(error);
|
|
} finally {
|
|
this.loadingScopes = false;
|
|
}
|
|
}
|
|
|
|
<template>
|
|
<BackButton @route="adminApiKeys.index" @label="admin.api_keys.back" />
|
|
|
|
<div class="admin-config-area user-field">
|
|
<div class="admin-config-area__primary-content">
|
|
<div class="admin-config-area-card">
|
|
<ConditionalLoadingSection @isLoading={{this.loadingScopes}}>
|
|
<Form
|
|
@onSubmit={{this.save}}
|
|
@data={{this.formData}}
|
|
as |form transientData|
|
|
>
|
|
<form.Field
|
|
@name="description"
|
|
@title={{i18n "admin.api.description"}}
|
|
@format="large"
|
|
@validation="required"
|
|
as |field|
|
|
>
|
|
<field.Input />
|
|
</form.Field>
|
|
|
|
<form.Field
|
|
@name="user_mode"
|
|
@title={{i18n "admin.api.user_mode"}}
|
|
@format="large"
|
|
@validation="required"
|
|
as |field|
|
|
>
|
|
<field.Select as |select|>
|
|
{{#each this.userModes as |userMode|}}
|
|
<select.Option
|
|
@value={{userMode.id}}
|
|
>{{userMode.name}}</select.Option>
|
|
{{/each}}
|
|
</field.Select>
|
|
</form.Field>
|
|
|
|
{{#if (eq transientData.user_mode "single")}}
|
|
<form.Field
|
|
@name="user"
|
|
@title={{i18n "admin.api.user"}}
|
|
@format="large"
|
|
@validation="required"
|
|
as |field|
|
|
>
|
|
<field.Custom>
|
|
<EmailGroupUserChooser
|
|
@value={{this.username}}
|
|
@onChange={{fn this.updateUsername field}}
|
|
@options={{hash
|
|
maximum=1
|
|
filterPlaceholder="admin.api.user_placeholder"
|
|
}}
|
|
/>
|
|
</field.Custom>
|
|
</form.Field>
|
|
{{/if}}
|
|
|
|
<form.Field
|
|
@name="scope_mode"
|
|
@title={{i18n "admin.api.scope_mode"}}
|
|
@format="large"
|
|
@validation="required"
|
|
as |field|
|
|
>
|
|
<field.Select as |select|>
|
|
{{#each this.scopeModes as |scopeMode|}}
|
|
<select.Option
|
|
@value={{scopeMode.id}}
|
|
>{{scopeMode.name}}</select.Option>
|
|
{{/each}}
|
|
</field.Select>
|
|
</form.Field>
|
|
|
|
{{#if (eq transientData.scope_mode "granular")}}
|
|
<h2 class="scopes-title">{{i18n "admin.api.scopes.title"}}</h2>
|
|
<p>{{i18n "admin.api.scopes.description"}}</p>
|
|
<table class="scopes-table grid">
|
|
<thead>
|
|
<tr>
|
|
<td></td>
|
|
<td></td>
|
|
<td>{{i18n "admin.api.scopes.allowed_urls"}}</td>
|
|
<td>{{i18n
|
|
"admin.api.scopes.optional_allowed_parameters"
|
|
}}</td>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<form.Object @name="scopes" as |scopesObject scopeName|>
|
|
<tr class="scope-resource-name">
|
|
<td><b>{{scopeName}}</b></td>
|
|
<td></td>
|
|
<td></td>
|
|
<td></td>
|
|
</tr>
|
|
|
|
<scopesObject.Collection
|
|
@name={{scopeName}}
|
|
@tagName="tr"
|
|
as |topicsCollection index collectionData|
|
|
>
|
|
<td>
|
|
<topicsCollection.Field
|
|
@name="enabled"
|
|
@title={{collectionData.key}}
|
|
@showTitle={{false}}
|
|
as |field|
|
|
>
|
|
<field.Checkbox />
|
|
</topicsCollection.Field>
|
|
</td>
|
|
<td>
|
|
<div class="scope-name">{{collectionData.name}}</div>
|
|
<DTooltip
|
|
@icon="circle-question"
|
|
@content={{i18n
|
|
(concat
|
|
"admin.api.scopes.descriptions."
|
|
scopeName
|
|
"."
|
|
collectionData.key
|
|
)
|
|
class="scope-tooltip"
|
|
}}
|
|
/>
|
|
</td>
|
|
<td>
|
|
<DButton
|
|
@icon="link"
|
|
@action={{fn this.showURLs collectionData.urls}}
|
|
class="btn-info"
|
|
/>
|
|
</td>
|
|
<td>
|
|
<topicsCollection.Object
|
|
@name="params"
|
|
as |paramsObject name|
|
|
>
|
|
<paramsObject.Field
|
|
@name={{name}}
|
|
@title={{name}}
|
|
@showTitle={{false}}
|
|
as |field|
|
|
>
|
|
<field.Input placeholder={{name}} />
|
|
</paramsObject.Field>
|
|
</topicsCollection.Object>
|
|
</td>
|
|
</scopesObject.Collection>
|
|
</form.Object>
|
|
</tbody>
|
|
</table>
|
|
{{/if}}
|
|
|
|
<form.Actions>
|
|
<form.Submit class="save" @label="admin.api_keys.save" />
|
|
<form.Button
|
|
@route="adminApiKeys.index"
|
|
@label="admin.api_keys.cancel"
|
|
class="btn-default"
|
|
/>
|
|
</form.Actions>
|
|
</Form>
|
|
</ConditionalLoadingSection>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
}
|