"devDependencies": {
"@babel/eslint-parser": "^7.22.11",
"@babel/preset-react": "^7.13.13",
+ "@codemirror/lang-json": "^6.0.2",
"@popperjs/core": "^2.10.2",
"@tailwindcss/forms": "^0.5.6",
"@testing-library/jest-dom": "^6.4.2",
"@lezer/javascript": "^1.0.0"
}
},
+ "node_modules/@codemirror/lang-json": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz",
+ "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@codemirror/language": "^6.0.0",
+ "@lezer/json": "^1.0.0"
+ }
+ },
"node_modules/@codemirror/language": {
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.1.tgz",
"@lezer/lr": "^1.3.0"
}
},
+ "node_modules/@lezer/json": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz",
+ "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@lezer/common": "^1.2.0",
+ "@lezer/highlight": "^1.0.0",
+ "@lezer/lr": "^1.0.0"
+ }
+ },
"node_modules/@lezer/lr": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
"devDependencies": {
"@babel/eslint-parser": "^7.22.11",
"@babel/preset-react": "^7.13.13",
+ "@codemirror/lang-json": "^6.0.2",
"@popperjs/core": "^2.10.2",
"@tailwindcss/forms": "^0.5.6",
"@testing-library/jest-dom": "^6.4.2",
import PropTypes from 'prop-types';
import React from 'react';
-import { useTranslation } from 'react-i18next';
-import { getUserName } from '../../helpers/User';
+import GuildProtocolEntry from './GuildProtocolEntry';
const GuildProtocol = ({ protocol }) => {
- const { t } = useTranslation();
-
return protocol.map((entry) =>
- <div className="discord-bot-protocol border-top" key={entry.id}>
- <div className="d-flex justify-content-between">
- <span>{t(`discordBot.commandType.${entry.command}`)}</span>
- <span>{t(`discordBot.commandStatus.${entry.status}`)}</span>
- </div>
- <div className="d-flex justify-content-between">
- <span className="text-muted">
- {entry.user
- ? t('discordBot.commandTimeUser', { time: new Date(entry.created_at), user: getUserName(entry.user) })
- : t('discordBot.commandTime', { time: new Date(entry.created_at) })
- }
- </span>
- <span className="text-muted">
- {entry.executed_at
- ? t('discordBot.commandTime', { time: new Date(entry.executed_at) })
- : t('discordBot.commandPending')
- }
- </span>
- </div>
- </div>
+ <GuildProtocolEntry entry={entry} key={entry.id} />
);
};
--- /dev/null
+import { json } from '@codemirror/lang-json';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+import { githubDark } from '@uiw/codemirror-theme-github';
+import CodeMirror from '@uiw/react-codemirror';
+
+import { getUserName } from '../../helpers/User';
+
+const GuildProtocolEntry = ({ entry }) => {
+ const [showDetail, setShowDeatil] = React.useState(false);
+
+ const { t } = useTranslation();
+
+ const className = React.useMemo(() => {
+ const classNames = ['discord-bot-protocol', 'border-top'];
+ if (entry.status === 'exception') {
+ classNames.push('has-error');
+ }
+ return classNames.join(' ');
+ }, [entry]);
+
+ const statusLine = React.useMemo(() => {
+ const info = {};
+ if (entry.status === 'exception') {
+ if (entry.result && entry.result.type === 'Discord\\Http\\Exceptions\\NoPermissionsException') {
+ info.message = t('discordBot.commandResult.permissionDenied');
+ } else {
+ info.message = entry.result.type;
+ }
+ }
+ return t(`discordBot.commandStatus.${entry.status}`, info);
+ }, [entry, t]);
+
+ return <div className={className}>
+ <div className="d-flex justify-content-between">
+ <span className="command-type">{t(`discordBot.commandType.${entry.command}`)}</span>
+ {entry.parameters || entry.result ?
+ <Button
+ className="command-status p-0"
+ onClick={() => setShowDeatil(d => !d)}
+ variant="link"
+ >
+ {statusLine}
+ </Button>
+ :
+ <span className="command-status">{statusLine}</span>
+ }
+ </div>
+ <div className="d-flex justify-content-between">
+ <span className="text-muted command-create-time">
+ {entry.user
+ ? t('discordBot.commandTimeUser', { time: new Date(entry.created_at), user: getUserName(entry.user) })
+ : t('discordBot.commandTime', { time: new Date(entry.created_at) })
+ }
+ </span>
+ <span className="text-muted command-execute-time">
+ {entry.executed_at
+ ? t('discordBot.commandTime', { time: new Date(entry.executed_at) })
+ : t('discordBot.commandPending')
+ }
+ </span>
+ </div>
+ <div className={`command-detail ${showDetail ? '' : 'd-none'}`}>
+ {entry.parameters ? <figure>
+ <caption>{t('discordBot.commandParameters')}</caption>
+ <CodeMirror
+ extensions={[json()]}
+ readOnly
+ theme={githubDark}
+ value={JSON.stringify(entry.parameters, null, 2)}
+ />
+ </figure> : null}
+ {entry.result ? <figure>
+ <caption>{t('discordBot.commandResult.heading')}</caption>
+ <CodeMirror
+ extensions={[json()]}
+ readOnly
+ theme={githubDark}
+ value={JSON.stringify(entry.result, null, 2)}
+ />
+ </figure> : null}
+ </div>
+ </div>;
+};
+
+GuildProtocolEntry.propTypes = {
+ entry: PropTypes.shape({
+ command: PropTypes.string,
+ created_at: PropTypes.string,
+ executed_at: PropTypes.string,
+ id: PropTypes.number,
+ parameters: PropTypes.shape({
+ }),
+ result: PropTypes.shape({
+ type: PropTypes.string,
+ }),
+ status: PropTypes.string,
+ user: PropTypes.shape({
+ }),
+ }).isRequired,
+};
+
+export default GuildProtocolEntry;
addUser: 'User abonnieren',
channel: 'Kanal',
channelControls: 'Kanal-Steuerung',
+ commandParameters: 'Parameter',
commandPending: 'Steht aus',
+ commandResult: {
+ heading: 'Ergebnis',
+ permissionDenied: 'Zugriff verweigert',
+ },
commandStatus: {
done: 'Abgeschlossen',
- exception: 'Fehler',
+ exception: 'Fehler: {{ message }}',
executing: 'Ausführen',
hold: 'Pausiert',
pending: 'Ausstehend',
addUser: 'Subscribe to user',
channel: 'Channel',
channelControls: 'Channel controls',
+ commandParameters: 'Parameters',
commandPending: 'Pending execution',
+ commandResult: {
+ heading: 'Result',
+ permissionDenied: 'Permission denied',
+ },
commandStatus: {
done: 'Done',
- exception: 'Error',
+ exception: 'Error: {{ message }}',
executing: 'Executing',
hold: 'Hold',
pending: 'Pending',
}
}
+pre.code {
+ background: $dark;
+ color: $light;
+ padding: .5ex 1ex;
+}
+
@keyframes loading-pulse {
from {
box-shadow: 0 0 0 2em #aaa;
}
}
+.discord-bot-protocol {
+ .command-status {
+ color: inherit;
+ text-decoration: none;
+ }
+ &.has-error {
+ .command-status {
+ color: $warning;
+ }
+ }
+}
+
.guild-box {
padding: 0;
color: inherit;