Spiegel von
https://github.com/paviliondev/discourse-custom-wizard.git
synchronisiert 2024-11-22 09:20:29 +01:00
WIP
Dieser Commit ist enthalten in:
Ursprung
bbd1253891
Commit
81bb7e56c2
30 geänderte Dateien mit 930 neuen und 341 gelöschten Zeilen
14
assets/javascripts/discourse/components/wizard-notice-row.js.es6
Normale Datei
14
assets/javascripts/discourse/components/wizard-notice-row.js.es6
Normale Datei
|
@ -0,0 +1,14 @@
|
|||
import Component from "@ember/component";
|
||||
import NoticeMessage from "../mixins/notice-message";
|
||||
|
||||
export default Component.extend(NoticeMessage, {
|
||||
tagName: "tr",
|
||||
attributeBindings: ["notice.id:data-notice-id"],
|
||||
classNameBindings: [":wizard-notice-row", "notice.typeClass", "notice.expired:expired", "notice.dismissed:dismissed"],
|
||||
|
||||
actions: {
|
||||
dismiss() {
|
||||
this.notice.dismiss();
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,35 +1,9 @@
|
|||
import Component from "@ember/component";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { not, notEmpty } from "@ember/object/computed";
|
||||
import I18n from "I18n";
|
||||
import NoticeMessage from "../mixins/notice-message";
|
||||
|
||||
export default Component.extend({
|
||||
classNameBindings: [
|
||||
":wizard-notice",
|
||||
"notice.type",
|
||||
"dismissed",
|
||||
"expired",
|
||||
"resolved",
|
||||
],
|
||||
showFull: false,
|
||||
resolved: notEmpty("notice.expired_at"),
|
||||
dismissed: notEmpty("notice.dismissed_at"),
|
||||
canDismiss: not("dismissed"),
|
||||
|
||||
@discourseComputed("notice.type")
|
||||
title(type) {
|
||||
return I18n.t(`admin.wizard.notice.title.${type}`);
|
||||
},
|
||||
|
||||
@discourseComputed("notice.type")
|
||||
icon(type) {
|
||||
return {
|
||||
plugin_status_warning: "exclamation-circle",
|
||||
plugin_status_connection_error: "bolt",
|
||||
subscription_messages_connection_error: "bolt",
|
||||
info: "info-circle",
|
||||
}[type];
|
||||
},
|
||||
export default Component.extend(NoticeMessage, {
|
||||
attributeBindings: ["notice.id:data-notice-id"],
|
||||
classNameBindings: [':wizard-notice', 'notice.typeClass', 'notice.dismissed:dismissed', 'notice.expired:expired', 'notice.hidden:hidden'],
|
||||
|
||||
actions: {
|
||||
dismiss() {
|
||||
|
@ -38,5 +12,12 @@ export default Component.extend({
|
|||
this.set("dismissing", false);
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
hide() {
|
||||
this.set('hiding', true);
|
||||
this.notice.hide().then(() => {
|
||||
this.set('hiding', false);
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{{#if notices}}
|
||||
{{#each notices as |notice|}}
|
||||
{{wizard-notice notice=notice showPlugin=true}}
|
||||
{{/each}}
|
||||
{{/if}}
|
|
@ -0,0 +1,19 @@
|
|||
import { getOwner } from "discourse-common/lib/get-owner";
|
||||
|
||||
export default {
|
||||
shouldRender(attrs, ctx) {
|
||||
return ctx.siteSettings.wizard_critical_notices_on_dashboard;
|
||||
},
|
||||
|
||||
setupComponent(attrs, component) {
|
||||
const controller = getOwner(this).lookup('controller:admin-dashboard');
|
||||
|
||||
component.set('notices', controller.get('customWizardCriticalNotices'));
|
||||
controller.addObserver('customWizardCriticalNotices.[]', () => {
|
||||
if (this._state === "destroying") {
|
||||
return;
|
||||
}
|
||||
component.set('notices', controller.get('customWizardCriticalNotices'));
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
{{#if importantNotice}}
|
||||
{{wizard-notice notice=importantNotice importantOnDashboard=true}}
|
||||
{{/if}}
|
|
@ -1,16 +0,0 @@
|
|||
import { getOwner } from "discourse-common/lib/get-owner";
|
||||
|
||||
export default {
|
||||
shouldRender(attrs, ctx) {
|
||||
return ctx.siteSettings.wizard_important_notices_on_dashboard;
|
||||
},
|
||||
|
||||
setupComponent() {
|
||||
const controller = getOwner(this).lookup("controller:admin-dashboard");
|
||||
const importantNotice = controller.get("customWizardImportantNotice");
|
||||
|
||||
if (importantNotice) {
|
||||
this.set("importantNotice", importantNotice);
|
||||
}
|
||||
},
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
import Controller from "@ember/controller";
|
||||
import CustomWizardNotice from "../models/custom-wizard-notice";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { notEmpty } from "@ember/object/computed";
|
||||
import { A } from "@ember/array";
|
||||
import I18n from "I18n";
|
||||
|
||||
export default Controller.extend({
|
||||
messageUrl: "https://thepavilion.io/t/3652",
|
||||
messageKey: "info",
|
||||
messageIcon: "info-circle",
|
||||
messageClass: "info",
|
||||
hasNotices: notEmpty("notices"),
|
||||
page: 0,
|
||||
loadingMore: false,
|
||||
canLoadMore: true,
|
||||
|
||||
@discourseComputed('notices.[]', 'notices.@each.dismissed')
|
||||
allDismisssed(notices) {
|
||||
return notices.every(n => !n.canDismiss || n.dismissed);
|
||||
},
|
||||
|
||||
loadMoreNotices() {
|
||||
if (!this.canLoadMore) {
|
||||
return;
|
||||
}
|
||||
const page = this.get("page");
|
||||
this.set("loadingMore", true);
|
||||
|
||||
CustomWizardNotice.list({ page, include_all: true })
|
||||
.then((result) => {
|
||||
if (result.notices.length === 0) {
|
||||
this.set("canLoadMore", false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.get("notices").pushObjects(
|
||||
A(result.notices.map(notice => CustomWizardNotice.create(notice)))
|
||||
);
|
||||
})
|
||||
.finally(() => this.set("loadingMore", false));
|
||||
},
|
||||
|
||||
actions: {
|
||||
loadMore() {
|
||||
if (this.canLoadMore) {
|
||||
this.set("page", this.page + 1);
|
||||
this.loadMoreNotices();
|
||||
}
|
||||
},
|
||||
|
||||
dismissAll() {
|
||||
bootbox.confirm(
|
||||
I18n.t("admin.wizard.notice.dismiss_all.confirm"),
|
||||
I18n.t("no_value"),
|
||||
I18n.t("yes_value"),
|
||||
(result) => {
|
||||
if (result) {
|
||||
this.set('loadingMore', true);
|
||||
CustomWizardNotice.dismissAll()
|
||||
.finally(() => this.set("loadingMore", false));
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,20 +1,26 @@
|
|||
import Controller from "@ember/controller";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import { isPresent } from "@ember/utils";
|
||||
import { A } from "@ember/array";
|
||||
|
||||
export default Controller.extend({
|
||||
actions: {
|
||||
dismissNotice(noticeId) {
|
||||
ajax(`/admin/wizards/notice/${this.id}`, {
|
||||
type: "DELETE",
|
||||
})
|
||||
.then((result) => {
|
||||
if (result.success) {
|
||||
const notices = this.notices;
|
||||
notices.removeObject(notices.findBy("id", noticeId));
|
||||
}
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
},
|
||||
adminWizardsNotices: controller(),
|
||||
|
||||
unsubscribe() {
|
||||
this.messageBus.unsubscribe("/custom-wizard/notices");
|
||||
},
|
||||
|
||||
subscribe() {
|
||||
this.unsubscribe();
|
||||
this.messageBus.subscribe("/custom-wizard/notices", (data) => {
|
||||
if (isPresent(data.active_notice_count)) {
|
||||
this.set("activeNoticeCount", data.active_notice_count);
|
||||
this.adminWizardsNotices.setProperties({
|
||||
notices: A(),
|
||||
page: 0,
|
||||
canLoadMore: true
|
||||
});
|
||||
this.adminWizardsNotices.loadMoreNotices();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -63,6 +63,11 @@ export default {
|
|||
path: "/subscription",
|
||||
resetNamespace: true,
|
||||
});
|
||||
|
||||
this.route("adminWizardsNotices", {
|
||||
path: "/notices",
|
||||
resetNamespace: true,
|
||||
});
|
||||
}
|
||||
);
|
||||
},
|
||||
|
|
41
assets/javascripts/discourse/helpers/notice-badge.js.es6
Normale Datei
41
assets/javascripts/discourse/helpers/notice-badge.js.es6
Normale Datei
|
@ -0,0 +1,41 @@
|
|||
import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import I18n from "I18n";
|
||||
import { registerUnbound } from "discourse-common/lib/helpers";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
|
||||
registerUnbound("notice-badge", function(attrs) {
|
||||
let tag = attrs.url ? 'a' : 'div';
|
||||
let attrStr = '';
|
||||
if (attrs.title) {
|
||||
attrStr += `title='${I18n.t(attrs.title)}'`;
|
||||
}
|
||||
if (attrs.url) {
|
||||
attrStr += `href='${attrs.url}'`;
|
||||
}
|
||||
let html = `<${tag} class="${attrs.class ? `${attrs.class} ` : ''}notice-badge" ${attrStr}>`;
|
||||
if (attrs.icon) {
|
||||
html += iconHTML(attrs.icon);
|
||||
}
|
||||
if (attrs.label) {
|
||||
if (attrs.icon) {
|
||||
html += ' ';
|
||||
}
|
||||
html += `<span>${I18n.t(attrs.label)}</span>`;
|
||||
}
|
||||
if (attrs.date) {
|
||||
if (attrs.icon || attrs.label) {
|
||||
html += ' ';
|
||||
}
|
||||
let dateAttrs = {};
|
||||
if (attrs.leaveAgo) {
|
||||
dateAttrs = {
|
||||
format: "medium",
|
||||
leaveAgo: true
|
||||
};
|
||||
}
|
||||
html += autoUpdatingRelativeAge(new Date(attrs.date), dateAttrs);
|
||||
}
|
||||
html += `</${tag}>`;
|
||||
return htmlSafe(html);
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
import DiscourseURL from "discourse/lib/url";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import CustomWizardNotice from "../models/custom-wizard-notice";
|
||||
import { isPresent } from "@ember/utils";
|
||||
import { A } from "@ember/array";
|
||||
|
||||
export default {
|
||||
|
@ -21,37 +22,46 @@ export default {
|
|||
};
|
||||
|
||||
withPluginApi("0.8.36", (api) => {
|
||||
api.modifyClass("route:admin-dashboard", {
|
||||
afterModel() {
|
||||
return CustomWizardNotice.list().then((result) => {
|
||||
if (result && result.length) {
|
||||
this.set(
|
||||
"notices",
|
||||
A(result.map((n) => CustomWizardNotice.create(n)))
|
||||
);
|
||||
api.modifyClass('route:admin-dashboard', {
|
||||
setupController(controller) {
|
||||
this._super(...arguments);
|
||||
|
||||
controller.loadCriticalNotices();
|
||||
controller.subscribe();
|
||||
}
|
||||
});
|
||||
|
||||
api.modifyClass('controller:admin-dashboard', {
|
||||
criticalNotices: A(),
|
||||
|
||||
unsubscribe() {
|
||||
this.messageBus.unsubscribe("/custom-wizard/notices");
|
||||
},
|
||||
|
||||
subscribe() {
|
||||
this.unsubscribe();
|
||||
this.messageBus.subscribe("/custom-wizard/notices", (data) => {
|
||||
if (isPresent(data.active_notice_count)) {
|
||||
this.loadCriticalNotices();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
setupController(controller) {
|
||||
if (this.notices) {
|
||||
let pluginStatusConnectionError = this.notices.filter(
|
||||
(n) => n.type === "plugin_status_connection_error"
|
||||
)[0];
|
||||
let pluginStatusWarning = this.notices.filter(
|
||||
(n) => n.type === "plugin_status_warning"
|
||||
)[0];
|
||||
|
||||
if (pluginStatusConnectionError || pluginStatusWarning) {
|
||||
controller.set(
|
||||
"customWizardImportantNotice",
|
||||
pluginStatusConnectionError || pluginStatusWarning
|
||||
);
|
||||
loadCriticalNotices() {
|
||||
CustomWizardNotice.list({
|
||||
type: [
|
||||
'connection_error',
|
||||
'warning'
|
||||
],
|
||||
archetype: 'plugin_status',
|
||||
visible: true
|
||||
}).then(result => {
|
||||
if (result.notices && result.notices.length) {
|
||||
const criticalNotices = A(result.notices.map(n => CustomWizardNotice.create(n)));
|
||||
this.set('customWizardCriticalNotices', criticalNotices);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._super(...arguments);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
65
assets/javascripts/discourse/mixins/notice-message.js.es6
Normale Datei
65
assets/javascripts/discourse/mixins/notice-message.js.es6
Normale Datei
|
@ -0,0 +1,65 @@
|
|||
import Mixin from "@ember/object/mixin";
|
||||
import { bind, scheduleOnce } from "@ember/runloop";
|
||||
import { cookAsync } from "discourse/lib/text";
|
||||
import { createPopper } from "@popperjs/core";
|
||||
|
||||
export default Mixin.create({
|
||||
showCookedMessage: false,
|
||||
|
||||
didReceiveAttrs(){
|
||||
const message = this.notice.message;
|
||||
cookAsync(message).then((cooked) => {
|
||||
this.set("cookedMessage", cooked);
|
||||
});
|
||||
},
|
||||
|
||||
createMessageModal() {
|
||||
let container = this.element.querySelector('.notice-message');
|
||||
let modal = this.element.querySelector('.cooked-notice-message');
|
||||
|
||||
this._popper = createPopper(
|
||||
container,
|
||||
modal, {
|
||||
strategy: "absolute",
|
||||
placement: "bottom-start",
|
||||
modifiers: [
|
||||
{
|
||||
name: "preventOverflow",
|
||||
},
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, 5],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
didInsertElement() {
|
||||
$(document).on("click", bind(this, this.documentClick));
|
||||
},
|
||||
|
||||
willDestroyElement() {
|
||||
$(document).off("click", bind(this, this.documentClick));
|
||||
},
|
||||
|
||||
documentClick(event) {
|
||||
if (this._state === "destroying") { return; }
|
||||
|
||||
if (!event.target.closest(`[data-notice-id="${this.notice.id}"] .notice-message`)) {
|
||||
this.set('showCookedMessage', false);
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
toggleCookedMessage() {
|
||||
this.toggleProperty("showCookedMessage");
|
||||
|
||||
if (this.showCookedMessage) {
|
||||
scheduleOnce("afterRender", this, this.createMessageModal);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -1,25 +1,68 @@
|
|||
import EmberObject from "@ember/object";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { and, not, notEmpty } from "@ember/object/computed";
|
||||
import { dasherize } from "@ember/string";
|
||||
import I18n from "I18n";
|
||||
|
||||
const CustomWizardNotice = EmberObject.extend();
|
||||
const CustomWizardNotice = EmberObject.extend({
|
||||
expired: notEmpty('expired_at'),
|
||||
dismissed: notEmpty('dismissed_at'),
|
||||
hidden: notEmpty('hidden_at'),
|
||||
notHidden: not('hidden'),
|
||||
notDismissed: not('dismissed'),
|
||||
canDismiss: and('dismissable', 'notDismissed'),
|
||||
canHide: and('can_hide', 'notHidden'),
|
||||
|
||||
CustomWizardNotice.reopen({
|
||||
dismiss() {
|
||||
return ajax(`/admin/wizards/notice/${this.id}`, { type: "PUT" })
|
||||
.then((result) => {
|
||||
if (result.success) {
|
||||
this.set("dismissed_at", result.dismissed_at);
|
||||
}
|
||||
})
|
||||
.catch(popupAjaxError);
|
||||
@discourseComputed('type')
|
||||
typeClass(type) {
|
||||
return dasherize(type);
|
||||
},
|
||||
|
||||
@discourseComputed('type')
|
||||
typeLabel(type) {
|
||||
return I18n.t(`admin.wizard.notice.type.${type}`);
|
||||
},
|
||||
|
||||
dismiss() {
|
||||
if (!this.get('canDismiss')) {
|
||||
return;
|
||||
}
|
||||
|
||||
return ajax(`/admin/wizards/notice/${this.get('id')}/dismiss`, { type: 'PUT' }).then(result => {
|
||||
if (result.success) {
|
||||
this.set('dismissed_at', result.dismissed_at);
|
||||
}
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
hide() {
|
||||
if (!this.get('canHide')) {
|
||||
return;
|
||||
}
|
||||
|
||||
return ajax(`/admin/wizards/notice/${this.get('id')}/hide`, { type: 'PUT' }).then(result => {
|
||||
if (result.success) {
|
||||
this.set('hidden_at', result.hidden_at);
|
||||
}
|
||||
}).catch(popupAjaxError);
|
||||
}
|
||||
});
|
||||
|
||||
CustomWizardNotice.reopenClass({
|
||||
list() {
|
||||
return ajax("/admin/wizards/notice").catch(popupAjaxError);
|
||||
list(data = {}) {
|
||||
return ajax('/admin/wizards/notice', {
|
||||
type: "GET",
|
||||
data
|
||||
}).catch(popupAjaxError);
|
||||
},
|
||||
|
||||
dismissAll() {
|
||||
return ajax('/admin/wizards/notice/dismiss', {
|
||||
type: "PUT"
|
||||
}).catch(popupAjaxError);
|
||||
}
|
||||
});
|
||||
|
||||
export default CustomWizardNotice;
|
||||
|
|
15
assets/javascripts/discourse/routes/admin-wizards-notices.js.es6
Normale Datei
15
assets/javascripts/discourse/routes/admin-wizards-notices.js.es6
Normale Datei
|
@ -0,0 +1,15 @@
|
|||
import CustomWizardNotice from "../models/custom-wizard-notice";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { A } from "@ember/array";
|
||||
|
||||
export default DiscourseRoute.extend({
|
||||
model() {
|
||||
return CustomWizardNotice.list({ include_all: true });
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.setProperties({
|
||||
notices: A(model.notices.map(notice => CustomWizardNotice.create(notice))),
|
||||
});
|
||||
},
|
||||
});
|
|
@ -1,6 +1,5 @@
|
|||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { A } from "@ember/array";
|
||||
|
||||
export default DiscourseRoute.extend({
|
||||
model() {
|
||||
|
@ -8,13 +7,21 @@ export default DiscourseRoute.extend({
|
|||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
controller.set("notices", A(model.notices));
|
||||
controller.set("api_section", model.api_section);
|
||||
|
||||
if (model.active_notice_count) {
|
||||
controller.set("activeNoticeCount", model.active_notice_count);
|
||||
}
|
||||
if (model.featured_notices) {
|
||||
controller.set("featuredNotices", model.featured_notices);
|
||||
}
|
||||
|
||||
controller.subscribe();
|
||||
},
|
||||
|
||||
afterModel(model, transition) {
|
||||
if (transition.targetName === "adminWizards.index") {
|
||||
this.transitionTo("adminWizardsWizard");
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
49
assets/javascripts/discourse/templates/admin-wizards-notices.hbs
Normale Datei
49
assets/javascripts/discourse/templates/admin-wizards-notices.hbs
Normale Datei
|
@ -0,0 +1,49 @@
|
|||
<div class="admin-wizard-controls">
|
||||
<h3>{{i18n "admin.wizard.notices.title"}}</h3>
|
||||
|
||||
<div class="buttons">
|
||||
{{d-button
|
||||
label="admin.wizard.notice.dismiss_all.label"
|
||||
title="admin.wizard.notice.dismiss_all.title"
|
||||
action=(action "dismissAll")
|
||||
disabled=allDismisssed
|
||||
icon="check"}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{wizard-message
|
||||
key=messageKey
|
||||
url=messageUrl
|
||||
type=messageType
|
||||
opts=messageOpts
|
||||
items=messageItems
|
||||
loading=loading
|
||||
component="notices"}}
|
||||
|
||||
<div class="wizard-table">
|
||||
{{#load-more selector=".wizard-table tr" action=(action "loadMore")}}
|
||||
{{#if hasNotices}}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{I18n "admin.wizard.notice.time"}}</th>
|
||||
<th>{{I18n "admin.wizard.notice.type.label"}}</th>
|
||||
<th>{{I18n "admin.wizard.notice.title"}}</th>
|
||||
<th>{{I18n "admin.wizard.notice.status"}}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each notices as |notice|}}
|
||||
{{wizard-notice-row notice=notice}}
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{else}}
|
||||
{{#unless loadingMore}}
|
||||
<p>{{i18n "search.no_results"}}</p>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
|
||||
{{conditional-loading-spinner condition=loadingMore}}
|
||||
{{/load-more}}
|
||||
</div>
|
|
@ -10,6 +10,12 @@
|
|||
{{nav-item route="adminWizardsSubscription" label="admin.wizard.subscription.nav_label"}}
|
||||
|
||||
<div class="admin-actions">
|
||||
<div class="wizard-notices-link">
|
||||
{{nav-item tagName="div" route="adminWizardsNotices" label="admin.wizard.notices.nav_label"}}
|
||||
{{#if activeNoticeCount}}
|
||||
<span class="active-notice-count">{{activeNoticeCount}}</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<a target="_blank" class="btn btn-pavilion-support" rel="noreferrer noopener" href="https://thepavilion.io/w/support" title={{i18n "admin.wizard.support_button.title"}}>
|
||||
{{d-icon "far-life-ring"}}{{i18n "admin.wizard.support_button.label"}}
|
||||
</a>
|
||||
|
@ -17,8 +23,5 @@
|
|||
{{/admin-nav}}
|
||||
|
||||
<div class="admin-container">
|
||||
{{#each notices as |notice|}}
|
||||
{{wizard-notice notice=notice}}
|
||||
{{/each}}
|
||||
{{outlet}}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<td class="small">
|
||||
{{#if notice.updated_at}}
|
||||
{{notice-badge class="notice-updated-at" date=notice.updated_at label="admin.wizard.notice.updated_at" leaveAgo=true}}
|
||||
{{else}}
|
||||
{{notice-badge class="notice-created-at" date=notice.created_at label="admin.wizard.notice.created_at" leaveAgo=true}}
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>{{notice.typeLabel}}</td>
|
||||
<td class="notice-message">
|
||||
<a role="button" {{action "toggleCookedMessage"}} class="show-notice-message">{{notice.title}}</a>
|
||||
{{#if showCookedMessage}}
|
||||
<span class="cooked-notice-message">{{cookedMessage}}</span>
|
||||
{{/if}}
|
||||
</td>
|
||||
<td>
|
||||
{{#if notice.canDismiss}}
|
||||
{{d-button
|
||||
action="dismiss"
|
||||
label="admin.wizard.notice.dismiss.label"
|
||||
title="admin.wizard.notice.dismiss.title"
|
||||
class="btn-dismiss"
|
||||
icon="check"}}
|
||||
{{else if notice.dismissed}}
|
||||
<span>{{i18n "admin.wizard.notice.dismissed_at"}} {{format-date notice.dismissed_at leaveAgo="true"}}</span>
|
||||
{{else if notice.expired}}
|
||||
<span>{{i18n "admin.wizard.notice.expired_at"}} {{format-date notice.expired_at leaveAgo="true"}}</span>
|
||||
{{else}}
|
||||
<span>{{i18n "admin.wizard.notice.active"}}</span>
|
||||
{{/if}}
|
||||
</td>
|
|
@ -1,51 +1,39 @@
|
|||
<div class="notice-header">
|
||||
{{#if resolved}}
|
||||
<div class="notice-expired-at notice-badge" title={{notice.expired_at}}>
|
||||
{{d-icon "check"}}
|
||||
<span class="notice-resolved">{{i18n "admin.wizard.notice.resolved"}}</span>
|
||||
{{format-date notice.expired_at leaveAgo="true"}}
|
||||
</div>
|
||||
<div class="notice-title notice-badge notice-message">
|
||||
<a role="button" {{action "toggleCookedMessage"}} class="show-notice-message">{{notice.title}}</a>
|
||||
{{#if showCookedMessage}}
|
||||
<span class="cooked-notice-message">{{cookedMessage}}</span>
|
||||
{{/if}}
|
||||
|
||||
<div class="notice-title notice-badge" title={{title}}>
|
||||
{{d-icon icon}}
|
||||
<span>{{title}}</span>
|
||||
</div>
|
||||
|
||||
<div class="notice-created-at notice-badge" title={{notice.created_at}}>
|
||||
{{d-icon "far-clock"}}
|
||||
<span class="notice-issued">{{i18n "admin.wizard.notice.issued"}}</span>
|
||||
{{format-date notice.created_at leaveAgo="true"}}
|
||||
</div>
|
||||
|
||||
<div class="notice-header-right">
|
||||
{{#if notice.expired}}
|
||||
{{notice-badge class="notice-expired-at" icon="check" label="admin.wizard.notice.expired_at" date=notice.expired_at}}
|
||||
{{/if}}
|
||||
{{#if showPlugin}}
|
||||
{{notice-badge class="notice-plugin" icon="plug" title="admin.wizard.notice.plugin" label="admin.wizard.notice.plugin" url="/admin/wizards/notices"}}
|
||||
{{/if}}
|
||||
{{notice-badge class="notice-created-at" icon="far-clock" label="admin.wizard.notice.created_at" date=notice.created_at leaveAgo=true}}
|
||||
{{#if notice.updated_at}}
|
||||
<div class="notice-updated-at notice-badge" title={{notice.updated_at}}>
|
||||
{{d-icon "calendar-alt"}}
|
||||
<span class="notice-updated">{{i18n "admin.wizard.notice.updated"}}</span>
|
||||
{{format-date notice.updated_at leaveAgo="true"}}
|
||||
</div>
|
||||
{{notice-badge class="notice-updated-at" icon="far-clock" label="admin.wizard.notice.updated_at" date=notice.updated_at}}
|
||||
{{/if}}
|
||||
|
||||
<div class="notice-plugin notice-badge" title={{i18n "admin.wizard.notice.plugin"}}>
|
||||
{{d-icon "plug"}}
|
||||
<span>{{i18n "admin.wizard.notice.plugin"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="notice-message">
|
||||
{{html-safe notice.message}}
|
||||
</div>
|
||||
|
||||
{{#if importantOnDashboard}}
|
||||
<a href="/admin/site_settings/category/all_results?filter=wizard_important_notices_on_dashboard" class="disable-important">
|
||||
{{i18n "admin.wizard.notice.disable_important_on_dashboard"}}
|
||||
</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if canDismiss}}
|
||||
{{#if notice.canDismiss}}
|
||||
<div class="dismiss-notice-container">
|
||||
{{#if dismissing}}
|
||||
{{loading-spinner size="small"}}
|
||||
{{else}}
|
||||
<a {{action "dismiss"}} role="button" class="dismiss-notice">{{d-icon "times"}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if notice.canHide}}
|
||||
<div class="hide-notice-container">
|
||||
{{#if hiding}}
|
||||
{{loading-spinner size="small"}}
|
||||
{{else}}
|
||||
<a {{action "hide"}} role="button" class="hide-notice">{{d-icon "far-eye-slash"}}</a>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,15 +4,25 @@
|
|||
@import "common/components/buttons";
|
||||
@import "wizard/variables";
|
||||
|
||||
$expired: #339b18;
|
||||
$info: #038ae7;
|
||||
$warning: #d47e00;
|
||||
$error: #ef1700;
|
||||
|
||||
.admin-wizard-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
min-height: 34px;
|
||||
|
||||
& + .wizard-message + div {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-message {
|
||||
|
@ -909,87 +919,142 @@
|
|||
}
|
||||
}
|
||||
|
||||
.admin-wizards-notices {
|
||||
.wizard-table {
|
||||
overflow: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.wizard-notice {
|
||||
padding: 1em;
|
||||
padding: .75em;
|
||||
margin-bottom: 1em;
|
||||
border: 1px solid var(--primary);
|
||||
position: relative;
|
||||
border: 1px solid var(--primary-low);
|
||||
|
||||
&.dismissed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.resolved .notice-badge:not(.notice-expired-at),
|
||||
&.resolved a,
|
||||
&.resolved p {
|
||||
&.expired .notice-badge:not(.notice-expired-at),
|
||||
&.expired a,
|
||||
&.expired p {
|
||||
color: var(--primary-medium) !important;
|
||||
}
|
||||
|
||||
.d-icon {
|
||||
margin-right: 0.4em;
|
||||
.notice-badge {
|
||||
padding: 0 .5em;
|
||||
}
|
||||
|
||||
.notice-header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.notice-badge {
|
||||
border: 1px solid var(--primary);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0 0.5em;
|
||||
margin-right: 1em;
|
||||
font-size: 0.9em;
|
||||
line-height: 25px;
|
||||
min-height: 25px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&:last-of-type {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.warning {
|
||||
.notice-expired-at {
|
||||
border: 1px solid var(--success);
|
||||
background-color: rgba($success, 0.1);
|
||||
color: var(--success);
|
||||
}
|
||||
|
||||
.notice-title {
|
||||
border: 1px solid var(--pavilion-warning);
|
||||
background-color: rgba($pavilion_warning, 0.1);
|
||||
color: var(--pavilion-warning);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.notice-header-right {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.notice-badge {
|
||||
margin-left: .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notice-issued,
|
||||
.notice-resolved {
|
||||
margin-right: 0.3em;
|
||||
}
|
||||
|
||||
.notice-message {
|
||||
p {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.dismiss-notice-container,
|
||||
.hide-notice-container {
|
||||
width: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dismiss-notice,
|
||||
.spinner {
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
right: 1em;
|
||||
color: var(--primary-medium);
|
||||
.hide-notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.d-icon {
|
||||
margin-right: 0;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.disable-important {
|
||||
.notice-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 25px;
|
||||
box-sizing: border-box;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.admin-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wizard-notices-link {
|
||||
position: relative;
|
||||
margin-right: 10px;
|
||||
|
||||
div > a {
|
||||
@include btn;
|
||||
color: var(--secondary) !important;
|
||||
background-color: var(--primary-medium);
|
||||
|
||||
&.active {
|
||||
background-color: var(--tertiary) !important;
|
||||
color: var(--secondary) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.active-notice-count {
|
||||
background-color: $danger;
|
||||
color: $secondary;
|
||||
border-radius: 50%;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
right: 3em;
|
||||
top: 1em;
|
||||
color: var(--primary-medium);
|
||||
top: -8px;
|
||||
right: -8px;
|
||||
font-size: .7em;
|
||||
}
|
||||
|
||||
a.show-notice-message {
|
||||
padding: .25em .5em;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.wizard-notice,
|
||||
.wizard-notice-row:not(.expired):not(.dismissed) {
|
||||
&.info {
|
||||
background-color: rgba($info, 0.1);
|
||||
border: 1px solid rgba($info, 0.5);
|
||||
}
|
||||
&.warning,
|
||||
&.connection-error {
|
||||
background-color: rgba($warning, 0.1);
|
||||
border: 1px solid rgba($warning, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.notice-message {
|
||||
position: relative;
|
||||
|
||||
.cooked-notice-message {
|
||||
background-color: var(--secondary);
|
||||
padding: 1em;
|
||||
z-index: 1;
|
||||
box-shadow: shadow("dropdown");
|
||||
border-top: 1px solid var(--primary-low);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,10 +2,6 @@
|
|||
display: flex;
|
||||
justify-content: flex-start;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
|
|
|
@ -112,6 +112,9 @@ en:
|
|||
select: "Select a wizard to see its logs"
|
||||
viewing: "View recent logs for wizards on the forum"
|
||||
documentation: "Check out the logs documentation"
|
||||
notices:
|
||||
info: "Plugin status and subscription notices"
|
||||
documentation: Check out the notices documentation
|
||||
|
||||
editor:
|
||||
show: "Show"
|
||||
|
@ -485,14 +488,31 @@ en:
|
|||
|
||||
notice:
|
||||
plugin: Custom Wizard Plugin
|
||||
issued: Issued
|
||||
update: Updated
|
||||
resolved: Resolved
|
||||
title:
|
||||
plugin_status_warning: Warning Notice
|
||||
plugin_status_connection_error: Connection Notice
|
||||
subscription_messages_connection_error: Connection Notice
|
||||
disable_important_on_dashboard: disable
|
||||
message: Message
|
||||
time: Time
|
||||
status: Status
|
||||
title: Title
|
||||
dismiss:
|
||||
label: Dismiss
|
||||
title: Dismiss notice
|
||||
dismiss_all:
|
||||
label: Dismiss All
|
||||
title: Dismiss all informational Custom Wizard notices
|
||||
confirm: Are you sure you want to dismiss all informational Custom Wizard notices?
|
||||
active: active
|
||||
created_at: issued
|
||||
updated_at: updated
|
||||
expired_at: expired
|
||||
dismissed_at: dismissed
|
||||
type:
|
||||
label: Type
|
||||
info: Information
|
||||
warning: Warning
|
||||
connection_error: Connection Error
|
||||
|
||||
notices:
|
||||
nav_label: Notices
|
||||
title: Plugin Notices
|
||||
|
||||
wizard_js:
|
||||
group:
|
||||
|
|
|
@ -54,21 +54,21 @@ en:
|
|||
|
||||
notice:
|
||||
connection_error: "Failed to connect to http://%{domain}"
|
||||
compatibility_issue: >
|
||||
The Custom Wizard Plugin has a compatibility issue with the latest version of Discourse.
|
||||
Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse.
|
||||
compatibility_issue:
|
||||
title: The Custom Wizard Plugin is incompatibile with the latest version of Discourse.
|
||||
message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse.
|
||||
plugin_status:
|
||||
connection_error_limit: >
|
||||
We're unable to connect to the Pavilion Plugin Status Server. Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse or the plugin.
|
||||
If this connection issue persists please contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
|
||||
subscription_messages:
|
||||
connection_error_limit: >
|
||||
We're unable to connect to the Pavilion Subscription Server. This will not affect the operation of the plugin.
|
||||
If this connection issue persists please contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
|
||||
connection_error:
|
||||
title: Unable to connect to the Custom Wizard Plugin status server
|
||||
message: Please check the Custom Wizard Plugin status on [%{domain}](http://%{domain}) before updating Discourse. If this issue persists contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
|
||||
subscription_message:
|
||||
connection_error:
|
||||
title: Unable to connect to the Custom Wizard Plugin subscription server
|
||||
message: If this issue persists contact <a href="mailto:support@thepavilion.io">support@thepavilion.io</a> for further assistance.
|
||||
|
||||
site_settings:
|
||||
custom_wizard_enabled: "Enable custom wizards."
|
||||
wizard_redirect_exclude_paths: "Routes excluded from wizard redirects."
|
||||
wizard_recognised_image_upload_formats: "File types which will result in upload displaying an image preview"
|
||||
wizard_apis_enabled: "Enable API features (experimental)."
|
||||
wizard_important_notices_on_dashboard: "Show important notices about the custom wizard plugin on the admin dashboard."
|
||||
wizard_critical_notices_on_dashboard: "Show critical notices about the custom wizard plugin on the admin dashboard."
|
||||
|
|
|
@ -52,6 +52,9 @@ Discourse::Application.routes.append do
|
|||
delete 'admin/wizards/subscription/authorize' => 'admin_subscription#destroy_authentication'
|
||||
|
||||
get 'admin/wizards/notice' => 'admin_notice#index'
|
||||
put 'admin/wizards/notice/:notice_id' => 'admin_notice#dismiss'
|
||||
put 'admin/wizards/notice/:notice_id/dismiss' => 'admin_notice#dismiss'
|
||||
put 'admin/wizards/notice/:notice_id/hide' => 'admin_notice#hide'
|
||||
put 'admin/wizards/notice/dismiss' => 'admin_notice#dismiss_all'
|
||||
get 'admin/wizards/notices' => 'admin_notice#index'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,8 +6,12 @@ class CustomWizard::AdminController < ::Admin::AdminController
|
|||
render_json_dump(
|
||||
#TODO replace with appropriate static?
|
||||
api_section: ["business"].include?(CustomWizard::Subscription.type),
|
||||
notices: ActiveModel::ArraySerializer.new(
|
||||
CustomWizard::Notice.list,
|
||||
active_notice_count: CustomWizard::Notice.active_count,
|
||||
featured_notices: ActiveModel::ArraySerializer.new(
|
||||
CustomWizard::Notice.list(
|
||||
type: CustomWizard::Notice.types[:info],
|
||||
archetype: CustomWizard::Notice.archetypes[:subscription_message]
|
||||
),
|
||||
each_serializer: CustomWizard::NoticeSerializer
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,20 +1,66 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CustomWizard::AdminNoticeController < CustomWizard::AdminController
|
||||
before_action :find_notice, only: [:dismiss]
|
||||
before_action :find_notice, only: [:dismiss, :hide]
|
||||
|
||||
def index
|
||||
render_serialized(CustomWizard::Notice.list(include_recently_expired: true), CustomWizard::NoticeSerializer)
|
||||
type = params[:type]
|
||||
archetype = params[:archtype]
|
||||
page = params[:page].to_i
|
||||
include_all = ActiveRecord::Type::Boolean.new.cast(params[:include_all])
|
||||
visible = ActiveRecord::Type::Boolean.new.cast(params[:visible])
|
||||
|
||||
if type
|
||||
if type.is_a?(Array)
|
||||
type = type.map { |type| CustomWizard::Notice.types[type.to_sym] }
|
||||
else
|
||||
type = CustomWizard::Notice.types[type.to_sym]
|
||||
end
|
||||
end
|
||||
|
||||
if archetype
|
||||
if archetype.is_a?(Array)
|
||||
archetype = archetype.map { |type| CustomWizard::Notice.archetypes[archetype.to_sym] }
|
||||
else
|
||||
archetype = CustomWizard::Notice.archetypes[archetype.to_sym]
|
||||
end
|
||||
end
|
||||
|
||||
notices = CustomWizard::Notice.list(
|
||||
include_all: include_all,
|
||||
page: page,
|
||||
type: type,
|
||||
archetype: archetype,
|
||||
visible: visible
|
||||
)
|
||||
|
||||
render_serialized(notices, CustomWizard::NoticeSerializer, root: :notices)
|
||||
end
|
||||
|
||||
def dismiss
|
||||
if @notice.dismissable? && @notice.dismiss
|
||||
if @notice.dismissable? && @notice.dismiss!
|
||||
render json: success_json.merge(dismissed_at: @notice.dismissed_at)
|
||||
else
|
||||
render json: failed_json
|
||||
end
|
||||
end
|
||||
|
||||
def hide
|
||||
if @notice.can_hide? && @notice.hide!
|
||||
render json: success_json.merge(hidden_at: @notice.hidden_at)
|
||||
else
|
||||
render json: failed_json
|
||||
end
|
||||
end
|
||||
|
||||
def dismiss_all
|
||||
if CustomWizard::Notice.dismiss_all
|
||||
render json: success_json
|
||||
else
|
||||
render json: failed_json
|
||||
end
|
||||
end
|
||||
|
||||
def find_notice
|
||||
@notice = CustomWizard::Notice.find(params[:notice_id])
|
||||
raise Discourse::InvalidParameters.new(:notice_id) unless @notice
|
||||
|
|
|
@ -7,42 +7,61 @@ class CustomWizard::Notice
|
|||
"tests-passed" => "plugins.discourse.pavilion.tech",
|
||||
"stable" => "stable.plugins.discourse.pavilion.tech"
|
||||
}
|
||||
SUBSCRIPTION_MESSAGES_DOMAIN = "test.thepavilion.io"
|
||||
SUBSCRIPTION_MESSAGE_DOMAIN = "test.thepavilion.io"
|
||||
LOCALHOST_DOMAIN = "localhost:3000"
|
||||
PLUGIN_STATUSES_TO_WARN = %w(incompatible tests_failing)
|
||||
CHECK_PLUGIN_STATUS_ON_BRANCH = %w(tests-passed main stable)
|
||||
PAGE_LIMIT = 30
|
||||
|
||||
attr_reader :id,
|
||||
:title,
|
||||
:message,
|
||||
:type,
|
||||
:archetype,
|
||||
:created_at
|
||||
|
||||
attr_accessor :retrieved_at,
|
||||
:updated_at,
|
||||
:dismissed_at,
|
||||
:expired_at
|
||||
:expired_at,
|
||||
:hidden_at
|
||||
|
||||
def initialize(attrs)
|
||||
@id = Digest::SHA1.hexdigest(attrs[:message])
|
||||
@id = self.class.generate_notice_id(attrs[:title], attrs[:created_at])
|
||||
@title = attrs[:title]
|
||||
@message = attrs[:message]
|
||||
@type = attrs[:type].to_i
|
||||
@archetype = attrs[:archetype].to_i
|
||||
@created_at = attrs[:created_at]
|
||||
@updated_at = attrs[:updated_at]
|
||||
@retrieved_at = attrs[:retrieved_at]
|
||||
@dismissed_at = attrs[:dismissed_at]
|
||||
@expired_at = attrs[:expired_at]
|
||||
@hidden_at = attrs[:hidden_at]
|
||||
end
|
||||
|
||||
def dismiss
|
||||
def dismiss!
|
||||
if dismissable?
|
||||
self.dismissed_at = Time.now
|
||||
self.save
|
||||
self.class.publish_notice_count
|
||||
end
|
||||
end
|
||||
|
||||
def expire
|
||||
def hide!
|
||||
if can_hide?
|
||||
self.hidden_at = Time.now
|
||||
self.save
|
||||
self.class.publish_notice_count
|
||||
end
|
||||
end
|
||||
|
||||
def expire!
|
||||
if !expired?
|
||||
self.expired_at = Time.now
|
||||
self.save
|
||||
self.class.publish_notice_count
|
||||
end
|
||||
end
|
||||
|
||||
def expired?
|
||||
|
@ -54,15 +73,33 @@ class CustomWizard::Notice
|
|||
end
|
||||
|
||||
def dismissable?
|
||||
true
|
||||
!expired? && !dismissed? && type === self.class.types[:info]
|
||||
end
|
||||
|
||||
def hidden?
|
||||
hidden_at.present?
|
||||
end
|
||||
|
||||
def can_hide?
|
||||
!hidden? && (
|
||||
type === self.class.types[:connection_error] ||
|
||||
type === self.class.types[:warning]
|
||||
) && (
|
||||
archetype === self.class.archetypes[:plugin_status]
|
||||
)
|
||||
end
|
||||
|
||||
def save
|
||||
attrs = {
|
||||
expired_at: expired_at,
|
||||
updated_at: updated_at,
|
||||
retrieved_at: retrieved_at,
|
||||
created_at: created_at,
|
||||
hidden_at: hidden_at,
|
||||
title: title,
|
||||
message: message,
|
||||
type: type
|
||||
type: type,
|
||||
archetype: archetype
|
||||
}
|
||||
|
||||
if current = self.class.find(self.id)
|
||||
|
@ -75,9 +112,15 @@ class CustomWizard::Notice
|
|||
def self.types
|
||||
@types ||= Enum.new(
|
||||
info: 0,
|
||||
plugin_status_warning: 1,
|
||||
plugin_status_connection_error: 2,
|
||||
subscription_messages_connection_error: 3
|
||||
warning: 1,
|
||||
connection_error: 2
|
||||
)
|
||||
end
|
||||
|
||||
def self.archetypes
|
||||
@archetypes ||= Enum.new(
|
||||
subscription_message: 0,
|
||||
plugin_status: 1
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -85,7 +128,7 @@ class CustomWizard::Notice
|
|||
notices = []
|
||||
|
||||
if !skip_subscription
|
||||
subscription_messages = request(:subscription_messages)
|
||||
subscription_messages = request(:subscription_message)
|
||||
|
||||
if subscription_messages.present?
|
||||
subscription_notices = convert_subscription_messages_to_notices(subscription_messages[:messages])
|
||||
|
@ -96,27 +139,46 @@ class CustomWizard::Notice
|
|||
if !skip_plugin && request_plugin_status?
|
||||
plugin_status = request(:plugin_status)
|
||||
|
||||
if plugin_status.present? && plugin_status[:status].present? && plugin_status[:status].is_a?(Hash)
|
||||
plugin_notice = convert_plugin_status_to_notice(plugin_status[:status])
|
||||
if plugin_status.present? && plugin_status[:status].present?
|
||||
plugin_notice = convert_plugin_status_to_notice(plugin_status)
|
||||
notices.push(plugin_notice) if plugin_notice
|
||||
end
|
||||
end
|
||||
|
||||
if notices.any?
|
||||
|
||||
notices.each do |notice_data|
|
||||
notice = new(notice_data)
|
||||
notice.retrieved_at = Time.now
|
||||
notice.save
|
||||
end
|
||||
|
||||
publish_notice_count
|
||||
end
|
||||
end
|
||||
|
||||
def self.publish_notice_count
|
||||
payload = {
|
||||
active_notice_count: CustomWizard::Notice.active_count
|
||||
}
|
||||
MessageBus.publish("/custom-wizard/notices", payload, group_ids: [Group::AUTO_GROUPS[:admins]])
|
||||
end
|
||||
|
||||
def self.convert_subscription_messages_to_notices(messages)
|
||||
messages.map do |message|
|
||||
{
|
||||
messages.reduce([]) do |result, message|
|
||||
notice_id = generate_notice_id(message[:title], message[:created_at])
|
||||
|
||||
unless exists?(notice_id)
|
||||
result.push(
|
||||
title: message[:title],
|
||||
message: message[:message],
|
||||
type: types[message[:type].to_sym],
|
||||
archetype: archetypes[:subscription_message],
|
||||
created_at: message[:created_at],
|
||||
expired_at: message[:expired_at]
|
||||
}
|
||||
)
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -124,22 +186,32 @@ class CustomWizard::Notice
|
|||
notice = nil
|
||||
|
||||
if PLUGIN_STATUSES_TO_WARN.include?(plugin_status[:status])
|
||||
title = I18n.t('wizard.notice.compatibility_issue.title')
|
||||
created_at = plugin_status[:status_changed_at]
|
||||
id = generate_notice_id(title, created_at)
|
||||
|
||||
unless exists?(id)
|
||||
message = I18n.t('wizard.notice.compatibility_issue.message', domain: plugin_status_domain)
|
||||
notice = {
|
||||
message: I18n.t('wizard.notice.compatibility_issue', domain: plugin_status_domain),
|
||||
type: types[:plugin_status_warning],
|
||||
created_at: plugin_status[:status_changed_at]
|
||||
id: id,
|
||||
title: title,
|
||||
message: message,
|
||||
type: types[:warning],
|
||||
archetype: archetypes[:plugin_status],
|
||||
created_at: created_at
|
||||
}
|
||||
end
|
||||
else
|
||||
expire_notices(types[:plugin_status_warning])
|
||||
expire_all(types[:warning], archetypes[:plugin_status])
|
||||
end
|
||||
|
||||
notice
|
||||
end
|
||||
|
||||
def self.notify_connection_errors(connection_type_key)
|
||||
domain = self.send("#{connection_type_key.to_s}_domain")
|
||||
message = I18n.t("wizard.notice.#{connection_type_key.to_s}.connection_error_limit", domain: domain)
|
||||
notices = list(type: types[:connection_error], message: message)
|
||||
def self.notify_connection_errors(archetype)
|
||||
domain = self.send("#{archetype.to_s}_domain")
|
||||
title = I18n.t("wizard.notice.#{archetype.to_s}.connection_error.title")
|
||||
notices = list(type: types[:connection_error], archetype: archetypes[archetype.to_sym], title: title)
|
||||
|
||||
if notices.any?
|
||||
notice = notices.first
|
||||
|
@ -147,28 +219,29 @@ class CustomWizard::Notice
|
|||
notice.save
|
||||
else
|
||||
notice = new(
|
||||
message: message,
|
||||
type: types["#{connection_type_key}_connection_error".to_sym],
|
||||
created_at: Time.now
|
||||
title: title,
|
||||
message: I18n.t("wizard.notice.#{archetype.to_s}.connection_error.message", domain: domain),
|
||||
archetype: archetypes[archetype.to_sym],
|
||||
type: types[:connection_error],
|
||||
created_at: Time.now,
|
||||
updated_at: Time.now
|
||||
)
|
||||
notice.save
|
||||
end
|
||||
end
|
||||
|
||||
def self.expire_notices(type)
|
||||
list(type: type).each(&:expire)
|
||||
publish_notice_count
|
||||
end
|
||||
|
||||
def self.request_plugin_status?
|
||||
CHECK_PLUGIN_STATUS_ON_BRANCH.include?(Discourse.git_branch) || Rails.env.test? || Rails.env.development?
|
||||
end
|
||||
|
||||
def self.subscription_messages_domain
|
||||
(Rails.env.test? || Rails.env.development?) ? LOCALHOST_DOMAIN : SUBSCRIPTION_MESSAGES_DOMAIN
|
||||
def self.subscription_message_domain
|
||||
(Rails.env.test? || Rails.env.development?) ? LOCALHOST_DOMAIN : SUBSCRIPTION_MESSAGE_DOMAIN
|
||||
end
|
||||
|
||||
def self.subscription_messages_url
|
||||
"http://#{subscription_messages_domain}/subscription-server/messages.json"
|
||||
def self.subscription_message_url
|
||||
"http://#{subscription_message_domain}/subscription-server/messages.json"
|
||||
end
|
||||
|
||||
def self.plugin_status_domain
|
||||
|
@ -180,14 +253,19 @@ class CustomWizard::Notice
|
|||
"http://#{plugin_status_domain}/plugin-manager/status/discourse-custom-wizard"
|
||||
end
|
||||
|
||||
def self.request(type)
|
||||
url = self.send("#{type.to_s}_url")
|
||||
response = Excon.get(url)
|
||||
connection_error = CustomWizard::Notice::ConnectionError.new(type)
|
||||
def self.request(archetype)
|
||||
url = self.send("#{archetype.to_s}_url")
|
||||
|
||||
if response.status == 200
|
||||
begin
|
||||
response = Excon.get(url)
|
||||
rescue Excon::Error::Socket, Excon::Error::Timeout => e
|
||||
response = nil
|
||||
end
|
||||
connection_error = CustomWizard::Notice::ConnectionError.new(archetype)
|
||||
|
||||
if response && response.status == 200
|
||||
connection_error.expire!
|
||||
expire_notices(types["#{type}_connection_error".to_sym])
|
||||
expire_all(types[:connection_error], archetypes[archetype.to_sym])
|
||||
|
||||
begin
|
||||
data = JSON.parse(response.body).deep_symbolize_keys
|
||||
|
@ -198,8 +276,7 @@ class CustomWizard::Notice
|
|||
data
|
||||
else
|
||||
connection_error.create!
|
||||
notify_connection_errors(type) if connection_error.reached_limit?
|
||||
|
||||
notify_connection_errors(archetype) if connection_error.reached_limit?
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
@ -213,20 +290,65 @@ class CustomWizard::Notice
|
|||
new(raw.symbolize_keys) if raw.present?
|
||||
end
|
||||
|
||||
def self.exists?(id)
|
||||
PluginStoreRow.where(plugin_name: namespace, key: id).exists?
|
||||
end
|
||||
|
||||
def self.store(id, raw_notice)
|
||||
PluginStore.set(namespace, id, raw_notice)
|
||||
end
|
||||
|
||||
def self.list_query(type: nil, message: nil, include_recently_expired: false)
|
||||
def self.list_query(type: nil, archetype: nil, title: nil, include_all: false, include_recently_expired: false, page: nil, visible: false)
|
||||
query = PluginStoreRow.where(plugin_name: namespace)
|
||||
query = query.where("(value::json->>'expired_at') IS NULL#{include_recently_expired ? " OR (value::json->>'expired_at')::date > now()::date - 1" : ""}")
|
||||
query = query.where("(value::json->>'type')::integer = ?", type) if type
|
||||
query = query.where("(value::json->>'message')::text = ?", message) if message
|
||||
query.order("value::json->>'created_at' DESC")
|
||||
query = query.where("(value::json->>'hidden_at') IS NULL") if visible
|
||||
query = query.where("(value::json->>'dismissed_at') IS NULL") unless include_all
|
||||
query = query.where("(value::json->>'expired_at') IS NULL#{include_recently_expired ? " OR (value::json->>'expired_at')::date > now()::date - 1" : ""}") unless include_all
|
||||
query = query.where("(value::json->>'archetype')::integer = ?", archetype) if archetype
|
||||
if type
|
||||
type_query_str = type.is_a?(Array) ? "(value::json->>'type')::integer IN (?)" : "(value::json->>'type')::integer = ?"
|
||||
query = query.where(type_query_str, type)
|
||||
end
|
||||
query = query.where("(value::json->>'title')::text = ?", title) if title
|
||||
query = query.limit(PAGE_LIMIT).offset(page.to_i * PAGE_LIMIT) if !page.nil?
|
||||
query.order("value::json->>'expired_at' DESC, value::json->>'updated_at' DESC,value::json->>'dismissed_at' DESC, value::json->>'created_at' DESC")
|
||||
end
|
||||
|
||||
def self.list(type: nil, message: nil, include_recently_expired: false)
|
||||
list_query(type: type, message: message, include_recently_expired: include_recently_expired)
|
||||
def self.list(type: nil, archetype: nil, title: nil, include_all: false, include_recently_expired: false, page: 0, visible: false)
|
||||
list_query(type: type, archetype: archetype, title: title, include_all: include_all, include_recently_expired: include_recently_expired, page: page, visible: visible)
|
||||
.map { |r| self.new(JSON.parse(r.value).symbolize_keys) }
|
||||
end
|
||||
|
||||
def self.active_count
|
||||
list_query.count
|
||||
end
|
||||
|
||||
def self.dismiss_all
|
||||
dismissed_count = PluginStoreRow.where("
|
||||
plugin_name = '#{namespace}' AND
|
||||
(value::json->>'type')::integer = #{types[:info]} AND
|
||||
(value::json->>'expired_at') IS NULL AND
|
||||
(value::json->>'dismissed_at') IS NULL
|
||||
").update_all("
|
||||
value = jsonb_set(value::jsonb, '{dismissed_at}', (to_json(now())::text)::jsonb, true)
|
||||
")
|
||||
publish_notice_count if dismissed_count.to_i > 0
|
||||
dismissed_count
|
||||
end
|
||||
|
||||
def self.expire_all(type, archetype)
|
||||
expired_count = PluginStoreRow.where("
|
||||
plugin_name = '#{namespace}' AND
|
||||
(value::json->>'type')::integer = #{type} AND
|
||||
(value::json->>'archetype')::integer = #{archetype} AND
|
||||
(value::json->>'expired_at') IS NULL
|
||||
").update_all("
|
||||
value = jsonb_set(value::jsonb, '{expired_at}', (to_json(now())::text)::jsonb, true)
|
||||
")
|
||||
publish_notice_count if expired_count.to_i > 0
|
||||
expired_count
|
||||
end
|
||||
|
||||
def self.generate_notice_id(title, created_at)
|
||||
Digest::SHA1.hexdigest("#{title}-#{created_at}")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,80 +2,73 @@
|
|||
|
||||
class CustomWizard::Notice::ConnectionError
|
||||
|
||||
attr_reader :type_key
|
||||
attr_reader :archetype
|
||||
|
||||
def initialize(type_key)
|
||||
@type_key = type_key
|
||||
def initialize(archetype)
|
||||
@archetype = archetype
|
||||
end
|
||||
|
||||
def create!
|
||||
id = "#{type_key.to_s}_error"
|
||||
|
||||
if attrs = PluginStore.get(namespace, id)
|
||||
if attrs = current_error
|
||||
key = "#{archetype.to_s}_error_#{attrs["id"]}"
|
||||
attrs['updated_at'] = Time.now
|
||||
attrs['count'] = attrs['count'].to_i + 1
|
||||
else
|
||||
domain = CustomWizard::Notice.send("#{type_key.to_s}_domain")
|
||||
domain = CustomWizard::Notice.send("#{archetype.to_s}_domain")
|
||||
id = SecureRandom.hex(8)
|
||||
attrs = {
|
||||
id: id,
|
||||
message: I18n.t("wizard.notice.connection_error", domain: domain),
|
||||
type: self.class.types[type_key],
|
||||
archetype: CustomWizard::Notice.archetypes[archetype.to_sym],
|
||||
created_at: Time.now,
|
||||
count: 1
|
||||
}
|
||||
key = "#{archetype.to_s}_error_#{id}"
|
||||
end
|
||||
|
||||
PluginStore.set(namespace, id, attrs)
|
||||
PluginStore.set(namespace, key, attrs)
|
||||
@errors = nil
|
||||
end
|
||||
|
||||
def expire!
|
||||
if errors.exists?
|
||||
errors.each do |error_row|
|
||||
error = JSON.parse(error_row.value)
|
||||
if query = current_error(query_only: true)
|
||||
record = query.first
|
||||
error = JSON.parse(record.value)
|
||||
error['expired_at'] = Time.now
|
||||
error_row.value = error.to_json
|
||||
error_row.save
|
||||
record.value = error.to_json
|
||||
record.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.types
|
||||
@types ||= Enum.new(
|
||||
plugin_status: 0,
|
||||
subscription_messages: 1
|
||||
)
|
||||
end
|
||||
|
||||
def plugin_status_limit
|
||||
5
|
||||
end
|
||||
|
||||
def subscription_messages_limit
|
||||
def subscription_message_limit
|
||||
10
|
||||
end
|
||||
|
||||
def limit
|
||||
self.send("#{type_key.to_s}_limit")
|
||||
self.send("#{archetype.to_s}_limit")
|
||||
end
|
||||
|
||||
def reached_limit?
|
||||
return false unless errors.exists?
|
||||
return false unless current_error.present?
|
||||
current_error['count'].to_i >= limit
|
||||
end
|
||||
|
||||
def current_error
|
||||
JSON.parse(errors.first.value)
|
||||
end
|
||||
|
||||
def namespace
|
||||
"#{CustomWizard::PLUGIN_NAME}_notice_connection"
|
||||
end
|
||||
|
||||
def errors
|
||||
@errors ||= begin
|
||||
def current_error(query_only: false)
|
||||
@current_error ||= begin
|
||||
query = PluginStoreRow.where(plugin_name: namespace)
|
||||
query = query.where("(value::json->>'type')::integer = ?", self.class.types[type_key])
|
||||
query.where("(value::json->>'expired_at') IS NULL")
|
||||
query = query.where("(value::json->>'archetype')::integer = ?", CustomWizard::Notice.archetypes[archetype])
|
||||
query = query.where("(value::json->>'expired_at') IS NULL")
|
||||
return nil if !query.exists?
|
||||
return query if query_only
|
||||
JSON.parse(query.first.value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -245,7 +245,10 @@ after_initialize do
|
|||
end
|
||||
|
||||
AdminDashboardData.add_problem_check do
|
||||
warning_notices = CustomWizard::Notice.list(type: CustomWizard::Notice.types[:plugin_status_warning])
|
||||
warning_notices = CustomWizard::Notice.list(
|
||||
type: CustomWizard::Notice.types[:warning],
|
||||
archetype: CustomWizard::Notice.archetypes[:plugin_status]
|
||||
)
|
||||
warning_notices.any? ? ActionView::Base.full_sanitizer.sanitize(warning_notices.first.message, tags: %w(a)) : nil
|
||||
end
|
||||
|
||||
|
|
|
@ -2,19 +2,27 @@
|
|||
|
||||
class CustomWizard::NoticeSerializer < ApplicationSerializer
|
||||
attributes :id,
|
||||
:title,
|
||||
:message,
|
||||
:type,
|
||||
:archetype,
|
||||
:created_at,
|
||||
:expired_at,
|
||||
:updated_at,
|
||||
:dismissed_at,
|
||||
:retrieved_at,
|
||||
:dismissable
|
||||
:hidden_at,
|
||||
:dismissable,
|
||||
:can_hide
|
||||
|
||||
def dismissable
|
||||
object.dismissable?
|
||||
end
|
||||
|
||||
def can_hide
|
||||
object.can_hide?
|
||||
end
|
||||
|
||||
def type
|
||||
CustomWizard::Notice.types.key(object.type)
|
||||
end
|
||||
|
|
Laden …
In neuem Issue referenzieren