Add a Kerio sample file to the bulk import

Only the plain four-column format had a downloadable example, leaving
the Kerio route undocumented in the UI. Each import button now carries
its own sample link underneath: the plain comma-separated layout and a
Kerio export with the Name;FullName;Description;Enable header, a
disabled row included to show what the import skips.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-28 11:50:45 +07:00
co-authored by Claude Opus 5
parent 8bc7ff026d
commit bb3635e517
2 changed files with 59 additions and 19 deletions
+14 -1
View File
@@ -905,11 +905,24 @@ table.tbl a.rowlink:hover {
.upload-row { .upload-row {
display: flex; display: flex;
align-items: center; align-items: flex-start;
gap: 12px; gap: 12px;
flex-wrap: wrap; flex-wrap: wrap;
} }
/* One import route per column: the action on top, its sample file underneath. */
.upload-item {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 6px;
}
.upload-item .link-btn {
align-self: center;
text-align: center;
}
.file-btn { .file-btn {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
+37 -10
View File
@@ -260,18 +260,38 @@ export function TaskDetail({ id }: { id: number }) {
} }
} }
function downloadCSV(name: string, content: string) {
const url = URL.createObjectURL(new Blob([content], { type: 'text/csv' }))
const a = document.createElement('a')
a.href = url
a.download = name
a.click()
URL.revokeObjectURL(url)
}
// Plain import: comma-separated, no header, src_login,src_pass,dst_login,dst_pass.
function downloadExampleCSV() { function downloadExampleCSV() {
const sample = [ const sample =
[
'alice@source.example,SrcPass1,alice@dest.example,DstPass1', 'alice@source.example,SrcPass1,alice@dest.example,DstPass1',
'bob@source.example,SrcPass2,bob@dest.example,DstPass2', 'bob@source.example,SrcPass2,bob@dest.example,DstPass2',
'carol@source.example,SrcPass3,carol@dest.example,DstPass3', 'carol@source.example,SrcPass3,carol@dest.example,DstPass3',
].join('\n') + '\n' ].join('\n') + '\n'
const url = URL.createObjectURL(new Blob([sample], { type: 'text/csv' })) downloadCSV('imap-copier-accounts-example.csv', sample)
const a = document.createElement('a') }
a.href = url
a.download = 'imap-copier-accounts-example.csv' // Kerio Connect export: semicolon-separated with a Name;FullName;Description;Enable
a.click() // header. The password lives in Description; disabled rows and admin are skipped
URL.revokeObjectURL(url) // on import, and the domain is supplied in the import dialog.
function downloadKerioExampleCSV() {
const sample =
[
'Name;FullName;Description;Enable',
'alice;Alice Smith;SrcPass1;Yes',
'bob;Bob Jones;SrcPass2;Yes',
'carol;Carol White (disabled, skipped);SrcPass3;No',
].join('\n') + '\n'
downloadCSV('kerio-users-example.csv', sample)
} }
async function onFileChosen(e: ChangeEvent<HTMLInputElement>) { async function onFileChosen(e: ChangeEvent<HTMLInputElement>) {
@@ -556,17 +576,24 @@ export function TaskDetail({ id }: { id: number }) {
<div className="divider-label">or bulk import</div> <div className="divider-label">or bulk import</div>
<div className="upload-row"> <div className="upload-row">
<div className="upload-item">
<label className={`btn file-btn${busy !== null ? ' is-disabled' : ''}`}> <label className={`btn file-btn${busy !== null ? ' is-disabled' : ''}`}>
{busy === 'import' ? 'Importing…' : 'Upload CSV'} {busy === 'import' ? 'Importing…' : 'Upload CSV'}
<input ref={fileInputRef} type="file" accept=".csv,text/csv" onChange={onFileChosen} disabled={busy !== null} /> <input ref={fileInputRef} type="file" accept=".csv,text/csv" onChange={onFileChosen} disabled={busy !== null} />
</label> </label>
<button type="button" className="btn" onClick={() => setKerioOpen(true)} disabled={busy !== null}>
Import from Kerio
</button>
<button type="button" className="link-btn" onClick={downloadExampleCSV}> <button type="button" className="link-btn" onClick={downloadExampleCSV}>
download example.csv download example.csv
</button> </button>
</div> </div>
<div className="upload-item">
<button type="button" className="btn" onClick={() => setKerioOpen(true)} disabled={busy !== null}>
Import from Kerio
</button>
<button type="button" className="link-btn" onClick={downloadKerioExampleCSV}>
download kerio example.csv
</button>
</div>
</div>
</div> </div>
<div className="panel"> <div className="panel">