관리자 표가 넘치던 문제 수정, 이메일 내보내기 제거

- 관리자 표: 칸의 줄바꿈 금지를 풀고 화면 폭을 넓혀, 긴 파일 이름이 있어도
  펼쳤을 때 버튼이 표 밖으로 밀리지 않게 함
- 이메일 내보내기 기능 제거 (버튼, mailTemplate 설정, 문구, 문서)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
엄태정 2026-11-05 10:44:05 +09:00
parent 22b2e19431
commit 6019c7df95
10 changed files with 23 additions and 22 deletions

View File

@ -16,7 +16,7 @@
* 비밀번호는 필수 (1회 다운로드만 예외). 무작위 비밀번호 생성 버튼
* **"인증된 사용자는 비밀번호 없이 다운로드"**: 업로드 로그인 상태인 사내 사용자는 링크를 열 때 비밀번호를 묻지 않고,
외부 수신자는 비밀번호를 입력
* 완료 후 "링크와 비밀번호 복사", QR 코드, 이메일 버튼
* 완료 후 "링크와 비밀번호 복사", QR 코드 버튼
* "내 업로드": 이 브라우저에서 올린 링크를 다시 보기 (비밀번호는 저장하지 않음)
* 업로드 로그인은 한 번 하면 7일 유지되고, 사용할 때마다 연장

View File

@ -11,7 +11,6 @@ export const ICONS = {
lock: '<rect width="18" height="11" x="3" y="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
trash: '<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/>',
search: '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
mail: '<rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/>',
qr: '<rect width="5" height="5" x="3" y="3" rx="1"/><rect width="5" height="5" x="16" y="3" rx="1"/><rect width="5" height="5" x="3" y="16" rx="1"/><path d="M21 16h-3a2 2 0 0 0-2 2v3"/><path d="M21 21v.01"/><path d="M12 7v3a2 2 0 0 1-2 2H7"/><path d="M3 12h.01"/><path d="M12 3h.01"/><path d="M12 16v.01"/><path d="M16 12h1"/><path d="M21 12v.01"/><path d="M12 21v-1"/>',
history: '<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M12 7v5l4 2"/>',
refresh: '<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/>',

View File

@ -247,8 +247,8 @@ const resetLogo = () => sendLogo({ method: 'DELETE' });
<table>
<thead>
<tr>
<th>{{ t('colSid') }}</th><th>{{ t('colFiles') }}</th><th class="r">{{ t('colSize') }}</th>
<th>{{ t('colCreated') }}</th><th>{{ t('colLastDownload') }}</th><th>{{ t('colExpires') }}</th><th></th>
<th>{{ t('colSid') }}</th><th class="nw">{{ t('colFiles') }}</th><th class="r nw">{{ t('colSize') }}</th>
<th class="date">{{ t('colCreated') }}</th><th class="date">{{ t('colLastDownload') }}</th><th class="date">{{ t('colExpires') }}</th><th></th>
</tr>
</thead>
<tbody>
@ -261,15 +261,15 @@ const resetLogo = () => sendLogo({ method: 'DELETE' });
<Icon v-if="b.hasPassword" name="lock" :size="14" />
<span v-if="b.partial" class="tag warn">{{ t('unfinished') }}</span>
</td>
<td>{{ t('filesCount', b.files.length) }}</td>
<td class="r">{{ fmtSize(b.size) }}</td>
<td>{{ fmtDate(b.createdAt) }}</td>
<td><template v-if="b.lastDownload">{{ fmtDate(b.lastDownload) }}</template><span v-else class="muted">-</span></td>
<td>
<td class="nw">{{ t('filesCount', b.files.length) }}</td>
<td class="r nw">{{ fmtSize(b.size) }}</td>
<td class="date">{{ fmtDate(b.createdAt) }}</td>
<td class="date"><template v-if="b.lastDownload">{{ fmtDate(b.lastDownload) }}</template><span v-else class="muted">-</span></td>
<td class="date">
<span v-if="b.oneTime" class="tag warn">{{ t('oneTimeDownload') }}</span>
<template v-else>{{ fmtDate(b.expiresAt) }} <span class="tag" :class="isSoon(b.expiresAt, data.now) ? 'warn' : 'opt'">{{ remain(b.expiresAt, data.now) }}</span></template>
</td>
<td class="r" style="white-space:nowrap">
<td class="r nw">
<button class="btn sm" @click.stop="downloadLink({ sid: b.sid, format: 'zip' })"><Icon name="download" :size="15" /><span class="hide-m">zip</span></button>
<button class="btn sm ic-only" :title="t('deleteBucket')" :aria-label="t('deleteBucket')" @click.stop="askDeleteBucket(b)"><Icon name="trash" :size="15" /></button>
</td>
@ -285,9 +285,9 @@ const resetLogo = () => sendLogo({ method: 'DELETE' });
</div>
</div>
</td>
<td class="r">{{ fmtSize(f.size) }}</td>
<td class="r nw">{{ fmtSize(f.size) }}</td>
<td colspan="3"></td>
<td class="r" style="white-space:nowrap">
<td class="r nw">
<button v-if="f.previewType" class="btn sm ic-only" :title="t('preview')" :aria-label="t('preview')" @click="openPreview(b, f)"><Icon name="eye" :size="15" /></button>
<button class="btn sm ic-only" :title="t('download')" :aria-label="t('download')" @click="downloadLink({ sid: b.sid, key: f.key })"><Icon name="download" :size="15" /></button>
<button class="btn sm ic-only" :title="t('delete')" :aria-label="t('delete')" @click="askDeleteFile(b, f)"><Icon name="trash" :size="15" /></button>

View File

@ -192,8 +192,6 @@ function openPreview(file) {
// ---- done
const showQr = ref(false);
const showMine = ref(false);
const mailLink = computed(() => store.result && config.value.mailTemplate
? config.value.mailTemplate.replace('%%URL%%', store.result.link) : '');
const expiryText = computed(() => {
const r = store.result;
if (!r) return '';
@ -325,7 +323,6 @@ function newUpload() {
<div class="done-acts">
<button class="btn primary" @click="copyAll"><Icon name="copy" />{{ t('copyLinkAndPassword') }}</button>
<button v-if="!config.disableQrCode" class="btn" @click="showQr = true"><Icon name="qr" />{{ t('qrCode') }}</button>
<a v-if="mailLink" class="btn" :href="mailLink"><Icon name="mail" />{{ t('email') }}</a>
</div>
</div>

View File

@ -184,8 +184,18 @@ input::placeholder{color:var(--muted)}
.tools .s input{padding-left:34px}
.tools select{border:1px solid var(--border);background:var(--surface);border-radius:10px;padding:9px 12px;font-size:14px}
table{width:100%;border-collapse:collapse;font-size:14px}
th{text-align:left;font-size:12px;color:var(--muted);font-weight:700;padding:10px 14px;border-bottom:1px solid var(--border);white-space:nowrap}
td{padding:11px 14px;border-bottom:1px solid var(--border);vertical-align:middle;white-space:nowrap}
th{text-align:left;font-size:12px;color:var(--muted);font-weight:700;padding:10px 12px;border-bottom:1px solid var(--border);white-space:nowrap}
/* cells may wrap, so a long file name can not push the buttons out of the table */
td{padding:10px 12px;border-bottom:1px solid var(--border);vertical-align:middle}
td.nw,th.nw{white-space:nowrap}
td.date,th.date{white-space:nowrap}
tr.frow .rn{white-space:normal;word-break:break-all;overflow:visible}
.admin-view .wrap{max-width:1240px}
@media (max-width:1000px){
/* dates wrap into date and time instead of widening the table */
td.date,th.date{white-space:normal;min-width:84px}
table{font-size:13.5px}
}
tr.b{cursor:pointer}
tr.b:hover td{background:var(--surface-2)}
tr.frow td{background:var(--surface-2)}

View File

@ -61,7 +61,6 @@ const config = {
"maxAge": 3600 * 24 * 75, // 75 days
// maximum file-size for text previews in byte
"maxPreviewSize": Math.pow(2, 20) * 2, // 2MB
"mailTemplate": 'mailto:?subject=파일 전달&body=다운로드 링크: %%URL%%',
// see https://github.com/expressjs/morgan
// set to false to disable logging
"accessLog": ':date[iso] :method :url :status :response-time :remote-addr',

View File

@ -50,7 +50,6 @@ node app.js
| `maxAge` | 75일 | 어떤 파일도 이 시간(초)이 지나면 삭제됩니다. 다운로드되지 않은 1회 다운로드 파일에도 적용됩니다 |
| `maxFileSize`, `maxBucketSize` | 제한 없음 | 파일 하나, 한 번에 올리는 전체의 최대 크기(바이트) |
| `maxPreviewSize` | 2MB | 텍스트 미리보기에서 읽는 최대 크기 |
| `mailTemplate` | `mailto:?subject=...` | 이메일 버튼의 `mailto:` 주소. `%%URL%%`이 다운로드 링크로 바뀝니다 |
| `disableQrCode` | `false` | QR 코드 버튼 숨기기 |
| `defaultLanguage` | `ko` | 브라우저 언어를 알 수 없을 때 쓰는 언어 |

View File

@ -81,7 +81,6 @@ module.exports = {
bypassUsers: "Logged-in users",
qrCode: "QR code",
qrHint: "Scan the code with a phone to open the download page",
email: "E-Mail",
// my uploads
myUploadsEmpty: "There are no uploads from this browser yet.",

View File

@ -81,7 +81,6 @@ module.exports = {
bypassUsers: "인증된 사용자",
qrCode: "QR 코드",
qrHint: "휴대폰 카메라로 스캔하면 다운로드 페이지가 열립니다",
email: "이메일",
// 내 업로드
myUploadsEmpty: "이 브라우저에서 올린 업로드가 아직 없습니다.",

View File

@ -176,7 +176,6 @@ app.get(`${ config.baseUrl }config.json`, (req, res) => {
retentions: config.retentions,
quickRetentions: config.quickRetentions,
defaultRetention: config.defaultRetention,
mailTemplate: config.mailTemplate,
requireBucketPassword: config.requireBucketPassword,
maxFileSize: config.maxFileSize,
maxBucketSize: config.maxBucketSize,