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:
+14
-1
@@ -905,11 +905,24 @@ table.tbl a.rowlink:hover {
|
||||
|
||||
.upload-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
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 {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -260,20 +260,40 @@ export function TaskDetail({ id }: { id: number }) {
|
||||
}
|
||||
}
|
||||
|
||||
function downloadExampleCSV() {
|
||||
const sample = [
|
||||
'alice@source.example,SrcPass1,alice@dest.example,DstPass1',
|
||||
'bob@source.example,SrcPass2,bob@dest.example,DstPass2',
|
||||
'carol@source.example,SrcPass3,carol@dest.example,DstPass3',
|
||||
].join('\n') + '\n'
|
||||
const url = URL.createObjectURL(new Blob([sample], { type: 'text/csv' }))
|
||||
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 = 'imap-copier-accounts-example.csv'
|
||||
a.download = name
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
// Plain import: comma-separated, no header, src_login,src_pass,dst_login,dst_pass.
|
||||
function downloadExampleCSV() {
|
||||
const sample =
|
||||
[
|
||||
'alice@source.example,SrcPass1,alice@dest.example,DstPass1',
|
||||
'bob@source.example,SrcPass2,bob@dest.example,DstPass2',
|
||||
'carol@source.example,SrcPass3,carol@dest.example,DstPass3',
|
||||
].join('\n') + '\n'
|
||||
downloadCSV('imap-copier-accounts-example.csv', sample)
|
||||
}
|
||||
|
||||
// Kerio Connect export: semicolon-separated with a Name;FullName;Description;Enable
|
||||
// header. The password lives in Description; disabled rows and admin are skipped
|
||||
// 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>) {
|
||||
const file = e.target.files?.[0]
|
||||
if (!file) return
|
||||
@@ -556,16 +576,23 @@ export function TaskDetail({ id }: { id: number }) {
|
||||
|
||||
<div className="divider-label">or bulk import</div>
|
||||
<div className="upload-row">
|
||||
<label className={`btn file-btn${busy !== null ? ' is-disabled' : ''}`}>
|
||||
{busy === 'import' ? 'Importing…' : 'Upload CSV'}
|
||||
<input ref={fileInputRef} type="file" accept=".csv,text/csv" onChange={onFileChosen} disabled={busy !== null} />
|
||||
</label>
|
||||
<button type="button" className="btn" onClick={() => setKerioOpen(true)} disabled={busy !== null}>
|
||||
Import from Kerio
|
||||
</button>
|
||||
<button type="button" className="link-btn" onClick={downloadExampleCSV}>
|
||||
download example.csv
|
||||
</button>
|
||||
<div className="upload-item">
|
||||
<label className={`btn file-btn${busy !== null ? ' is-disabled' : ''}`}>
|
||||
{busy === 'import' ? 'Importing…' : 'Upload CSV'}
|
||||
<input ref={fileInputRef} type="file" accept=".csv,text/csv" onChange={onFileChosen} disabled={busy !== null} />
|
||||
</label>
|
||||
<button type="button" className="link-btn" onClick={downloadExampleCSV}>
|
||||
download example.csv
|
||||
</button>
|
||||
</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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user