diff --git a/docker-compose.yml b/docker-compose.yml index 987f2cb..baa726a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,10 +7,10 @@ services: env_file: - .env environment: - MINIO_ENDPOINT: minio - MINIO_PORT: 9000 - MINIO_USE_SSL: "false" - PORT: 3000 + MINIO_ENDPOINT: ${MINIO_ENDPOINT:-minio} + MINIO_PORT: ${MINIO_PORT:-9000} + MINIO_USE_SSL: ${MINIO_USE_SSL:-false} + PORT: ${PORT:-3000} ports: - "3000:3000" depends_on: @@ -27,7 +27,6 @@ services: MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-minioadmin} MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:-minioadmin} ports: - - "9000:9000" - "9001:9001" restart: unless-stopped volumes: diff --git a/src/public/style.css b/src/public/style.css index 5824074..eae99bc 100644 --- a/src/public/style.css +++ b/src/public/style.css @@ -98,6 +98,8 @@ button:disabled { opacity: 0.55; cursor: not-allowed; } display: flex; flex-direction: column; flex: 1; + width: calc(100vw - 3rem); + margin-left: calc(50% - 50vw + 1.5rem); overflow: hidden; } @@ -248,7 +250,12 @@ textarea#content::placeholder { color: var(--text-muted); } .hidden { display: none !important; } /* ─── Paste View ─── */ -.paste-view { padding-top: 1.25rem; } +.paste-view { + padding-top: 1.25rem; + max-width: none; + width: calc(100vw - 3rem); + margin-left: calc(50% - 50vw + 1.5rem); +} .paste-meta { display: flex; @@ -336,6 +343,8 @@ textarea#content::placeholder { color: var(--text-muted); } overflow: auto; flex: 1; display: flex; + width: calc(100vw - 3rem); + margin-left: calc(50% - 50vw + 1.5rem); } #paste-content pre { diff --git a/src/routes/pastes.js b/src/routes/pastes.js index 01e5732..91ed94e 100644 --- a/src/routes/pastes.js +++ b/src/routes/pastes.js @@ -44,6 +44,29 @@ function parseExpiryOption(expiresIn) { return new Date(Date.now() + ms).toISOString(); } +function normalizeClientIp(ip) { + if (typeof ip !== 'string' || ip.length === 0) { + return 'unknown'; + } + + let normalized = ip.trim(); + if (normalized.startsWith('::ffff:')) { + normalized = normalized.slice(7); + } + + return normalized.replace(/[^0-9a-fA-F:.]/g, '').slice(0, 64) || 'unknown'; +} + +async function setPasteTags(id, tags) { + if (typeof client.setObjectTagging === 'function') { + await client.setObjectTagging(BUCKET, id, tags); + return; + } + if (typeof client.putObjectTagging === 'function') { + await client.putObjectTagging(BUCKET, id, tags); + } +} + function getExpiryFromMeta(metaData) { return ( metaData['x-amz-meta-expires-at'] || @@ -99,6 +122,7 @@ router.post('/', wrap(async (req, res) => { const id = generateId(); const buf = Buffer.from(content, 'utf8'); + const clientIp = normalizeClientIp(req.ip); const meta = { 'Content-Type': 'text/plain; charset=utf-8' }; if (typeof lang === 'string' && lang.length > 0) { @@ -116,6 +140,7 @@ router.post('/', wrap(async (req, res) => { } await client.putObject(BUCKET, id, buf, buf.length, meta); + await setPasteTags(id, { client_ip: clientIp }); res.status(201).json({ id, url: `/p/${id}`, expiresAt }); }));