snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 00:29:55 +05:00
commit 240db4e2a7
15309 changed files with 2173406 additions and 0 deletions

View File

@@ -0,0 +1,600 @@
import React, {useState, useMemo} from 'react';
import styles from './styles.module.css';
import {
TIERS,
PROVIDERS,
bomFor,
DB_BRAND,
REDIS_BRAND,
SEARCH_BRAND,
STORAGE_BRAND,
LB_BRAND_VM,
LB_BRAND_K8S,
type Tier,
type DeploymentType,
type Provider,
} from './data';
/**
* Interactive deployment-architecture builder.
*
* Pick a scale tier, deployment type, hosting provider, and optional
* services (Calls / Push). Renders an architecture diagram with explicit
* per-connection port labels, plus a bill of materials and a network
* flows table sized to those choices.
*/
/* ============================ Diagram primitives ============================ */
function FlowDown({port}: {port: string}) {
return (
<div className={styles.flow} aria-hidden>
<span className={styles.flowArrow}>↓</span>
<span className={styles.flowPorts}>{port}</span>
</div>
);
}
function MiniFlow({port}: {port: string}) {
return (
<div className={styles.miniFlow} aria-hidden>
<span className={styles.miniArrow}>↓</span> {port}
</div>
);
}
function HFlow({direction, port}: {direction: 'left' | 'right'; port: string}) {
const arrow = direction === 'left' ? '←' : '→';
return (
<div className={styles.hFlow} aria-hidden>
<span className={styles.hArrow}>{arrow}</span>
<span className={styles.hFlowPorts}>{port}</span>
</div>
);
}
function DataItem({
port,
accent,
label,
subtitle,
}: {
port: string;
accent: string;
label: string;
subtitle: string;
}) {
return (
<div className={styles.dataItem}>
<MiniFlow port={port} />
<div className={`${styles.box} ${accent}`}>
<div className={styles.boxLabel}>{label}</div>
<div className={styles.boxSub}>{subtitle}</div>
</div>
</div>
);
}
/* ============================ Architecture diagram ============================
*
* 3-column CSS grid: left side / center / right side.
* - Center column holds: clients, ALB, Mattermost, data tier (so they all
* align vertically — ALB is locked directly above Mattermost).
* - Left column holds RTCD when Calls is on.
* - Right column holds Push Proxy / HPNS.
*
* Connection labels are minimal (just ports). The Network Flows table
* below the BOM documents what each port is for.
*/
function ArchDiagram({
tier,
k8s,
provider,
showCalls,
pushMode,
}: {
tier: Tier;
k8s: boolean;
provider: Provider;
showCalls: boolean;
pushMode: 'self-hosted' | 'hpns';
}) {
const showLb = tier.loadBalancer !== 'none';
const showRedis = tier.redis;
const showSearch = tier.search !== 'none';
const showReplica = tier.dbReplicas > 0;
const lbName = k8s ? LB_BRAND_K8S[provider] : LB_BRAND_VM[provider];
return (
<div className={styles.diagram} aria-label="Architecture diagram">
{/* Client tier — labeled container, two cards (Web/Desktop + Mobile) */}
<div className={styles.gridCenter}>
<div className={styles.clientsBox}>
<div className={styles.clientsLabel}>Clients</div>
<div className={styles.clientsRow}>
<div className={styles.client}>
<div className={styles.clientLabel}>Web &amp; Desktop</div>
<div className={styles.clientSub}>Channels web app · Windows · macOS · Linux</div>
</div>
<div className={styles.client}>
<div className={styles.clientLabel}>Mobile</div>
<div className={styles.clientSub}>iOS · Android</div>
</div>
</div>
</div>
</div>
{/* Flow row: main HTTPS arrow from clients into LB / Mattermost */}
<div className={styles.gridCenter}>
<FlowDown port="TCP/443" />
</div>
{/* Edge tier (ALB) — center column, locked above Mattermost */}
{showLb && (
<>
<div className={styles.gridCenter}>
<div className={`${styles.box} ${styles.boxLb}`}>
<div className={styles.boxLabel}>{lbName}</div>
<div className={styles.boxSub}>
{tier.loadBalancer === 'cluster' ? 'HA pair · TLS termination' : 'TLS termination'}
</div>
</div>
</div>
<div className={styles.gridCenter}>
<FlowDown port="TCP/8065" />
</div>
</>
)}
{/* App row: RTCD (left) — Mattermost (center) — Push (right), all in one row.
* The DIRECT pill floats absolutely above the RTCD box so it doesn't claim
* its own grid row (which would create an empty band across the diagram). */}
{showCalls && (
<div className={styles.gridLeft}>
<div className={styles.sideGroup}>
<div className={styles.rtcdWrap}>
<div className={styles.flowDirectAbove} aria-label="Direct client media path to RTCD">
<span className={styles.directBadge}>direct</span>
<span className={styles.flowArrowDiag}>↓</span>
<span className={styles.flowPorts}>UDP+TCP/8443</span>
</div>
<div className={`${styles.box} ${styles.boxCalls}`}>
<div className={styles.boxLabel}>RTCD (Calls)</div>
<div className={styles.boxSub}>
{tier.appNodes >= 8 ? 'HA (2–3 nodes)' : '1 node'} · audio + screen-share
</div>
</div>
</div>
<HFlow direction="left" port="TCP/8045" />
</div>
</div>
)}
<div className={styles.gridCenter}>
<div className={`${styles.box} ${styles.boxApp}`}>
<div className={styles.boxLabel}>
Mattermost{k8s ? ' (Operator)' : ''}
<span className={styles.qty}>×{tier.appNodes}</span>
</div>
<div className={styles.boxSub}>
{tier.appCpu} vCPU · {tier.appRam} GB RAM{tier.ha ? ' · HA cluster' : ''}
</div>
</div>
</div>
<div className={styles.gridRight}>
<div className={styles.sideGroup}>
<HFlow direction="right" port="HTTPS/443" />
{pushMode === 'self-hosted' ? (
<div className={`${styles.box} ${styles.boxPush}`}>
<div className={styles.boxLabel}>Push Proxy</div>
<div className={styles.boxSub}>self-hosted</div>
</div>
) : (
<div className={`${styles.box} ${styles.boxPushManaged}`}>
<div className={styles.boxLabel}>Mattermost HPNS</div>
<div className={styles.boxSub}>
<span className={styles.managedBadge}>managed by Mattermost</span>
<br />
Hosted Push Notifications Service
</div>
</div>
)}
</div>
</div>
{/* Data tier — spans full width below center */}
<div className={`${styles.gridFull} ${styles.rowData}`}>
<DataItem
port="TCP/5432"
accent={styles.boxDb}
label={DB_BRAND[provider]}
subtitle={`primary${showReplica ? ` + ${tier.dbReplicas} replica${tier.dbReplicas > 1 ? 's' : ''}` : ''}`}
/>
{showRedis && (
<DataItem
port="TCP/6379"
accent={styles.boxRedis}
label={REDIS_BRAND[provider]}
subtitle={`${tier.appNodes >= 8 ? 'Cluster' : 'Single'} · sessions + cluster state`}
/>
)}
{showSearch && (
<DataItem
port="HTTPS/9200"
accent={styles.boxSearch}
label={SEARCH_BRAND[provider]}
subtitle={`${tier.searchNodes} node${tier.searchNodes > 1 ? 's' : ''} · message search`}
/>
)}
<DataItem
port="HTTPS/443"
accent={styles.boxStorage}
label={tier.storage === 'object' ? STORAGE_BRAND[provider] : 'Local disk'}
subtitle={tier.storage === 'object' ? 'S3-compatible · uploads + plugins' : 'host disk (trial)'}
/>
</div>
</div>
);
}
/* ============================ Network Flows table ============================
*
* Documents every connection in the architecture: source → destination,
* protocol, port, and purpose. Useful for firewall configuration.
* Rows are conditional on the user's choices. */
type NetworkFlow = {
from: string;
to: string;
protocol: string;
port: string;
direction: 'inbound' | 'internal' | 'egress';
purpose: string;
};
function networkFlowsFor(
tier: Tier,
provider: Provider,
showCalls: boolean,
pushMode: 'self-hosted' | 'hpns',
): NetworkFlow[] {
const flows: NetworkFlow[] = [];
flows.push({
from: 'Web / Desktop / Mobile clients',
to: tier.loadBalancer !== 'none' ? 'Load balancer' : 'Mattermost',
protocol: 'HTTPS + WSS',
port: 'TCP/443',
direction: 'inbound',
purpose: 'Channels API, WebSocket events, file uploads',
});
if (showCalls) {
flows.push({
from: 'Web / Desktop / Mobile clients',
to: 'RTCD',
protocol: 'UDP, TCP (fallback)',
port: '8443',
direction: 'inbound',
purpose: 'Calls media (audio, video, screen-share) — direct, bypasses LB',
});
}
if (tier.loadBalancer !== 'none') {
flows.push({
from: 'Load balancer',
to: 'Mattermost',
protocol: 'HTTP',
port: 'TCP/8065',
direction: 'internal',
purpose: 'TLS-terminated app traffic',
});
}
flows.push({
from: 'Mattermost',
to: `PostgreSQL (${DB_BRAND[provider]})`,
protocol: 'TCP',
port: '5432',
direction: 'internal',
purpose: 'Database queries',
});
if (tier.redis) {
flows.push({
from: 'Mattermost',
to: `Redis (${REDIS_BRAND[provider]})`,
protocol: 'TCP',
port: '6379',
direction: 'internal',
purpose: 'Sessions, cluster coordination, cache',
});
}
if (tier.search !== 'none') {
flows.push({
from: 'Mattermost',
to: `OpenSearch (${SEARCH_BRAND[provider]})`,
protocol: 'HTTPS',
port: '9200',
direction: 'internal',
purpose: 'Message search index reads + writes',
});
}
flows.push({
from: 'Mattermost',
to: tier.storage === 'object' ? `Object storage (${STORAGE_BRAND[provider]})` : 'Local disk',
protocol: 'HTTPS',
port: 'TCP/443',
direction: tier.storage === 'object' ? 'internal' : 'internal',
purpose: 'File uploads, plugin assets, compliance exports',
});
if (showCalls) {
flows.push({
from: 'Mattermost',
to: 'RTCD',
protocol: 'TCP',
port: '8045',
direction: 'internal',
purpose: 'Calls signaling and orchestration API',
});
}
flows.push({
from: 'Mattermost',
to: pushMode === 'self-hosted' ? 'Mattermost Push Proxy (self-hosted)' : 'Mattermost HPNS (managed)',
protocol: 'HTTPS',
port: 'TCP/443',
direction: pushMode === 'self-hosted' ? 'internal' : 'egress',
purpose: 'Push notification submission',
});
flows.push({
from: pushMode === 'self-hosted' ? 'Mattermost Push Proxy' : 'Mattermost HPNS',
to: 'Apple APNs (api.push.apple.com)',
protocol: 'HTTPS',
port: 'TCP/443',
direction: 'egress',
purpose: 'iOS push fan-out',
});
flows.push({
from: pushMode === 'self-hosted' ? 'Mattermost Push Proxy' : 'Mattermost HPNS',
to: 'Google FCM (fcm.googleapis.com)',
protocol: 'HTTPS',
port: 'TCP/443',
direction: 'egress',
purpose: 'Android push fan-out',
});
return flows;
}
function NetworkFlowsTable({flows}: {flows: NetworkFlow[]}) {
return (
<>
<h3 className={styles.bomHeading}>Network flows</h3>
<p className={styles.flowsIntro}>
Every connection in the architecture above — useful when configuring firewall
rules, security groups, or a CAP. <strong>Inbound</strong> = from clients;{' '}
<strong>internal</strong> = within the deployment boundary;{' '}
<strong>egress</strong> = out to external services.
</p>
<div className={styles.bomTableWrap}>
<table className={styles.bomTable}>
<thead>
<tr>
<th>From</th>
<th>To</th>
<th>Direction</th>
<th>Protocol</th>
<th>Port</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
{flows.map((f, i) => (
<tr key={`${f.from}-${f.to}-${i}`}>
<td className={styles.cellComponent}>{f.from}</td>
<td className={styles.cellComponent}>{f.to}</td>
<td className={styles.cellQty}>
<span className={`${styles.directionPill} ${styles[`dir_${f.direction}`]}`}>
{f.direction}
</span>
</td>
<td className={styles.cellSpec}>{f.protocol}</td>
<td className={styles.cellSpec}>{f.port}</td>
<td className={styles.cellNotes}>{f.purpose}</td>
</tr>
))}
</tbody>
</table>
</div>
</>
);
}
/* ============================ Main component ============================ */
export default function DeploymentArchitectureBuilder() {
const [tierId, setTierId] = useState<string>('medium');
const [deploymentType, setDeploymentType] = useState<DeploymentType>('vm');
const [provider, setProvider] = useState<Provider>('aws');
const [includeCalls, setIncludeCalls] = useState<boolean>(false);
const [selfHostPush, setSelfHostPush] = useState<boolean>(true);
const tier = useMemo(
() => TIERS.find((t) => t.id === tierId) ?? TIERS[0],
[tierId],
);
const bom = useMemo(
() => bomFor(tier, deploymentType, provider, {calls: includeCalls, push: selfHostPush}),
[tier, deploymentType, provider, includeCalls, selfHostPush],
);
const flows = useMemo(
() => networkFlowsFor(tier, provider, includeCalls, selfHostPush ? 'self-hosted' : 'hpns'),
[tier, provider, includeCalls, selfHostPush],
);
return (
<section className={styles.builder} aria-label="Deployment architecture builder">
<div className={styles.controls}>
<ControlGroup label="Scale tier">
<select
className={styles.select}
value={tierId}
onChange={(e) => setTierId(e.target.value)}
aria-label="Scale tier"
>
{TIERS.map((t) => (
<option key={t.id} value={t.id}>
{t.seats}
</option>
))}
</select>
</ControlGroup>
<ControlGroup label="Deployment type">
<SegmentedControl
name="deployment-type"
value={deploymentType}
onChange={(v) => setDeploymentType(v as DeploymentType)}
options={[
{value: 'vm', label: 'Virtual machines'},
{value: 'kubernetes', label: 'Kubernetes'},
]}
/>
</ControlGroup>
<ControlGroup label="Hosting">
<select
className={styles.select}
value={provider}
onChange={(e) => setProvider(e.target.value as Provider)}
aria-label="Hosting"
>
{PROVIDERS.map((p) => (
<option key={p.id} value={p.id}>{p.label}</option>
))}
</select>
</ControlGroup>
<ControlGroup label="Optional services">
<div className={styles.checkboxes}>
<label className={styles.checkbox}>
<input
type="checkbox"
checked={includeCalls}
onChange={(e) => setIncludeCalls(e.target.checked)}
/>
<span>Calls (RTCD)</span>
</label>
<label className={styles.checkbox}>
<input
type="checkbox"
checked={selfHostPush}
onChange={(e) => setSelfHostPush(e.target.checked)}
/>
<span>Self-host Push Proxy</span>
</label>
</div>
</ControlGroup>
</div>
<aside className={styles.summary} role="note">
<strong>{tier.seats}</strong> — {tier.description}
</aside>
<ArchDiagram
tier={tier}
k8s={deploymentType === 'kubernetes'}
provider={provider}
showCalls={includeCalls}
pushMode={selfHostPush ? 'self-hosted' : 'hpns'}
/>
<h3 className={styles.bomHeading}>Bill of materials</h3>
<div className={styles.bomTableWrap}>
<table className={styles.bomTable}>
<thead>
<tr>
<th>Component</th>
<th>Quantity</th>
<th>Recommended size / SKU</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
{bom.map((row, i) => (
<tr key={`${row.component}-${i}`}>
<td className={styles.cellComponent}>{row.component}</td>
<td className={styles.cellQty}>{row.quantity}</td>
<td className={styles.cellSpec}>{row.spec}</td>
<td className={styles.cellNotes}>{row.notes}</td>
</tr>
))}
</tbody>
</table>
</div>
<NetworkFlowsTable flows={flows} />
<p className={styles.caveat}>
Sizing follows the Mattermost scaling guide; provider SKUs are illustrative
starting points. Verify against current pricing, regional availability, and any
compliance constraints before procurement.
</p>
</section>
);
}
/* ============================ Small UI bits ============================ */
function ControlGroup({label, children}: {label: string; children: React.ReactNode}) {
return (
<div className={styles.controlGroup}>
<div className={styles.controlLabel}>{label}</div>
{children}
</div>
);
}
function SegmentedControl<T extends string>({
name,
value,
onChange,
options,
}: {
name: string;
value: T;
onChange: (v: T) => void;
options: {value: T; label: string}[];
}) {
return (
<div className={styles.segmented} role="radiogroup" aria-label={name}>
{options.map((opt) => (
<label
key={opt.value}
className={`${styles.segment} ${value === opt.value ? styles.segmentActive : ''}`}
>
<input
type="radio"
name={name}
value={opt.value}
checked={value === opt.value}
onChange={() => onChange(opt.value)}
className={styles.segmentInput}
/>
{opt.label}
</label>
))}
</div>
);
}