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 {
|
.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;
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user