ce7cb702218693d08ea6d83464d2457c9250027c

This commit is contained in:
ms2k 2026-11-12 20:46:28 +09:00
commit 34c5f8963c
4 changed files with 145336 additions and 0 deletions

View file

@ -0,0 +1,41 @@
import React, { useEffect, useState } from 'react';
import { getBackendSrv } from '@grafana/runtime';
import { Alert, Button, Input, Stack } from '@grafana/ui';
const root = '/api/plugins/quorate-rcon-app/resources';
export function Console() {
const [allowed, setAllowed] = useState(false);
const [command, setCommand] = useState('');
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const [transcript, setTranscript] = useState<Array<{ command: string; output: string }>>([]);
useEffect(() => {
getBackendSrv().get<{ submit: boolean }>(`${root}/access`).then(v => setAllowed(v.submit)).catch(() => setError('Command authorization is unavailable.'));
}, []);
async function submit(e: React.FormEvent) {
e.preventDefault();
if (busy || !allowed || !command.trim()) { return; }
setBusy(true); setError('');
const submitted = command;
try {
const result = await getBackendSrv().post<{ output: string }>(`${root}/command`, { command: submitted }, { retry: 0 });
setTranscript(items => [...items, { command: submitted, output: result.output }]);
setCommand('');
} catch (e) {
const failure = e as { data?: { message?: string }; message?: string };
setError(failure.data?.message || failure.message || 'Command failed. Check server state before resubmitting.');
} finally { setBusy(false); }
}
return <div style={{ height: '100%', overflow: 'auto', padding: 8 }}>
{!allowed && <Alert title="Commands require a Grafana administrator" severity="info">You can continue viewing server metrics.</Alert>}
{error && <Alert title={error} severity="error" />}
{allowed && <form onSubmit={submit}><Stack gap={1}>
<Input aria-label="Minecraft command" placeholder="list" value={command} disabled={busy} onChange={e => setCommand(e.currentTarget.value)} />
<Button type="submit" disabled={busy || !command.trim()}>{busy ? 'Submitting…' : 'Submit'}</Button>
</Stack></form>}
<div role="log" aria-label="Command output">
{transcript.map((item, i) => <div key={i}><strong>{`> ${item.command}`}</strong><pre style={{ whiteSpace: 'pre-wrap', overflowWrap: 'anywhere' }}>{item.output}</pre></div>)}
</div>
</div>;
}

View file

@ -0,0 +1,2 @@
import { AppPlugin } from '@grafana/data';
export const plugin = new AppPlugin();

View file

@ -0,0 +1,17 @@
import { DataSourceApi, DataQuery, DataQueryRequest, DataQueryResponse, DataSourcePlugin, DataSourceInstanceSettings } from '@grafana/data';
import { of } from 'rxjs';
import { getBackendSrv } from '@grafana/runtime';
import { Console } from './Console';
class DataSource extends DataSourceApi<DataQuery> {
constructor(settings: DataSourceInstanceSettings) { super(settings); }
query(_request: DataQueryRequest<DataQuery>) { return of<DataQueryResponse>({ data: [] }); }
async testDatasource() {
try {
await getBackendSrv().get('/api/plugins/quorate-rcon-app/resources/health');
return { status: 'success', message: 'RCON connection and authentication succeeded.' };
} catch {
return { status: 'error', message: 'RCON connection or authorization failed.' };
}
}
}
export const plugin = new DataSourcePlugin(DataSource).setQueryEditor(Console);

View file

@ -0,0 +1,3 @@
import { PanelPlugin } from '@grafana/data';
import { Console } from './Console';
export const plugin = new PanelPlugin(Console);