fix docker deployment

This commit is contained in:
Alberto-Audrix committed 2026-09-11 14:48:18 +07:00
1 parent ded52d1ece
commit fb0f996776
17 files changed
+49 -18

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
docker/**/*.sh text eol=lf
docker/crontab text eol=lf
docker/Dockerfile* text eol=lf
docker/nginx.conf text eol=lf
docker/entrypoint-*.sh text eol=lf
+1 -1
View File
@@ -36,7 +36,7 @@ Deploy **dashboard-cpsp-executive** (HQ) with Docker Compose. Same layout as sit
- Docker 20.10+
- Docker Compose v2+
- Ports available: **80** (UI), **18001** (direct API), **15433** (Postgres on localhost)
- Ports available: **80** (UI; set `FRONTEND_PORT=8080` in `.env` if busy), **18001** (direct API), **15433** (Postgres on localhost)
```bash
docker --version
+1
View File
@@ -33,6 +33,7 @@ Same Compose layout as the site app. On the HQ host: UI **:80**, API debug **:18
```bash
cp docker/.env.example .env
# set SECRET_KEY, DB_PASSWORD, BOOTSTRAP_ADMIN_PASSWORD
# optional: FRONTEND_PORT=8080 if host :80 is busy
docker compose up -d --build
```
+4 -4
View File
@@ -4,7 +4,7 @@ from __future__ import annotations
import logging
import requests
import httpx
from apps.farms.models import Cycle
@@ -32,11 +32,11 @@ def push_close_decision(cycle: Cycle, *, approved: bool) -> None:
payload["end_date"] = cycle.end_date.isoformat()
try:
response = requests.post(
response = httpx.post(
url,
json=payload,
headers={"X-API-Key": city.api_key, "Content-Type": "application/json"},
timeout=30,
timeout=30.0,
)
if response.status_code >= 400:
raise SiteClosePushError(
@@ -44,6 +44,6 @@ def push_close_decision(cycle: Cycle, *, approved: bool) -> None:
)
except SiteClosePushError:
raise
except requests.RequestException as exc:
except httpx.HTTPError as exc:
logger.exception("Push close decision failed for cycle %s", cycle.pk)
raise SiteClosePushError("Gagal kirim keputusan ke situs — coba lagi") from exc
+6
View File
@@ -32,6 +32,12 @@ function phasesFor(totalDays: number): Phase[] {
const statusConfig = {
active: { label: 'Active', bg: 'bg-red-100', text: 'text-red-800', icon: 'fa-running' },
pending_close: {
label: 'Pending Close',
bg: 'bg-amber-100',
text: 'text-amber-800',
icon: 'fa-hourglass-half',
},
closed: {
label: 'Completed',
bg: 'bg-green-100',
@@ -138,7 +138,9 @@ describe('UserManagementPage', () => {
it('resets password', async () => {
render(<UserManagementPage />);
await screen.findByText('gm1');
fireEvent.click(screen.getAllByRole('button', { name: /Reset password/i })[0]);
const resetBtn = screen.getAllByRole('button', { name: /Reset password/i })[0];
expect(resetBtn).toBeTruthy();
fireEvent.click(resetBtn!);
fireEvent.change(screen.getByPlaceholderText(/Password baru/i), {
target: { value: 'reset-me' },
});
+3 -1
View File
@@ -116,7 +116,9 @@ const DataManagementPage: React.FC<{ onSettings: () => void }> = ({ onSettings }
const kpiByDate = new Map(kpis.map((k) => [k.date, k]));
const lastCycleDay = Math.max(0, (selectedCycle.total_days || 1) - 1);
const endDay = cycleDayForDate(selectedCycle.start_date, selectedCycle.end_date);
const endDay = selectedCycle.end_date
? cycleDayForDate(selectedCycle.start_date, selectedCycle.end_date)
: lastCycleDay;
const calendarDay = cycleDayForDate(selectedCycle.start_date, todayIso());
const maxDay = Math.min(calendarDay, lastCycleDay, endDay);
const today = todayIso();
@@ -65,7 +65,9 @@ const ReportPreviewOverlay: React.FC<Props> = ({ open, reportType, onClose }) =>
const endCycleDay = selectedCycle
? Math.min(
selectedCycle.total_days,
cycleDayForDate(selectedCycle.start_date, selectedCycle.end_date)
selectedCycle.end_date
? cycleDayForDate(selectedCycle.start_date, selectedCycle.end_date)
: Math.max(0, (selectedCycle.total_days || 1) - 1)
)
: 0;
const effectiveUpToDay = isEndCycle ? endCycleDay : draftDay;
+3 -2
View File
@@ -144,7 +144,8 @@ services:
container_name: dashboard-cpsp-executive-frontend
restart: unless-stopped
ports:
- "80:80"
# Override with FRONTEND_PORT in .env when host :80 is busy (e.g. local Windows).
- "${FRONTEND_PORT:-80}:80"
depends_on:
api:
condition: service_healthy
@@ -152,7 +153,7 @@ services:
- dashboard-cpsp-executive-network
healthcheck:
test:
["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:80/health"]
["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:80/health"]
interval: 30s
timeout: 3s
retries: 3
+3
View File
@@ -13,6 +13,9 @@ ALLOWED_HOSTS=localhost,127.0.0.1,api,frontend,executive.local
CSRF_TRUSTED_ORIGINS=http://localhost,http://127.0.0.1,http://executive.local
CORS_ALLOWED_ORIGINS=http://localhost,http://127.0.0.1,http://executive.local
# Host port for the UI (default 80). Use 8080 if :80 is already taken.
FRONTEND_PORT=80
# Production admin (bootstrap_admin — no demo data)
BOOTSTRAP_ADMIN_USER=admin
BOOTSTRAP_ADMIN_PASSWORD=Pr04dm1n
+3 -1
View File
@@ -22,10 +22,12 @@ COPY backend/ .
COPY docker/entrypoint-api.sh docker/entrypoint-cron.sh docker/karung_sync_loop.sh /app/
COPY docker/crontab /etc/cron.d/executive
# /etc/cron.d files include a username field; system cron reads them directly
# (do not install with `crontab`, which expects user-crontab format).
RUN chmod +x /app/entrypoint-api.sh /app/entrypoint-cron.sh /app/karung_sync_loop.sh \
&& mkdir -p /app/data /app/media /app/staticfiles \
&& chmod 0644 /etc/cron.d/executive \
&& crontab /etc/cron.d/executive
&& touch /var/log/cron.log
EXPOSE 8000
+1 -1
View File
@@ -20,6 +20,6 @@ COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost:80/health || exit 1
CMD wget --quiet --tries=1 --spider http://127.0.0.1:80/health || exit 1
CMD ["nginx", "-g", "daemon off;"]
+3 -1
View File
@@ -7,6 +7,7 @@ printenv | grep -E '^(PATH|DJANGO_|SECRET_|DB_|KARUNG_|IOT_|CHICKEN_COUNTING_|DA
# Render publish-time cron from DASHBOARD_PUBLISH_* (default 17:00).
# Edge syncs are OFF by default at HQ (mirror via city-sites); commands no-op when disabled.
# File lives in /etc/cron.d (requires username field) — do not feed it to `crontab`.
PUBLISH_HOUR="${DASHBOARD_PUBLISH_HOUR:-17}"
PUBLISH_MINUTE="${DASHBOARD_PUBLISH_MINUTE:-0}"
CRON_FILE=/etc/cron.d/executive
@@ -20,9 +21,10 @@ CRON_FILE=/etc/cron.d/executive
echo ""
echo "# Daily publish — optional karung sync then KPI rollups at configured cutoff."
echo "${PUBLISH_MINUTE} ${PUBLISH_HOUR} * * * root cd /app && /usr/local/bin/python manage.py sync_karung_from_web_admin >> /var/log/cron.log 2>&1 && /usr/local/bin/python manage.py recompute_kpi_rollups >> /var/log/cron.log 2>&1"
echo ""
} > "$CRON_FILE"
chmod 0644 "$CRON_FILE"
crontab "$CRON_FILE"
touch /var/log/cron.log
if [ "${KARUNG_WEB_ADMIN_SYNC_ENABLED:-false}" = "true" ] || [ "${KARUNG_WEB_ADMIN_SYNC_ENABLED:-false}" = "True" ]; then
/app/karung_sync_loop.sh &
+1 -1
View File
@@ -14,7 +14,7 @@ const cycle: Cycle = {
chick_in_weight: 45,
doc_in_count: 20000,
feed_initial_balance: 0,
status: 'gm',
status: 'active',
created_at: '',
updated_at: '',
};
@@ -14,7 +14,7 @@ const cycle = {
chick_in_weight: 45,
doc_in_count: 1000,
feed_initial_balance: 0,
status: 'gm',
status: 'active',
created_at: '',
updated_at: '',
} satisfies Cycle;
+4 -2
View File
@@ -50,7 +50,9 @@ export function buildInMemoryReport(input: BuildInMemoryReportInput): DashboardR
// Day numbering stays 0-based (day 0 = start_date), but the report must never
// run past the cycle end date. lastDay = start_date → end_date in days, which
// is total_days - 1 for a contiguous cycle.
const lastDay = cycleDayForDate(cycle.start_date, cycle.end_date);
const lastDay = cycle.end_date
? cycleDayForDate(cycle.start_date, cycle.end_date)
: Math.max(0, (cycle.total_days || 1) - 1);
const endCycleUpTo = Math.min(cycle.total_days, lastDay);
const defaultUpTo =
input.reportType === 'end_cycle'
@@ -202,7 +204,7 @@ export function buildInMemoryReport(input: BuildInMemoryReportInput): DashboardR
return {
cycleId: String(cycle.id),
cycleStart: cycle.start_date,
cycleEnd: cycle.end_date,
cycleEnd: cycle.end_date ?? '',
kandangId: input.kandangId,
kandangName: input.kandangName,
area: input.area,
+3 -1
View File
@@ -38,7 +38,9 @@ function stockAkhirByDate(
const manualByDate = new Map(manualInputs.map((m) => [m.date, m]));
const lastCycleDay = Math.max(0, (cycle.total_days || 1) - 1);
const endDay = cycleDayForDate(cycle.start_date, cycle.end_date);
const endDay = cycle.end_date
? cycleDayForDate(cycle.start_date, cycle.end_date)
: lastCycleDay;
const maxDay = Math.min(lastCycleDay, endDay);
const result = new Map<string, number>();