export * from './avatar/Avatar';
export * from './avatar/GenericAvatar';
export * from './buttons';
-export { ClipboardIconButton } from './clipboard';
+export { ClipboardButton, ClipboardIconButton } from './clipboard';
export * from './code-line/LineCoverage';
export * from './code-line/LineFinding';
export * from './code-line/LineIssuesIndicatorIcon';
'/project/background_tasks',
'/admin/background_tasks',
'/admin/groups',
+ '/admin/system',
'/admin/users',
'/admin/settings/encryption',
'/admin/extension/license/support',
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Variant } from 'design-system';
import { groupBy, isEmpty, mapValues } from 'lodash';
import * as React from 'react';
import { getSystemUpgrades } from '../../../api/system';
-import { Alert, AlertVariant } from '../../../components/ui/Alert';
+import { Alert } from '../../../components/ui/Alert';
import DismissableAlert from '../../../components/ui/DismissableAlert';
import SystemUpgradeButton from '../../../components/upgrade/SystemUpgradeButton';
import { UpdateUseCase, sortUpgrades } from '../../../components/upgrade/utils';
[x: string]: Dict<SystemUpgrade[]>;
};
-const MAP_VARIANT: Dict<AlertVariant> = {
+const MAP_VARIANT: Dict<Variant> = {
[UpdateUseCase.NewMinorVersion]: 'info',
[UpdateUseCase.NewPatch]: 'warning',
[UpdateUseCase.PreLTS]: 'warning',
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ButtonPrimary, FlagMessage, Modal, RadioButton } from 'design-system';
import * as React from 'react';
import { setLogLevel } from '../../../api/system';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
-import Modal from '../../../components/controls/Modal';
-import { Alert } from '../../../components/ui/Alert';
import { translate } from '../../../helpers/l10n';
import { LOGS_LEVELS } from '../utils';
updating: boolean;
}
+const FORM_ID = 'set-log-level-form';
export default class ChangeLogLevelForm extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
}
};
- handleLevelChange = (event: React.ChangeEvent<HTMLInputElement>) =>
- this.setState({ newLevel: event.currentTarget.value });
+ handleLevelChange = (value: string) => this.setState({ newLevel: value });
render() {
const { updating, newLevel } = this.state;
const header = translate('system.set_log_level');
return (
- <Modal contentLabel={header} onRequestClose={this.props.onClose}>
- <form id="set-log-level-form" onSubmit={this.handleFormSubmit}>
- <div className="modal-head">
- <h2>{header}</h2>
- </div>
- <div className="modal-body">
+ <Modal
+ headerTitle={header}
+ onClose={this.props.onClose}
+ body={
+ <form id={FORM_ID} onSubmit={this.handleFormSubmit}>
{LOGS_LEVELS.map((level) => (
- <p className="spacer-bottom" key={level}>
- <input
- checked={level === newLevel}
- className="spacer-right text-middle"
- id={`loglevel-${level}`}
- name="system.log_levels"
- onChange={this.handleLevelChange}
- type="radio"
- value={level}
- />
+ <RadioButton
+ key={level}
+ checked={level === newLevel}
+ className="sw-mb-2"
+ id={`loglevel-${level}`}
+ name="system.log_levels"
+ onCheck={this.handleLevelChange}
+ value={level}
+ >
<label className="text-middle" htmlFor={`loglevel-${level}`}>
{level}
</label>
- </p>
+ </RadioButton>
))}
- <Alert className="spacer-top" variant="info">
+ <FlagMessage className="sw-mt-2" variant="info">
{this.props.infoMsg}
- </Alert>
+ </FlagMessage>
{newLevel !== 'INFO' && (
- <Alert className="spacer-top" variant="warning">
+ <FlagMessage className="sw-mt-2" variant="warning">
{translate('system.log_level.warning')}
- </Alert>
+ </FlagMessage>
)}
- </div>
- <div className="modal-foot">
- {updating && <i className="spinner spacer-right" />}
- <SubmitButton disabled={updating} id="set-log-level-submit">
- {translate('save')}
- </SubmitButton>
- <ResetButtonLink id="set-log-level-cancel" onClick={this.props.onClose}>
- {translate('cancel')}
- </ResetButtonLink>
- </div>
- </form>
- </Modal>
+ </form>
+ }
+ primaryButton={
+ <ButtonPrimary disabled={updating} id="set-log-level-submit" type="submit" form={FORM_ID}>
+ {translate('save')}
+ </ButtonPrimary>
+ }
+ secondaryButtonLabel={translate('cancel')}
+ loading={updating}
+ />
);
}
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Note, UnorderedList } from 'design-system';
import { sortBy } from 'lodash';
import * as React from 'react';
import { translate } from '../../../helpers/l10n';
toggleCard: (toggledCard: string) => void;
}
-export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard }: Props) {
+export default function ClusterSysInfos({
+ expandedCards,
+ sysInfoData,
+ toggleCard,
+}: Readonly<Props>) {
const mainCardName = 'System';
return (
- <ul>
+ <UnorderedList className="sw-flex sw-flex-col sw-gap-4">
<HealthCard
- biggerHealth
health={getHealth(sysInfoData)}
healthCauses={getHealthCauses(sysInfoData)}
name={mainCardName}
open={expandedCards.includes(mainCardName)}
sysInfoData={ignoreInfoFields(getClusterMainCardSection(sysInfoData))}
/>
- <li className="note system-info-health-title">
- {translate('system.application_nodes_title')}
+ <li>
+ <Note>{translate('system.application_nodes_title')}</Note>
</li>
{sortBy(getAppNodes(sysInfoData), getNodeName).map((node: SysInfoAppNode) => (
<HealthCard
sysInfoData={ignoreInfoFields(node)}
/>
))}
- <li className="note system-info-health-title">{translate('system.search_nodes_title')}</li>
+ <li>
+ <Note>{translate('system.search_nodes_title')}</Note>
+ </li>
{sortBy(getSearchNodes(sysInfoData), getNodeName).map((node: SysInfoSearchNode) => (
<HealthCard
key={getNodeName(node)}
sysInfoData={ignoreInfoFields(node)}
/>
))}
- </ul>
+ </UnorderedList>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import {
+ ButtonPrimary,
+ ChevronDownIcon,
+ DownloadButton,
+ Dropdown,
+ InteractiveIcon,
+ ItemDownload,
+ PencilIcon,
+} from 'design-system';
import * as React from 'react';
-import { Button, EditButton } from '../../../components/controls/buttons';
-import Dropdown from '../../../components/controls/Dropdown';
-import DropdownIcon from '../../../components/icons/DropdownIcon';
import { translate } from '../../../helpers/l10n';
import { getBaseUrl } from '../../../helpers/system';
import { getFileNameSuffix } from '../utils';
const infoUrl = getBaseUrl() + '/api/system/info';
const logsUrl = getBaseUrl() + '/api/system/logs';
return (
- <div className="page-actions">
- <span>
- <span className="text-middle">
+ <div className="sw-flex sw-items-center sw-gap-2">
+ <div className="sw-flex sw-items-center">
+ <span>
{translate('system.logs_level')}
- {':'}
- <strong className="little-spacer-left">{this.props.logLevel}</strong>
+ {': '}
+ <strong>{this.props.logLevel}</strong>
</span>
- <EditButton
- className="spacer-left button-small"
+ <InteractiveIcon
+ className="sw-ml-1"
+ Icon={PencilIcon}
id="edit-logs-level-button"
onClick={this.handleLogsLevelOpen}
aria-label={translate('system.logs_level.change')}
/>
- </span>
+ </div>
{this.props.canDownloadLogs && (
<Dropdown
- className="display-inline-block spacer-left"
+ id="system-logs-download"
overlay={
- <ul className="menu">
- <li>
- <a
- download="sonarqube_app.log"
- href={logsUrl + '?name=app'}
- id="logs-link"
- rel="noopener noreferrer"
- target="_blank"
- >
- Main Process
- </a>
- </li>
- <li>
- <a
- download="sonarqube_ce.log"
- href={logsUrl + '?name=ce'}
- id="ce-logs-link"
- rel="noopener noreferrer"
- target="_blank"
- >
- Compute Engine
- </a>
- </li>
- <li>
- <a
- download="sonarqube_es.log"
- href={logsUrl + '?name=es'}
- id="es-logs-link"
- rel="noopener noreferrer"
- target="_blank"
- >
- Search Engine
- </a>
- </li>
- <li>
- <a
- download="sonarqube_web.log"
- href={logsUrl + '?name=web'}
- id="web-logs-link"
- rel="noopener noreferrer"
- target="_blank"
- >
- Web Server
- </a>
- </li>
- <li>
- <a
- download="sonarqube_access.log"
- href={logsUrl + '?name=access'}
- id="access-logs-link"
- rel="noopener noreferrer"
- target="_blank"
- >
- Access Logs
- </a>
- </li>
- <li>
- <a
- download="sonarqube_deprecation.log"
- href={logsUrl + '?name=deprecation'}
- rel="noopener noreferrer"
- target="_blank"
- >
- Deprecation Logs
- </a>
- </li>
- </ul>
+ <>
+ <ItemDownload download="sonarqube_app.log" href={logsUrl + '?name=app'}>
+ Main Process
+ </ItemDownload>
+ <ItemDownload download="sonarqube_ce.log" href={logsUrl + '?name=ce'}>
+ Compute Engine
+ </ItemDownload>
+ <ItemDownload download="sonarqube_es.log" href={logsUrl + '?name=es'}>
+ Search Engine
+ </ItemDownload>
+
+ <ItemDownload download="sonarqube_web.log" href={logsUrl + '?name=web'}>
+ Web Server
+ </ItemDownload>
+
+ <ItemDownload download="sonarqube_access.log" href={logsUrl + '?name=access'}>
+ Access Logs
+ </ItemDownload>
+
+ <ItemDownload
+ download="sonarqube_deprecation.log"
+ href={logsUrl + '?name=deprecation'}
+ >
+ Deprecation Logs
+ </ItemDownload>
+ </>
}
>
- <Button>
+ <ButtonPrimary>
{translate('system.download_logs')}
- <DropdownIcon className="little-spacer-left" />
- </Button>
+ <ChevronDownIcon className="sw-ml-1" />
+ </ButtonPrimary>
</Dropdown>
)}
- <a
- className="button spacer-left"
+ <DownloadButton
download={`sonarqube-system-info-${getFileNameSuffix(this.props.serverId)}.json`}
href={infoUrl}
id="download-link"
target="_blank"
>
{translate('system.download_system_info')}
- </a>
+ </DownloadButton>
{this.state.openLogsLevelForm && (
<ChangeLogLevelForm
infoMsg={translate(
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Card, ClipboardButton, FlagMessage, Spinner, Title } from 'design-system';
import * as React from 'react';
import withAppStateContext from '../../../app/components/app-state/withAppStateContext';
-import { ClipboardButton } from '../../../components/controls/clipboard';
-import { Alert } from '../../../components/ui/Alert';
import { toShortISO8601String } from '../../../helpers/dates';
import { translate } from '../../../helpers/l10n';
import { AppState } from '../../../types/appstate';
version?: string;
}
-function PageHeader(props: Props) {
+function PageHeader(props: Readonly<Props>) {
const { isCluster, loading, logLevel, serverId, version, appState } = props;
return (
- <header className="page-header">
- <h1 className="page-title">{translate('system_info.page')}</h1>
- <PageActions
- canDownloadLogs={!isCluster}
- cluster={isCluster}
- logLevel={logLevel}
- onLogLevelChange={props.onLogLevelChange}
- serverId={serverId}
- />
- {loading && (
- <div className="page-actions">
- <i className="spinner" />
+ <header className="sw-mt-8">
+ <div className="sw-flex sw-items-start sw-justify-between">
+ <Title>{translate('system_info.page')}</Title>
+
+ <div className="sw-flex sw-items-center">
+ <Spinner className="sw-mr-4 sw-mt-1" loading={loading} />
+
+ <PageActions
+ canDownloadLogs={!isCluster}
+ cluster={isCluster}
+ logLevel={logLevel}
+ onLogLevelChange={props.onLogLevelChange}
+ serverId={serverId}
+ />
</div>
- )}
+ </div>
+
{serverId && version && (
- <div className="system-info-copy-paste-id-info boxed-group ">
+ <Card className="sw-max-w-1/2 sw-mb-8">
{!appState.productionDatabase && (
- <Alert className="width-100" variant="warning">
+ <FlagMessage className="sw-mb-2" variant="warning">
{translate('system.not_production_database_warning')}
- </Alert>
+ </FlagMessage>
)}
- <div className="display-flex-center">
- <div className="flex-1">
- <table className="width-100">
- <tbody>
- <tr>
- <th>
- <strong>{translate('system.server_id')}</strong>
- </th>
- <td>
- <code>{serverId}</code>
- </td>
- </tr>
- <tr>
- <th>
- <strong>{translate('system.version')}</strong>
- </th>
- <td>{version}</td>
- </tr>
- </tbody>
- </table>
+ <div className="sw-flex sw-items-center sw-justify-between">
+ <div>
+ <div className="sw-flex sw-items-center">
+ <strong className="sw-w-32">{translate('system.server_id')}</strong>
+ <span className="sw-code">{serverId}</span>
+ </div>
+ <div className="sw-flex sw-items-center">
+ <strong className="sw-w-32">{translate('system.version')}</strong>
+ <span>{version}</span>
+ </div>
</div>
<ClipboardButton
- className="flex-0"
+ className="sw-ml-4"
copyValue={`SonarQube ID information
Server ID: ${serverId}
Version: ${version}
Date: ${toShortISO8601String(Date.now())}
`}
>
- {translate('system.copy_id_info')}
+ <span className="sw-ml-1 sw-whitespace-nowrap">
+ {translate('system.copy_id_info')}
+ </span>
</ClipboardButton>
</div>
- </div>
+ </Card>
)}
</header>
);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { UnorderedList } from 'design-system';
import { map } from 'lodash';
import * as React from 'react';
import { SysInfoStandalone } from '../../../types/types';
toggleCard: (toggledCard: string) => void;
}
-export default function StandAloneSysInfos({ expandedCards, sysInfoData, toggleCard }: Props) {
- const mainCardName = 'System';
+const mainCardName = 'System';
+
+export default function StandAloneSysInfos({
+ expandedCards,
+ sysInfoData,
+ toggleCard,
+}: Readonly<Props>) {
return (
- <ul>
+ <UnorderedList className="sw-flex sw-flex-col sw-gap-4">
<HealthCard
- biggerHealth
health={getHealth(sysInfoData)}
healthCauses={getHealthCauses(sysInfoData)}
name={mainCardName}
sysInfoData={ignoreInfoFields(section)}
/>
))}
- </ul>
+ </UnorderedList>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { LargeCenteredLayout, PageContentFontWrapper } from 'design-system';
import * as React from 'react';
import { Helmet } from 'react-helmet-async';
import { getSystemInfo } from '../../../api/system';
import { SysInfoCluster, SysInfoStandalone } from '../../../types/types';
import '../styles.css';
import {
+ Query,
getClusterVersion,
getServerId,
getSystemLogsLevel,
getVersion,
isCluster,
parseQuery,
- Query,
serializeQuery,
} from '../utils';
import ClusterSysInfos from './ClusterSysInfos';
render() {
const { loading, sysInfoData } = this.state;
return (
- <main className="page page-limited">
+ <LargeCenteredLayout as="main">
<Suggestions suggestions="system_info" />
<Helmet defer={false} title={translate('system_info.page')} />
- <div className="page-notifs">
- <UpdateNotification dismissable={false} />
- </div>
- {sysInfoData && (
- <PageHeader
- isCluster={isCluster(sysInfoData)}
- loading={loading}
- logLevel={getSystemLogsLevel(sysInfoData)}
- onLogLevelChange={this.fetchSysInfo}
- serverId={getServerId(sysInfoData)}
- version={
- isCluster(sysInfoData) ? getClusterVersion(sysInfoData) : getVersion(sysInfoData)
- }
- />
- )}
- {this.renderSysInfo()}
- </main>
+ <PageContentFontWrapper className="sw-body-sm sw-pb-8">
+ <div>
+ <UpdateNotification dismissable={false} />
+ </div>
+ {sysInfoData && (
+ <PageHeader
+ isCluster={isCluster(sysInfoData)}
+ loading={loading}
+ logLevel={getSystemLogsLevel(sysInfoData)}
+ onLogLevelChange={this.fetchSysInfo}
+ serverId={getServerId(sysInfoData)}
+ version={
+ isCluster(sysInfoData) ? getClusterVersion(sysInfoData) : getVersion(sysInfoData)
+ }
+ />
+ )}
+ {this.renderSysInfo()}
+ </PageContentFontWrapper>
+ </LargeCenteredLayout>
);
}
}
'Web Server',
'Access Logs',
'Deprecation Logs',
- ].forEach((link) => {
- expect(screen.getByRole('link', { name: link })).toBeInTheDocument();
+ ].forEach((name) => {
+ expect(screen.getByRole('menuitem', { name })).toBeInTheDocument();
});
expect(ui.downloadSystemInfoButton.get()).toBeInTheDocument();
});
pageHeading: byRole('heading', { name: 'system_info.page' }),
downloadLogsButton: byRole('button', { name: 'system.download_logs' }),
downloadSystemInfoButton: byRole('link', { name: 'system.download_system_info' }),
- copyIdInformation: byRole('button', { name: 'copy_to_clipboard' }),
+ copyIdInformation: byRole('button', { name: 'system.copy_id_info' }),
sectionButton: (name: string) => byRole('button', { name }),
changeLogLevelButton: byRole('button', { name: 'system.logs_level.change' }),
logLevelsRadioButton: (name: LogsLevels) => byRole('radio', { name }),
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Accordion, FlagMessage, SubHeadingHighlight } from 'design-system';
import { map } from 'lodash';
import * as React from 'react';
-import BoxedGroupAccordion from '../../../../components/controls/BoxedGroupAccordion';
-import { Alert } from '../../../../components/ui/Alert';
import { translate } from '../../../../helpers/l10n';
import { HealthTypes, SysInfoValueObject } from '../../../../types/types';
-import { getLogsLevel, groupSections, LogsLevels } from '../../utils';
+import { LogsLevels, getLogsLevel, groupSections } from '../../utils';
import HealthItem from './HealthItem';
import Section from './Section';
interface Props {
- biggerHealth?: boolean;
health?: HealthTypes;
healthCauses?: string[];
onClick: (toggledCard: string) => void;
}
export default function HealthCard({
- biggerHealth,
health,
healthCauses,
onClick,
open,
name,
sysInfoData,
-}: Props) {
+}: Readonly<Props>) {
const { mainSection, sections } = groupSections(sysInfoData);
const showFields = open && mainSection && Object.keys(mainSection).length > 0;
const showSections = open && sections;
const logLevel = getLogsLevel(sysInfoData);
const showLogLevelWarning = logLevel && logLevel !== LogsLevels.INFO;
+
return (
- <BoxedGroupAccordion
+ <Accordion
data={name}
onClick={onClick}
open={open}
- renderHeader={() => (
+ header={
<>
- {showLogLevelWarning && (
- <Alert
- className="boxed-group-accordion-alert spacer-left"
- display="inline"
- variant="warning"
- >
- {translate('system.log_level.warning.short')}
- </Alert>
- )}
- {health && (
- <HealthItem
- biggerHealth={biggerHealth}
- health={health}
- healthCauses={healthCauses}
- name={name}
- />
- )}
+ <div className="sw-flex-1 sw-flex sw-items-center">
+ <SubHeadingHighlight as="h2" className="sw-mb-0">
+ {name}
+ </SubHeadingHighlight>
+ {showLogLevelWarning && (
+ <FlagMessage className="sw-ml-4" variant="warning">
+ {translate('system.log_level.warning.short')}
+ </FlagMessage>
+ )}
+ </div>
+ {health && <HealthItem health={health} healthCauses={healthCauses} name={name} />}
</>
- )}
- title={name}
+ }
+ ariaLabel={name}
>
{showFields && <Section items={mainSection} />}
{showSections &&
map(sections, (section, name) => <Section items={section} key={name} name={name} />)}
- </BoxedGroupAccordion>
+ </Accordion>
);
}
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import classNames from 'classnames';
+import { FlagMessage } from 'design-system';
import * as React from 'react';
-import { Alert } from '../../../../components/ui/Alert';
import { HealthTypes } from '../../../../types/types';
interface Props {
healthCause: string;
}
-export default function HealthCauseItem({ className, health, healthCause }: Props) {
+export default function HealthCauseItem({ className, health, healthCause }: Readonly<Props>) {
return (
- <Alert
- className={classNames('boxed-group-accordion-alert', className)}
- display="inline"
+ <FlagMessage
+ className={classNames('-sw-my-2', className)}
variant={health === HealthTypes.RED ? 'error' : 'warning'}
>
{healthCause}
- </Alert>
+ </FlagMessage>
);
}
import HealthCauseItem from './HealthCauseItem';
interface Props {
- biggerHealth?: boolean;
name?: string;
className?: string;
health: HealthTypes;
healthCauses?: string[];
}
-export default function HealthItem({ biggerHealth, className, name, health, healthCauses }: Props) {
+export default function HealthItem({ className, name, health, healthCauses }: Readonly<Props>) {
const hasHealthCauses = healthCauses && healthCauses.length > 0 && health !== HealthTypes.GREEN;
- const statusIndicator = (
- <StatusIndicator color={health.toLowerCase()} size={biggerHealth ? 'big' : undefined} />
- );
+ const statusIndicator = <StatusIndicator color={health} />;
return (
- <div className={classNames('system-info-health-info', className)}>
+ <div className={classNames('sw-flex sw-items-center', className)}>
{hasHealthCauses &&
- healthCauses.map((cause, idx) => (
- <HealthCauseItem className="spacer-right" health={health} healthCause={cause} key={idx} />
+ healthCauses.map((cause) => (
+ <HealthCauseItem className="sw-mr-2" health={health} healthCause={cause} key={cause} />
))}
- {name ? (
- <Tooltip overlay={translateWithParameters('system.current_health_of_x', name)}>
- <span>{statusIndicator}</span>
- </Tooltip>
- ) : (
- statusIndicator
- )}
+
+ <Tooltip
+ overlay={name ? translateWithParameters('system.current_health_of_x', name) : undefined}
+ >
+ <span>{statusIndicator}</span>
+ </Tooltip>
</div>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ContentCell, SubHeading, Table, TableRow } from 'design-system';
import { map } from 'lodash';
import * as React from 'react';
import { SysInfoValueObject } from '../../../../types/types';
items: SysInfoValueObject;
}
-export default function Section({ name, items }: Props) {
+const COLUMNS = ['0', 'auto'];
+
+export default function Section({ name, items }: Readonly<Props>) {
return (
- <div className="system-info-section">
- {name && <h4 className="spacer-bottom">{name}</h4>}
- <table className="data zebra" id={name}>
- <tbody>
- {map(items, (value, name) => {
- return (
- <tr key={name}>
- <td className="thin">
- <div className="system-info-section-item-name">{name}</div>
- </td>
- <td style={{ wordBreak: 'break-all' }}>
+ <div className="it__system-info-section">
+ {name !== undefined && <SubHeading>{name}</SubHeading>}
+ <Table id={name} columnCount={COLUMNS.length} columnWidths={COLUMNS}>
+ {map(items, (value, name) => {
+ return (
+ <TableRow key={name}>
+ <ContentCell className="it__system-info-section-item-name">{name}</ContentCell>
+ <ContentCell>
+ <span className="sw-break-all">
<SysInfoItem name={name} value={value} />
- </td>
- </tr>
- );
- })}
- </tbody>
- </table>
+ </span>
+ </ContentCell>
+ </TableRow>
+ );
+ })}
+ </Table>
</div>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ContentCell, Table, TableRow } from 'design-system';
import { map } from 'lodash';
import * as React from 'react';
import { translate } from '../../../../helpers/l10n';
value: SysInfoValue;
}
-export default function SysInfoItem({ name, value }: Props) {
+const COLUMNS = [0, 'auto'];
+
+export default function SysInfoItem({ name, value }: Readonly<Props>) {
if (name === HEALTH_FIELD || name === STATE_FIELD) {
- return <HealthItem className="no-margin" health={value as HealthTypes} />;
+ return <HealthItem health={value as HealthTypes} />;
}
if (value instanceof Array) {
- return <code>{JSON.stringify(value)}</code>;
+ return <span className="sw-code">{JSON.stringify(value)}</span>;
}
switch (typeof value) {
case 'boolean':
return <>{translate(value ? 'yes' : 'no')}</>;
case 'object':
return (
- <table className="data">
- <tbody>
- {map(value, (v, n) => (
- <tr key={n}>
- <td className="thin nowrap">{n}</td>
- <td>
- <SysInfoItem name={n} value={v} />
- </td>
- </tr>
- ))}
- </tbody>
- </table>
+ <Table columnCount={COLUMNS.length} columnWidths={COLUMNS}>
+ {map(value, (v, n) => (
+ <TableRow key={n}>
+ <ContentCell className="sw-whitespace-nowrap">{n}</ContentCell>
+ <ContentCell>
+ <SysInfoItem name={n} value={v} />
+ </ContentCell>
+ </TableRow>
+ ))}
+ </Table>
);
default:
- return <code>{value}</code>;
+ return <span className="sw-code">{value}</span>;
}
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
+import { CheckIcon, FlagErrorIcon, FlagWarningIcon } from 'design-system';
import * as React from 'react';
import { translate } from '../../helpers/l10n';
+import { HealthTypes } from '../../types/types';
import './StatusIndicator.css';
export interface StatusIndicatorProps {
- className?: string;
- color?: string;
- size?: string;
+ color: HealthTypes;
}
-export default function StatusIndicator({ className, color, size }: StatusIndicatorProps) {
+const ICON_MAP = {
+ [HealthTypes.GREEN]: CheckIcon,
+ [HealthTypes.YELLOW]: FlagWarningIcon,
+ [HealthTypes.RED]: FlagErrorIcon,
+};
+
+export default function StatusIndicator({ color }: Readonly<StatusIndicatorProps>) {
+ const Icon = ICON_MAP[color];
+
return (
<>
- {color ? translate('system.current_health', color) : undefined}
- <i
- className={classNames(
- 'spacer-left',
- 'status-indicator',
- color,
- {
- 'small-status-indicator': size === 'small',
- 'big-status-indicator': size === 'big',
- },
- className,
- )}
- />
+ {translate('system.current_health', color.toLowerCase())}
+ <Icon aria-hidden className="sw-ml-2" />
</>
);
}
updateUseCase?: UpdateUseCase;
}
-interface State {
- openSystemUpgradeForm: boolean;
-}
+export default function SystemUpgradeButton(props: Readonly<Props>) {
+ const { latestLTS, systemUpgrades, updateUseCase } = props;
-export default class SystemUpgradeButton extends React.PureComponent<Props, State> {
- state: State = { openSystemUpgradeForm: false };
+ const [isSystemUpgradeFormOpen, setSystemUpgradeFormOpen] = React.useState(false);
- handleOpenSystemUpgradeForm = () => {
- this.setState({ openSystemUpgradeForm: true });
- };
+ const openSystemUpgradeForm = React.useCallback(() => {
+ setSystemUpgradeFormOpen(true);
+ }, [setSystemUpgradeFormOpen]);
- handleCloseSystemUpgradeForm = () => {
- this.setState({ openSystemUpgradeForm: false });
- };
+ const closeSystemUpgradeForm = React.useCallback(() => {
+ setSystemUpgradeFormOpen(false);
+ }, [setSystemUpgradeFormOpen]);
- render() {
- const { latestLTS, systemUpgrades, updateUseCase } = this.props;
- const { openSystemUpgradeForm } = this.state;
- return (
- <>
- <Button className="spacer-left" onClick={this.handleOpenSystemUpgradeForm}>
- {translate('learn_more')}
- </Button>
- {openSystemUpgradeForm && (
- <SystemUpgradeForm
- onClose={this.handleCloseSystemUpgradeForm}
- systemUpgrades={groupUpgrades(sortUpgrades(systemUpgrades))}
- latestLTS={latestLTS}
- updateUseCase={updateUseCase}
- />
- )}
- </>
- );
- }
+ return (
+ <>
+ <Button className="sw-ml-2" onClick={openSystemUpgradeForm}>
+ {translate('learn_more')}
+ </Button>
+ {isSystemUpgradeFormOpen && (
+ <SystemUpgradeForm
+ onClose={closeSystemUpgradeForm}
+ systemUpgrades={groupUpgrades(sortUpgrades(systemUpgrades))}
+ latestLTS={latestLTS}
+ updateUseCase={updateUseCase}
+ />
+ )}
+ </>
+ );
}
});
function renderSystemUpgradeButton(
- props: Partial<SystemUpgradeButton['props']> = {},
+ props: Partial<React.ComponentPropsWithoutRef<typeof SystemUpgradeButton>> = {},
version = '9.7',
) {
renderComponent(