Refine production workspace layouts
This commit is contained in:
@@ -4,8 +4,9 @@
|
||||
|
||||
| Environment | Purpose | Address |
|
||||
|---|---|---|
|
||||
| Windows | Source editing, local Docker build, and initial testing | `http://localhost:9190` |
|
||||
| Rocky Linux | Production-like isolated staging | `http://192.168.1.101:9190` |
|
||||
| Windows | Source editing and Git workflow only | Not applicable |
|
||||
| Rocky Linux | Docker development and production-like staging | `http://192.168.1.101:9190` |
|
||||
| Future VPS | Planned deployment environment | Not provisioned yet |
|
||||
| Workplace production | Out of scope until reviewed and approved | Not documented here |
|
||||
|
||||
Rocky checkout:
|
||||
@@ -37,30 +38,9 @@ codex/staging-baseline
|
||||
|
||||
## Windows development
|
||||
|
||||
Build the frontend using the project Node container:
|
||||
|
||||
```powershell
|
||||
docker compose run --rm node
|
||||
```
|
||||
|
||||
Start or update the complete local stack when backend or Compose changes require it:
|
||||
|
||||
```powershell
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
Seed deterministic observations:
|
||||
|
||||
```powershell
|
||||
docker compose --profile tools run --rm seed
|
||||
```
|
||||
|
||||
Inspect status and logs:
|
||||
|
||||
```powershell
|
||||
docker compose ps
|
||||
docker compose logs --tail 100 scala
|
||||
```
|
||||
Use Windows to edit and review source files and to create Git commits. Docker builds,
|
||||
the running application, synthetic-data operations, and browser testing belong on
|
||||
Rocky. Docker Desktop is not part of the normal project workflow.
|
||||
|
||||
## Commit and transfer workflow
|
||||
|
||||
@@ -126,9 +106,8 @@ Prefer a normal Git revert rather than manually copying old files:
|
||||
|
||||
1. Identify the faulty commit with `git log --oneline`.
|
||||
2. On the development checkout, run `git revert <commit>`.
|
||||
3. Build and test the reverted state locally.
|
||||
3. Push the revert and build and test it on Rocky.
|
||||
4. Push the revert commit.
|
||||
5. Pull it on Rocky and run the appropriate frontend-only or full-stack deployment command.
|
||||
|
||||
Database rollback is a separate operation and must not be inferred from a source rollback. Database backup and restore procedures are not yet defined.
|
||||
|
||||
|
||||
@@ -14,4 +14,49 @@
|
||||
}
|
||||
.overlayFields{display:grid;grid-template-columns:minmax(260px,2fr) minmax(160px,1fr);gap:12px;padding:16px 16px 0}.overlayFields label,.overlayFields span{display:block}.overlayFields span{margin-bottom:6px;font-size:12px;font-weight:700}.overlayFields input{width:100%}
|
||||
.mapControlSections{display:grid;gap:18px;padding:16px}.valueControls{padding-bottom:16px;border-bottom:1px solid var(--border)}
|
||||
|
||||
/* Workspace layout: controls explain the content without enclosing every region. */
|
||||
.aggregator>.container{
|
||||
grid-template-columns:230px minmax(0,1fr);
|
||||
grid-template-areas:"cities query" "cities results";
|
||||
column-gap:28px;
|
||||
row-gap:18px;
|
||||
}
|
||||
.aggregator .workspaceRail{
|
||||
grid-area:cities;
|
||||
padding:4px 24px 10px 0;
|
||||
border-right:1px solid rgba(90,126,166,.2);
|
||||
}
|
||||
.aggregator .workspaceRail h2{margin:0 0 18px;font-size:16px}
|
||||
.aggregator .workspaceToolbar{
|
||||
grid-area:query;
|
||||
display:grid;
|
||||
grid-template-columns:minmax(330px,1.15fr) minmax(480px,1.85fr);
|
||||
gap:14px 28px;
|
||||
align-items:end;
|
||||
padding:2px 0 20px;
|
||||
border-bottom:1px solid rgba(90,126,166,.22);
|
||||
}
|
||||
.aggregator .workspaceToolbar>h2{grid-column:1/-1;margin:0;font-size:18px}
|
||||
.aggregator .queryOptions{
|
||||
display:grid;
|
||||
grid-template-columns:minmax(130px,1fr) minmax(125px,1.05fr) minmax(115px,.8fr);
|
||||
gap:20px;
|
||||
align-items:end;
|
||||
}
|
||||
.aggregator .queryPanel h4{margin:0 0 9px}
|
||||
.aggregator .queryPanel p{margin:8px 0 0}
|
||||
.aggregator .queryOptions ul{margin:0;padding:0}
|
||||
.aggregator .resultsPanel{grid-area:results}
|
||||
|
||||
@media(max-width:1200px){
|
||||
.aggregator>.container{grid-template-columns:210px minmax(0,1fr);grid-template-areas:"cities query" "cities results"}
|
||||
.aggregator .workspaceToolbar{grid-template-columns:1fr}
|
||||
.aggregator .workspaceToolbar>h2{grid-column:auto}
|
||||
}
|
||||
@media(max-width:800px){
|
||||
.aggregator>.container{grid-template-columns:1fr;grid-template-areas:"cities" "query" "results"}
|
||||
.aggregator .workspaceRail{padding-right:0;border-right:0;border-bottom:1px solid rgba(90,126,166,.2)}
|
||||
.aggregator .queryOptions{grid-template-columns:1fr}
|
||||
}
|
||||
.pageWorkspace{max-width:1800px;margin:auto;padding:34px 28px 70px}.pageHeading{display:flex;align-items:end;justify-content:space-between;gap:24px;margin-bottom:24px}.pageHeading h1{margin:5px 0 6px;font-size:34px}.pageHeading p{margin:0;color:var(--text-muted)}.selectionCount{padding:10px 14px;border:1px solid var(--border);border-radius:10px;background:var(--surface);font-weight:700}.aggregator>.container{display:grid;grid-template-columns:240px 300px minmax(0,1fr);gap:18px;align-items:start}.panel{padding:20px;border:1px solid var(--border);border-radius:16px;background:var(--surface);box-shadow:var(--shadow-sm)}.panel h2{margin:0 0 18px;font-size:16px}.cityPanel{max-height:calc(100vh - 160px);overflow:auto}.cityPanel ul{margin:12px 0;padding:0;columns:1}.cityPanel li{padding:3px 0}.queryPanel h4{margin:22px 0 9px}.queryPanel p{margin:10px 0}.resultsPanel{min-width:0}.emptyState{display:flex;min-height:260px;align-items:center;justify-content:center;flex-direction:column;gap:8px;margin-top:18px;padding:30px;border:1px dashed #bac6d4;border-radius:16px;background:rgba(255,255,255,.55);color:var(--text-muted);text-align:center}.emptyState strong{color:var(--text);font-size:18px}.mapWorkspace{margin-top:14px}.mapToolbar{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.mapToolbar strong,.mapToolbar span{display:block}.mapToolbar span{margin-top:3px;color:var(--text-muted);font-size:12px}.broadcastControls{margin-bottom:14px;border:1px solid var(--border);border-radius:12px;background:var(--surface)}.broadcastControls summary{padding:14px 16px;cursor:pointer}.broadcastControls summary span,.broadcastControls summary strong,.broadcastControls summary small{display:block}.broadcastControls summary small,.controlHelp,.fieldHint{color:var(--text-muted);font-size:12px}.broadcastControls[open] summary{border-bottom:1px solid var(--border)}.broadcastControls .grid-1-2{padding:16px}.controlHelp{margin:16px 16px 0}.canvasFrame{overflow:hidden;border:1px solid var(--border);border-radius:14px;background:#d8dee6;box-shadow:var(--shadow-sm)}.canvasFrame canvas{display:block}.weatherIconButton{border-radius:7px}.fieldHint{margin:4px 0 10px}@media(max-width:1200px){.aggregator>.container{grid-template-columns:220px 1fr}.resultsPanel{grid-column:1/-1}.cityPanel{max-height:none}}@media(max-width:700px){.pageWorkspace{padding:24px 14px}.pageHeading{align-items:start;flex-direction:column}.aggregator>.container{grid-template-columns:1fr}.resultsPanel{grid-column:auto}}
|
||||
|
||||
@@ -24,13 +24,13 @@ export function Cities() {
|
||||
<div class="aggregator pageWorkspace">
|
||||
<div class="pageHeading"><div><span class="eyebrow">Historical observations</span><h1>City analysis</h1><p>Choose stations and a calculation, then compare the resulting values.</p></div><div class="selectionCount">{getCities().size} cities selected</div></div>
|
||||
<div class="container">
|
||||
<aside class="column panel cityPanel">
|
||||
<aside class="column cityPanel workspaceRail">
|
||||
<h2>1. Choose cities</h2>
|
||||
<SelectCity getCities={getCities} setCities={setCities} />
|
||||
</aside>
|
||||
<aside class="column panel queryPanel">
|
||||
<aside class="column queryPanel workspaceToolbar">
|
||||
<h2>2. Configure query</h2>
|
||||
<div>
|
||||
<div class="queryTimeRange">
|
||||
<SelectTimeRange
|
||||
getStart={getStart}
|
||||
setStart={setStart}
|
||||
@@ -38,8 +38,8 @@ export function Cities() {
|
||||
setEnd={setEnd}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<div class="queryOptions">
|
||||
<div class="queryField">
|
||||
<h4>Select field</h4>
|
||||
<SelectField getField={getField} setField={setField} />
|
||||
</div>
|
||||
|
||||
@@ -36,12 +36,14 @@ export function Faktiska() {
|
||||
return <div class="mapGraphicsPage pageWorkspace">
|
||||
<div class="pageHeading"><div><span class="eyebrow">Daily newsroom production</span><h1>Faktiskā</h1><p>Create the actual-weather map with temperatures and weather symbols.</p></div><div class="selectionCount">{cities().size} cities selected</div></div>
|
||||
<div class="graphicsSetup">
|
||||
<aside class="panel cityPanel"><h2>1. Choose cities</h2><SelectCity getCities={cities} setCities={setCities}/></aside>
|
||||
<aside class="cityPanel workspaceRail"><h2>1. Choose cities</h2><SelectCity getCities={cities} setCities={setCities}/></aside>
|
||||
<section class="graphicsMain">
|
||||
<div class="panel productionOptions"><h2>2. Choose output</h2>
|
||||
<div class="choiceGroup"><span>Output</span><div><button classList={{active: resolution() === "1920x1080"}} onClick={() => setResolution("1920x1080")}>1920 × 1080</button><button classList={{active: resolution() === "3840x1440"}} onClick={() => setResolution("3840x1440")}>3840 × 1440</button><label class="windChoice"><input type="checkbox" checked={wind()} onChange={e => setWind(e.target.checked)}/> Wind layout</label></div></div>
|
||||
<div class="productionToolbar">
|
||||
<div class="productionOptions"><h2>2. Choose output</h2>
|
||||
<div class="choiceGroup"><span>Output</span><div><button classList={{active: resolution() === "1920x1080"}} onClick={() => setResolution("1920x1080")}>1920 × 1080</button><button classList={{active: resolution() === "3840x1440"}} onClick={() => setResolution("3840x1440")}>3840 × 1440</button><label class="windChoice"><input type="checkbox" checked={wind()} onChange={e => setWind(e.target.checked)}/> Wind layout</label></div></div>
|
||||
</div>
|
||||
<div class="dataOptions"><h2>3. Retrieve temperatures</h2><div class="dataOptionsGrid"><SelectTimeRange getStart={start} setStart={setStart} getEnd={end} setEnd={setEnd}/><label><span>Temperature field</span><select value={field()} onChange={e => setField(e.target.value)}><option value="tempMax">Maximum temperature</option><option value="tempMin">Minimum temperature</option><option value="tempAvg">Average temperature</option></select></label><label><span>Calculation</span><select value={key()} onChange={e => setKey(e.target.value)}><option value="max">Maximum</option><option value="min">Minimum</option><option value="avg">Average</option></select></label><button class="primary loadMapButton" disabled={!cities().size} onClick={() => setTrigger(Date.now())}>Load map data</button></div></div>
|
||||
</div>
|
||||
<div class="panel dataOptions"><h2>3. Retrieve temperatures</h2><div class="dataOptionsGrid"><SelectTimeRange getStart={start} setStart={setStart} getEnd={end} setEnd={setEnd}/><label><span>Temperature field</span><select value={field()} onChange={e => setField(e.target.value)}><option value="tempMax">Maximum temperature</option><option value="tempMin">Minimum temperature</option><option value="tempAvg">Average temperature</option></select></label><label><span>Calculation</span><select value={key()} onChange={e => setKey(e.target.value)}><option value="max">Maximum</option><option value="min">Minimum</option><option value="avg">Average</option></select></label></div><button class="primary" disabled={!cities().size} onClick={() => setTrigger(Date.now())}>Load map data</button></div>
|
||||
<Show when={result.loading}><LoadingSpinner text="Loading map data"/></Show>
|
||||
<Show when={result.error}><div class="emptyState"><strong>Could not load map data</strong><span>{result.error.message}</span></div></Show>
|
||||
<Show when={result() && isQueryResult(result())}><section class="mapProduction"><h2>4. Add conditions and export</h2><MapView type={mapType()} data={cityData} mode="faktiska"/></section></Show>
|
||||
|
||||
@@ -1,2 +1,34 @@
|
||||
.graphicsSetup{display:grid;grid-template-columns:240px minmax(0,1fr);gap:18px;align-items:start}.graphicsMain{display:grid;gap:18px}.productionOptions,.dataOptions{padding:20px}.choiceGroup{display:grid;grid-template-columns:130px 1fr;gap:16px;align-items:start;margin-top:16px}.choiceGroup>span,.dataOptions label>span{display:block;font-size:12px;font-weight:700}.choiceGroup>div{display:flex;flex-wrap:wrap;gap:9px}.choiceGroup button{min-width:150px;text-align:left}.choiceGroup button small{display:block;margin-top:3px;color:var(--text-muted);font-family:Inter,sans-serif}.choiceGroup button.active{border-color:var(--accent);background:var(--accent-pale);color:var(--accent-strong)}.windChoice{display:flex;align-items:center;gap:7px;padding:9px 12px}.dataOptionsGrid{display:grid;grid-template-columns:minmax(250px,1.4fr) 1fr 1fr;gap:18px;align-items:end;margin-bottom:16px}.dataOptions select{width:100%;margin-top:6px}.mapProduction>h2{margin:0 0 10px}.symbolEditor{min-width:0}.symbolEditorHeading{display:flex;align-items:start;justify-content:space-between;gap:12px}.symbolPalette{display:flex;flex-wrap:wrap;gap:5px;margin:10px 0 16px}.citySymbols{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:8px}.citySymbolRow{display:grid;grid-template-columns:1fr 52px 54px;gap:6px;align-items:center;padding:7px;border:1px solid var(--border);border-radius:9px}.assignedSymbol{font-family:'Daira by LTV grafika';font-size:28px;padding:2px}.clearSymbol{padding:5px;font-size:11px}@media(max-width:900px){.graphicsSetup{grid-template-columns:1fr}.dataOptionsGrid{grid-template-columns:1fr}.choiceGroup{grid-template-columns:1fr}}
|
||||
.symbolEditorHeading{gap:16px}.bulkSymbol{display:flex;align-items:center;gap:8px}.selectedPreview,.currentSymbol{display:flex;align-items:center;justify-content:center;font-family:'Daira by LTV grafika';font-size:30px}.selectedPreview{width:42px;height:42px;border-radius:9px;background:#17212d;color:#fff}.symbolPalette{display:grid;grid-template-columns:repeat(auto-fill,46px);gap:8px;margin:14px 0 22px;padding:14px;border:1px solid var(--border);border-radius:14px;background:var(--surface-soft)}.assignmentHeading{display:flex;align-items:baseline;gap:10px;margin-bottom:10px}.assignmentHeading span{color:var(--text-muted);font-size:12px}.citySymbols{grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:10px}.citySymbolRow{grid-template-columns:minmax(90px,1fr) 48px 100px 36px;gap:8px;min-height:62px;padding:8px 10px;border-radius:12px;background:#fff}.cityName{overflow:hidden;text-overflow:ellipsis;font-weight:700}.currentSymbol{width:44px;height:44px;border:1px solid #d7dfe8;border-radius:9px;background:#f6f8fa}.currentSymbol.empty{font-family:Inter,sans-serif;font-size:18px;color:#98a2b3}.assignAction{padding:7px 9px;font-size:12px}.clearSymbol{width:36px;padding:0;font-size:20px;color:var(--text-muted)}@media(max-width:900px){.symbolEditorHeading{flex-direction:column}.citySymbols{grid-template-columns:1fr}}
|
||||
|
||||
/* Production layout: a compact control band above one uninterrupted workspace. */
|
||||
.graphicsSetup{grid-template-columns:230px minmax(0,1fr);column-gap:28px}
|
||||
.mapGraphicsPage .workspaceRail{
|
||||
padding:4px 24px 10px 0;
|
||||
border-right:1px solid rgba(90,126,166,.2);
|
||||
}
|
||||
.mapGraphicsPage .workspaceRail h2{margin:0 0 18px;font-size:16px}
|
||||
.graphicsMain{gap:16px}
|
||||
.productionToolbar{
|
||||
padding:2px 0 20px;
|
||||
border-bottom:1px solid rgba(90,126,166,.22);
|
||||
}
|
||||
.productionOptions,.dataOptions{padding:0}
|
||||
.productionOptions h2,.dataOptions h2{margin:0;font-size:18px}
|
||||
.productionOptions{display:grid;grid-template-columns:210px minmax(0,1fr);gap:24px;align-items:center}
|
||||
.productionOptions .choiceGroup{margin:0}
|
||||
.dataOptions{display:grid;grid-template-columns:210px minmax(0,1fr);gap:24px;align-items:center;margin-top:18px;padding-top:18px;border-top:1px solid rgba(90,126,166,.14)}
|
||||
.dataOptionsGrid{grid-template-columns:minmax(300px,1.35fr) minmax(180px,1fr) minmax(160px,.9fr) auto;gap:16px;margin:0;align-items:end}
|
||||
.loadMapButton{white-space:nowrap;align-self:end}
|
||||
.mapProduction>h2{font-size:22px}
|
||||
|
||||
@media(max-width:1200px){
|
||||
.productionOptions,.dataOptions{grid-template-columns:1fr;gap:12px}
|
||||
.dataOptionsGrid{grid-template-columns:repeat(2,minmax(0,1fr))}
|
||||
}
|
||||
@media(max-width:900px){
|
||||
.graphicsSetup{grid-template-columns:1fr}
|
||||
.mapGraphicsPage .workspaceRail{padding-right:0;border-right:0;border-bottom:1px solid rgba(90,126,166,.2)}
|
||||
.dataOptionsGrid{grid-template-columns:1fr}
|
||||
.loadMapButton{justify-self:start}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user