feat: F4.6g2 the DNS server window, its records being /dns/hosts
Oracle DnsWindowRun uses Java's DNS server window as a person does and asks a computer's resolver after each step. The core's DNS server is a program its window changes: on and off (its socket on port 53), recursive resolution, and records that are /dns/hosts, read on every query as Java's server reads them (the reader moves from fls to the core). The snapshot carries every installed DNS server with whether it runs. The room takes the window's actions, sends its view and writes the server back after the run; the editor shows Java's window with its three tabs. Not taken from Java: a cell edit spilling into records of another type, an add losing what another program wrote into the file, an empty mail domain, a removal hitting the first record of the name. A table cell no longer takes the room's value over what is being typed (the web server's too). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,99 @@
|
||||
// The DNS server on a computer's desktop in Chromium (F4.6g2), on FILIUS'
|
||||
// example email_komplett: Java's button and box, the address field with the
|
||||
// computer's own address, a record refused with Java's valid example, one
|
||||
// added, a cell written and one refused, a row removed, the MX tab, the
|
||||
// server stopped; and another computer's command line resolving the new name.
|
||||
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
import { drawing, onDevice, openEditor, serveEditor } from './editorPage.js';
|
||||
|
||||
let server: { readonly base: string; readonly close: () => Promise<void> };
|
||||
|
||||
test.beforeAll(async () => {
|
||||
server = await serveEditor();
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
async function desktopOf(page: Page, name: string) {
|
||||
const id = (await drawing(page)).nodes.find((node) => node.name === name)!.id;
|
||||
const at = await onDevice(page, id);
|
||||
await page.mouse.click(at.x, at.y);
|
||||
return page.locator(`section[data-desktop="${id}"]`);
|
||||
}
|
||||
|
||||
test('keeps the records as Java’s DNS window does, and the server answers with them', async ({ page }) => {
|
||||
test.setTimeout(120_000);
|
||||
await openEditor(page, server.base, 'email_komplett.fls');
|
||||
await page.getByRole('button', { name: 'Wechselt in den Aktionsmodus' }).click();
|
||||
await expect(page.locator('svg.filius-canvas')).toHaveAttribute('data-mode', 'simulation');
|
||||
const desktop = await desktopOf(page, 'DNS-Server');
|
||||
await desktop.locator('[data-program="dnsServer"]').click();
|
||||
const window = desktop.locator('[data-program-window="dnsServer"]');
|
||||
await expect(window.locator('.program-titlebar h3')).toHaveText('DNS-Server');
|
||||
await expect(window.getByRole('button', { name: 'Beenden' })).toBeVisible();
|
||||
await expect(window.getByRole('tab')).toHaveText(['Adressen (A)', 'Mailaustausch (MX)', 'Nameserver (NS)']);
|
||||
const name = window.getByRole('textbox', { name: 'Host-/Domainname:' });
|
||||
const address = window.getByRole('textbox', { name: 'IP-Adresse:' });
|
||||
await expect(address).toHaveValue('141.99.50.230');
|
||||
const rows = window.getByRole('table', { name: 'Einträge' }).locator('tbody tr');
|
||||
const before = await rows.count();
|
||||
|
||||
// Not taken: Java's valid example.
|
||||
await name.fill('kein name!');
|
||||
await expect(name).toHaveAttribute('aria-invalid', 'true');
|
||||
await window.getByRole('button', { name: 'Hinzufügen' }).click();
|
||||
await expect(window.getByRole('alert')).toHaveText('Eingaben sind ungültig! Valides Beispiel: \n> IP-Adresse: 192.168.0.10\n> Domainname: www.filius.de');
|
||||
await name.fill('neu.schule.de');
|
||||
await address.fill('10.0.0.1');
|
||||
await window.getByRole('button', { name: 'Hinzufügen' }).click();
|
||||
await expect(rows).toHaveCount(before + 1);
|
||||
await expect([await name.inputValue(), await address.inputValue()]).toEqual(['', '']);
|
||||
const last = rows.last();
|
||||
await expect(last.locator('input').first()).toHaveValue('neu.schule.de.');
|
||||
|
||||
// A cell written; one the check does not take is marked and goes back.
|
||||
const cell = last.locator('input').nth(1);
|
||||
await cell.fill('10.0.0.2');
|
||||
await cell.press('Enter');
|
||||
await expect(cell).toHaveValue('10.0.0.2');
|
||||
await cell.fill('10.0.0');
|
||||
await expect(cell).toHaveAttribute('aria-invalid', 'true');
|
||||
await cell.blur();
|
||||
await expect(cell).toHaveValue('10.0.0.2');
|
||||
|
||||
// A computer whose DNS server it is resolves the new name (the desktop closed, which covered it).
|
||||
await desktop.locator('.desktop-close').click();
|
||||
const web = await desktopOf(page, 'Web-Server');
|
||||
await web.locator('[data-program="installation"]').click();
|
||||
const installation = web.locator('[data-program-window="installation"]');
|
||||
await installation.locator('[data-list="available"] [role="option"]').filter({ hasText: 'Befehlszeile' }).click();
|
||||
await installation.getByRole('button', { name: 'Installieren' }).click();
|
||||
await installation.getByRole('button', { name: 'Änderungen annehmen' }).click();
|
||||
await web.locator('[data-program="commandPrompt"]').click();
|
||||
const input = web.locator('input[name="terminalInput"]');
|
||||
await input.fill('host neu.schule.de');
|
||||
await input.press('Enter');
|
||||
await expect(web.locator('.terminal-text')).toContainText('10.0.0.2', { timeout: 30_000 });
|
||||
await web.locator('.desktop-close').click();
|
||||
await desktopOf(page, 'DNS-Server');
|
||||
await desktop.locator('[data-program="dnsServer"]').click();
|
||||
|
||||
// A row chosen and removed; the MX tab; the box and the button.
|
||||
await rows.last().locator('td').first().dispatchEvent('pointerdown');
|
||||
await window.getByRole('button', { name: 'Auswahl entfernen' }).click();
|
||||
await expect(rows).toHaveCount(before);
|
||||
await window.getByRole('tab', { name: 'Mailaustausch (MX)' }).click();
|
||||
await window.getByRole('textbox', { name: 'Maildomain:' }).fill('schule.de');
|
||||
await window.getByRole('textbox', { name: 'Domainname Mailserver:' }).fill('mail.schule.de');
|
||||
await window.getByRole('button', { name: 'Hinzufügen' }).click();
|
||||
await expect(rows.last().locator('input').nth(1)).toHaveValue('mail.schule.de.');
|
||||
await window.getByRole('checkbox', { name: 'Aktiviere rekursive Domain-Auflösung' }).click();
|
||||
await expect(window.getByRole('checkbox', { name: 'Aktiviere rekursive Domain-Auflösung' })).toBeChecked();
|
||||
await window.getByRole('checkbox', { name: 'Aktiviere rekursive Domain-Auflösung' }).click();
|
||||
await expect(window.getByRole('checkbox', { name: 'Aktiviere rekursive Domain-Auflösung' })).not.toBeChecked();
|
||||
await window.getByRole('button', { name: 'Beenden' }).click();
|
||||
await expect(window.getByRole('button', { name: 'Starten' })).toBeVisible();
|
||||
});
|
||||
@@ -14,7 +14,8 @@
|
||||
// leaves it; so far the command line (F4.6b), the web browser and the web
|
||||
// server (F4.6d), the e-mail program and server (F4.6e), the text editor
|
||||
// (F4.6f2), the file explorer and the image viewer (F4.6f3), the echo
|
||||
// server and the generic client (F4.6g1) have one, the others follow. The browser's window takes its title from its page, the
|
||||
// server and the generic client (F4.6g1), the DNS server (F4.6g2) have one,
|
||||
// the others follow. The browser's window takes its title from its page, the
|
||||
// text editor's from its open file, the image viewer's from its picture, as
|
||||
// Java's do.
|
||||
|
||||
@@ -39,6 +40,7 @@ import { TerminalPanel } from './TerminalPanel.js';
|
||||
import { TextEditorPanel, useTextEditorWindow } from './TextEditorPanel.js';
|
||||
import { FileExplorerPanel } from './FileExplorerPanel.js';
|
||||
import { EchoServerPanel, SimpleClientPanel } from './EchoPanels.js';
|
||||
import { DnsServerPanel } from './DnsServerPanel.js';
|
||||
import { ImageViewerPanel, useImageViewer } from './ImageViewerPanel.js';
|
||||
import { WebServerPanel } from './WebServerPanel.js';
|
||||
import './desktop.css';
|
||||
@@ -69,7 +71,7 @@ export interface DesktopWindowProps {
|
||||
export type DesktopWindowName = DesktopProgram | 'installation';
|
||||
|
||||
/** The programs whose windows are built so far. */
|
||||
const WITH_WINDOW: ReadonlySet<DesktopProgram> = new Set(['commandPrompt', 'webBrowser', 'webServer', 'emailClient', 'emailServer', 'textEditor', 'fileExplorer', 'imageViewer', 'echoServer', 'simpleClient']);
|
||||
const WITH_WINDOW: ReadonlySet<DesktopProgram> = new Set(['commandPrompt', 'webBrowser', 'webServer', 'emailClient', 'emailServer', 'textEditor', 'fileExplorer', 'imageViewer', 'echoServer', 'simpleClient', 'dnsServer']);
|
||||
|
||||
/** The document's desktop of a computer, read anew after every change (a program installed, the label changed). */
|
||||
function useDesktopView(doc: Y.Doc, id: string) {
|
||||
@@ -186,6 +188,7 @@ export function DesktopWindow({ doc, id, simulation, active, x, y, z, program: o
|
||||
{shown === 'imageViewer' ? <ImageViewerPanel doc={doc} node={id} /> : null}
|
||||
{shown === 'echoServer' ? <EchoServerPanel node={id} simulation={simulation} /> : null}
|
||||
{shown === 'simpleClient' ? <SimpleClientPanel node={id} simulation={simulation} /> : null}
|
||||
{shown === 'dnsServer' ? <DnsServerPanel node={id} simulation={simulation} /> : null}
|
||||
</ProgramFrame>
|
||||
)}
|
||||
{info ? (
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
// A computer's DNS server, after Java's GUIApplicationDNSServerWindow
|
||||
// (measured with tools/java-oracle's DnsWindowRun): the start button
|
||||
// ("Starten"/"Beenden") and the box for recursive resolution above, three
|
||||
// tabs — addresses (A), mail exchange (MX), name servers (NS) — each with
|
||||
// its two fields (the address starts as the computer's own, as Java's), red
|
||||
// while Java's patterns do not take them, "Hinzufügen" (Java's message when
|
||||
// they do not: the valid example), "Auswahl entfernen" and the table, whose
|
||||
// cells are written when they are left. The server is the room's: the panel
|
||||
// shows its view and hands over what is done; what the server does better
|
||||
// than Java is in its core (apps/dnsServer.ts).
|
||||
//
|
||||
// Better than Java: a cell that its check does not take is marked and goes
|
||||
// back when it is left (Java's goes back without a word).
|
||||
|
||||
import { useEffect, useState, type KeyboardEvent } from 'react';
|
||||
import { validDnsRecord, type DnsRecordType } from '@mdg-filius/core';
|
||||
import type { DnsServerView } from '@mdg-filius/protocol';
|
||||
import { useTranslate } from '../i18n/i18n.js';
|
||||
import type { Simulation } from '../simulation/useSimulation.js';
|
||||
|
||||
/** Each tab's words: its title, its fields' labels, its columns, Java's message for what it does not take. */
|
||||
const TABS: Readonly<Record<DnsRecordType, { readonly tab: string; readonly fields: readonly [string, string]; readonly columns: readonly [string, string]; readonly invalid: string }>> = {
|
||||
A: { tab: 'dnsServer.tabA', fields: ['dnsServer.aName', 'dnsServer.aAddress'], columns: ['dnsServer.aNameColumn', 'dnsServer.aAddressColumn'], invalid: 'dnsServer.invalidA' },
|
||||
MX: { tab: 'dnsServer.tabMX', fields: ['dnsServer.mxDomain', 'dnsServer.mxServer'], columns: ['dnsServer.mxDomainColumn', 'dnsServer.mxServerColumn'], invalid: 'dnsServer.invalidMX' },
|
||||
NS: { tab: 'dnsServer.tabNS', fields: ['dnsServer.nsDomain', 'dnsServer.nsServer'], columns: ['dnsServer.nsDomainColumn', 'dnsServer.nsServerColumn'], invalid: 'dnsServer.invalidNS' },
|
||||
};
|
||||
|
||||
/** Whether a field's text is what the tab takes there (its other part taken as valid). */
|
||||
function fieldValid(type: DnsRecordType, column: 0 | 1, text: string): boolean {
|
||||
if (text === '') return true;
|
||||
return column === 0 ? validDnsRecord(type, text, type === 'A' ? '1.1.1.1' : 'a.de') : validDnsRecord(type, 'a.de', text);
|
||||
}
|
||||
|
||||
/** A cell of the table, written when it is left or Enter is pressed and it changed; one its check does not take goes back. */
|
||||
function RecordCell({
|
||||
type,
|
||||
column,
|
||||
value,
|
||||
label,
|
||||
onCommit,
|
||||
}: {
|
||||
readonly type: DnsRecordType;
|
||||
readonly column: 0 | 1;
|
||||
readonly value: string;
|
||||
readonly label: string;
|
||||
readonly onCommit: (value: string) => void;
|
||||
}) {
|
||||
const [draft, setDraft] = useState(value);
|
||||
const [editing, setEditing] = useState(false);
|
||||
// The server's value, but not over what is being typed since the last Enter, Escape or leaving (it may come just after a cell written).
|
||||
useEffect(() => {
|
||||
if (!editing) setDraft(value);
|
||||
}, [value, editing]);
|
||||
const valid = draft !== '' && fieldValid(type, column, draft.trim());
|
||||
const commit = () => {
|
||||
setEditing(false);
|
||||
if (draft !== value && valid) onCommit(draft);
|
||||
else setDraft(value);
|
||||
};
|
||||
const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') commit();
|
||||
if (event.key === 'Escape') {
|
||||
setEditing(false);
|
||||
setDraft(value);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<input
|
||||
value={draft}
|
||||
aria-label={label}
|
||||
aria-invalid={valid ? undefined : 'true'}
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
spellCheck={false}
|
||||
onChange={(event) => {
|
||||
setDraft(event.target.value);
|
||||
setEditing(true);
|
||||
}}
|
||||
onBlur={commit}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function DnsServerPanel({ node, simulation }: { readonly node: string; readonly simulation: Simulation }) {
|
||||
const t = useTranslate();
|
||||
const topic = `app:${node}/dnsServer` as const;
|
||||
const { watch, act } = simulation;
|
||||
useEffect(() => watch([topic, `node:${node}`]), [watch, topic, node]);
|
||||
const view = simulation.views.get(topic) as DnsServerView | null | undefined;
|
||||
const ownAddress = simulation.nodes.get(node)?.ipAddress ?? '';
|
||||
const [tab, setTab] = useState<DnsRecordType>('A');
|
||||
// The address field starts as the computer's own (Java's window takes it when it opens): null until it is typed into or emptied.
|
||||
const [fields, setFields] = useState<Record<DnsRecordType, readonly [string, string | null]>>({ A: ['', null], MX: ['', ''], NS: ['', ''] });
|
||||
const [chosen, setChosen] = useState<number | null>(null);
|
||||
const [notice, setNotice] = useState<string | null>(null);
|
||||
if (view === null || view === undefined) return <div className="web-server" />;
|
||||
const words = TABS[tab];
|
||||
const shownFields = (type: DnsRecordType): readonly [string, string] => [fields[type][0], fields[type][1] ?? ownAddress];
|
||||
const [first, second] = shownFields(tab);
|
||||
const rows = view.rows[tab];
|
||||
|
||||
const add = () => {
|
||||
if (!validDnsRecord(tab, first.trim(), second.trim())) {
|
||||
setNotice(t(words.invalid));
|
||||
return;
|
||||
}
|
||||
act(node, 'dnsServer', 'addRecord', { type: tab, name: first.trim(), data: second.trim() });
|
||||
setFields({ ...fields, [tab]: ['', ''] });
|
||||
setNotice(null);
|
||||
};
|
||||
const remove = () => {
|
||||
if (chosen === null || chosen >= rows.length) return;
|
||||
act(node, 'dnsServer', 'removeRecord', { type: tab, row: chosen });
|
||||
setChosen(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="web-server dns-server">
|
||||
<div className="web-server-bar">
|
||||
<button type="button" onClick={() => act(node, 'dnsServer', 'setActive', { active: !view.active })}>
|
||||
{t(view.active ? 'dnsServer.stop' : 'dnsServer.start')}
|
||||
</button>
|
||||
<label>
|
||||
<input type="checkbox" checked={view.recursive} onChange={(event) => act(node, 'dnsServer', 'setRecursive', { enabled: event.target.checked })} />
|
||||
{t('dnsServer.recursive')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="mail-tabs" role="tablist">
|
||||
{(['A', 'MX', 'NS'] as const).map((each) => (
|
||||
<button
|
||||
key={each}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={tab === each}
|
||||
onClick={() => {
|
||||
setTab(each);
|
||||
setChosen(null);
|
||||
setNotice(null);
|
||||
}}
|
||||
>
|
||||
{t(TABS[each].tab)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="dns-server-page" role="tabpanel" aria-label={t(words.tab)}>
|
||||
{([0, 1] as const).map((column) => (
|
||||
<label key={column} className="simple-client-field">
|
||||
<span>{t(words.fields[column])}</span>
|
||||
<input
|
||||
value={shownFields(tab)[column]}
|
||||
aria-invalid={fieldValid(tab, column, shownFields(tab)[column].trim()) ? undefined : 'true'}
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
spellCheck={false}
|
||||
onChange={(event) => {
|
||||
const next: [string, string] = [...shownFields(tab)];
|
||||
next[column] = event.target.value;
|
||||
setFields({ ...fields, [tab]: next });
|
||||
setNotice(null);
|
||||
}}
|
||||
onKeyDown={(event) => event.key === 'Enter' && add()}
|
||||
/>
|
||||
</label>
|
||||
))}
|
||||
<div className="web-server-bar">
|
||||
<button type="button" onClick={add}>
|
||||
{t('dnsServer.add')}
|
||||
</button>
|
||||
<button type="button" disabled={chosen === null || chosen >= rows.length} onClick={remove}>
|
||||
{t('dnsServer.remove')}
|
||||
</button>
|
||||
</div>
|
||||
{notice === null ? null : (
|
||||
<p className="mail-notice dns-server-notice" role="alert">
|
||||
{notice}
|
||||
</p>
|
||||
)}
|
||||
<table className="dns-server-table" aria-label={t('dnsServer.tables')}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t(words.columns[0])}</th>
|
||||
<th>{t(words.columns[1])}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map(([name, data], row) => (
|
||||
<tr key={row} aria-selected={row === chosen} onPointerDown={() => setChosen(row)}>
|
||||
{([name, data] as const).map((value, column) => (
|
||||
<td key={column}>
|
||||
<RecordCell
|
||||
type={tab}
|
||||
column={column as 0 | 1}
|
||||
value={value}
|
||||
label={`${t(words.columns[column]!)} ${t('dnsServer.row', { row: row + 1 })}`}
|
||||
onCommit={(written) => act(node, 'dnsServer', 'changeRecord', { type: tab, row, column, value: written })}
|
||||
/>
|
||||
</td>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -17,15 +17,37 @@ const NO_LINES: readonly LogLine<ServerLogEntry>[] = [];
|
||||
/** A cell of the table, written to the server when it is left or Enter is pressed and it changed. */
|
||||
function HostCell({ value, label, onCommit }: { readonly value: string; readonly label: string; readonly onCommit: (value: string) => void }) {
|
||||
const [draft, setDraft] = useState(value);
|
||||
useEffect(() => setDraft(value), [value]);
|
||||
const [editing, setEditing] = useState(false);
|
||||
// The server's value, but not over what is being typed since the last Enter, Escape or leaving (it may come just after a cell written).
|
||||
useEffect(() => {
|
||||
if (!editing) setDraft(value);
|
||||
}, [value, editing]);
|
||||
const commit = () => {
|
||||
setEditing(false);
|
||||
if (draft !== value) onCommit(draft);
|
||||
};
|
||||
const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') commit();
|
||||
if (event.key === 'Escape') setDraft(value);
|
||||
if (event.key === 'Escape') {
|
||||
setEditing(false);
|
||||
setDraft(value);
|
||||
}
|
||||
};
|
||||
return <input value={draft} aria-label={label} autoCapitalize="off" autoCorrect="off" spellCheck={false} onChange={(event) => setDraft(event.target.value)} onBlur={commit} onKeyDown={onKeyDown} />;
|
||||
return (
|
||||
<input
|
||||
value={draft}
|
||||
aria-label={label}
|
||||
autoCapitalize="off"
|
||||
autoCorrect="off"
|
||||
spellCheck={false}
|
||||
onChange={(event) => {
|
||||
setDraft(event.target.value);
|
||||
setEditing(true);
|
||||
}}
|
||||
onBlur={commit}
|
||||
onKeyDown={onKeyDown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function WebServerPanel({ node, simulation }: { readonly node: string; readonly simulation: Simulation }) {
|
||||
|
||||
@@ -1083,3 +1083,60 @@
|
||||
padding: 4px 6px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
/* The DNS server (GUIApplicationDNSServerWindow): button and box, three tabs
|
||||
* of two fields, two buttons and a table whose cells are written in place. */
|
||||
.dns-server-page {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.dns-server input:not([type='checkbox']) {
|
||||
min-height: 32px;
|
||||
padding: 0 6px;
|
||||
border: 1px solid #a0a0a0;
|
||||
background: #ffffff;
|
||||
color: #000000;
|
||||
font: 16px Dialog, sans-serif;
|
||||
}
|
||||
|
||||
.dns-server [aria-invalid='true'] {
|
||||
border-color: #ff0000;
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.dns-server-notice {
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.dns-server-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.dns-server-table th {
|
||||
padding: 4px 6px;
|
||||
border-bottom: 1px solid #a0a0a0;
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.dns-server-table td {
|
||||
padding: 2px;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
}
|
||||
|
||||
.dns-server-table td input {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.dns-server-table tr[aria-selected='true'] td,
|
||||
.dns-server-table tr[aria-selected='true'] input {
|
||||
background: #b8cfe5;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,31 @@
|
||||
"dhcp.static": "Statische Adresszuweisung",
|
||||
"dhcp.subnetMask": "Netzmaske",
|
||||
"dhcp.upperBound": "Adress-Obergrenze",
|
||||
"dnsServer.aAddress": "IP-Adresse:",
|
||||
"dnsServer.aAddressColumn": "IP-Adresse",
|
||||
"dnsServer.aName": "Host-/Domainname:",
|
||||
"dnsServer.aNameColumn": "Host-/Domainname",
|
||||
"dnsServer.add": "Hinzufügen",
|
||||
"dnsServer.invalidA": "Eingaben sind ungültig! Valides Beispiel: \n> IP-Adresse: 192.168.0.10\n> Domainname: www.filius.de",
|
||||
"dnsServer.invalidMX": "Eingaben sind ungültig! Valides Beispiel: \n> Maildomain: filius.de\n> Mailserver: mail.filius.de",
|
||||
"dnsServer.invalidNS": "Eingaben sind ungültig! Valides Beispiel: \n> Domainname: filius.de\n> Nameserver: dns.filius.de",
|
||||
"dnsServer.mxDomain": "Maildomain:",
|
||||
"dnsServer.mxDomainColumn": "Maildomain",
|
||||
"dnsServer.mxServer": "Domainname Mailserver:",
|
||||
"dnsServer.mxServerColumn": "Domainname Mailserver",
|
||||
"dnsServer.nsDomain": "Domain:",
|
||||
"dnsServer.nsDomainColumn": "Domain",
|
||||
"dnsServer.nsServer": "Nameserver:",
|
||||
"dnsServer.nsServerColumn": "Nameserver",
|
||||
"dnsServer.recursive": "Aktiviere rekursive Domain-Auflösung",
|
||||
"dnsServer.remove": "Auswahl entfernen",
|
||||
"dnsServer.row": "Zeile {row}",
|
||||
"dnsServer.start": "Starten",
|
||||
"dnsServer.stop": "Beenden",
|
||||
"dnsServer.tabA": "Adressen (A)",
|
||||
"dnsServer.tabMX": "Mailaustausch (MX)",
|
||||
"dnsServer.tabNS": "Nameserver (NS)",
|
||||
"dnsServer.tables": "Einträge",
|
||||
"echoServer.log": "Ausgabe",
|
||||
"echoServer.port": "Port:",
|
||||
"echoServer.start": "Starten",
|
||||
|
||||
@@ -59,6 +59,31 @@
|
||||
"dhcp.static": "Static Address Assignment",
|
||||
"dhcp.subnetMask": "Netmask",
|
||||
"dhcp.upperBound": "Upper bound of address",
|
||||
"dnsServer.aAddress": "IP address:",
|
||||
"dnsServer.aAddressColumn": "IP address",
|
||||
"dnsServer.aName": "Host/domain name:",
|
||||
"dnsServer.aNameColumn": "Host/domain name",
|
||||
"dnsServer.add": "Add",
|
||||
"dnsServer.invalidA": "Input invalid! Valid example: \n> IP address: 192.168.0.10\n> Domain name: www.filius.de",
|
||||
"dnsServer.invalidMX": "Input invalid! Valid example: \n> Mail domain: filius.de\n> Mail server: mail.filius.de!",
|
||||
"dnsServer.invalidNS": "Input invalid! Valid example: \n> Domain name: filius.de\n> Name server: dns.filius.de",
|
||||
"dnsServer.mxDomain": "Mail domain:",
|
||||
"dnsServer.mxDomainColumn": "Mail domain",
|
||||
"dnsServer.mxServer": "Mail server domain name:",
|
||||
"dnsServer.mxServerColumn": "Mail server domain name",
|
||||
"dnsServer.nsDomain": "Domain:",
|
||||
"dnsServer.nsDomainColumn": "Domain",
|
||||
"dnsServer.nsServer": "Name server:",
|
||||
"dnsServer.nsServerColumn": "Name server",
|
||||
"dnsServer.recursive": "Enable recursive domain resolution",
|
||||
"dnsServer.remove": "Remove entry",
|
||||
"dnsServer.row": "Row {row}",
|
||||
"dnsServer.start": "Start",
|
||||
"dnsServer.stop": "Stop",
|
||||
"dnsServer.tabA": "Address (A)",
|
||||
"dnsServer.tabMX": "Mail exchange (MX)",
|
||||
"dnsServer.tabNS": "Name server (NS)",
|
||||
"dnsServer.tables": "Records",
|
||||
"echoServer.log": "Output",
|
||||
"echoServer.port": "Port:",
|
||||
"echoServer.start": "Start",
|
||||
|
||||
@@ -61,6 +61,26 @@ const FROM_JAVA: Readonly<Record<string, string>> = {
|
||||
'simpleClient.failure.unresolved': 'sw_socket_msg1',
|
||||
'simpleClient.failure.timedOut': 'sw_tcpsocket_msg2',
|
||||
'simpleClient.failure.failed': 'sw_tcpsocket_msg3',
|
||||
'dnsServer.start': 'dnsserver_msg1',
|
||||
'dnsServer.stop': 'dnsserver_msg14',
|
||||
'dnsServer.tabA': 'dnsserver_msg2',
|
||||
'dnsServer.tabMX': 'dnsserver_msg3',
|
||||
'dnsServer.tabNS': 'dnsserver_msg15',
|
||||
'dnsServer.aName': 'dnsserver_msg4',
|
||||
'dnsServer.aAddress': 'dnsserver_msg5',
|
||||
'dnsServer.add': 'dnsserver_msg6',
|
||||
'dnsServer.remove': 'dnsserver_msg7',
|
||||
'dnsServer.aNameColumn': 'dnsserver_msg8',
|
||||
'dnsServer.aAddressColumn': 'dnsserver_msg9',
|
||||
'dnsServer.mxDomain': 'dnsserver_msg10',
|
||||
'dnsServer.mxServer': 'dnsserver_msg11',
|
||||
'dnsServer.mxDomainColumn': 'dnsserver_msg12',
|
||||
'dnsServer.mxServerColumn': 'dnsserver_msg13',
|
||||
'dnsServer.nsDomain': 'dnsserver_msg16',
|
||||
'dnsServer.nsServer': 'dnsserver_msg17',
|
||||
'dnsServer.nsDomainColumn': 'dnsserver_msg18',
|
||||
'dnsServer.nsServerColumn': 'dnsserver_msg19',
|
||||
'dnsServer.recursive': 'dnsserver_msg20',
|
||||
'textEditor.new': 'texteditor_msg3',
|
||||
'textEditor.open': 'texteditor_msg4',
|
||||
'textEditor.save': 'texteditor_msg5',
|
||||
@@ -253,6 +273,10 @@ const FROM_JAVA_MENDED: Readonly<Record<string, readonly [string, (java: string)
|
||||
'router.prompt': ['guicontainer_msg1', (java) => java.replace(' \\n ', ' ').replace('Vermittlungsrechnerns', 'Vermittlungsrechners')],
|
||||
// A label above its list, without the colon of Java's label beside it.
|
||||
'sheet.network': ['jhostkonfiguration_msg15', (java) => java.replace(/:$/, '')],
|
||||
// Java's message dialog breaks its lines with \n.
|
||||
'dnsServer.invalidA': ['dnsserver_msg21', (java) => java.replaceAll('\\n', '\n')],
|
||||
'dnsServer.invalidNS': ['dnsserver_msg22', (java) => java.replaceAll('\\n', '\n')],
|
||||
'dnsServer.invalidMX': ['dnsserver_msg23', (java) => java.replaceAll('\\n', '\n')],
|
||||
// Java writes "Eintrag Löschen".
|
||||
'sheet.deleteEntry': ['jvermittlungsrechnerkonfiguration_msg13', (java) => java.replace('Löschen', 'löschen')],
|
||||
// Java's word, then what it joins or which card.
|
||||
|
||||
Reference in New Issue
Block a user