fix: surface hash-duplicate warning and add manual refresh
Three issues fixed:
1. **Skipped-commit warning not visible** — When the user commits
unchanged content (hash matches most recent version), the backend
correctly returns {skipped: true, reason: "unchanged", message: ...}
but the frontend Notification.warning was easy to miss (default
4-5s autoClose, small toast at top-right). Now uses {autoClose: false}
so the warning stays until manually dismissed, plus a console.log
of the raw commit response for debugging. Added a fallback branch
that warns clearly if the response shape is unexpected.
2. **No manual refresh on the panel** — In S3 setups the list call
can occasionally lag the just-committed entry (manifest strong
consistency is a per-implementation property). Added a "刷新"
button at the top of the sidebar that calls model.refresh(), letting
the user force a re-fetch when the auto-refresh misses.
3. **S3 addressing_style** — user-side config addition: S3Storage
now uses path-style addressing (endpoint/bucket/key) which is the
format required by MinIO and several other S3-compatible services.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -82,7 +82,10 @@ class S3Storage(StorageAdapter):
|
||||
aws_access_key_id=access_key,
|
||||
aws_secret_access_key=secret_key,
|
||||
region_name="us-east-1",
|
||||
config=BotoConfig(signature_version="s3v4"),
|
||||
config=BotoConfig(
|
||||
signature_version='s3v4',
|
||||
s3={'addressing_style': 'path'},
|
||||
),
|
||||
)
|
||||
self._ClientError = ClientError
|
||||
self._connect_and_ensure_bucket()
|
||||
|
||||
+19
-4
@@ -64,12 +64,27 @@ export function registerCommands(
|
||||
name,
|
||||
description
|
||||
);
|
||||
if (result.skipped) {
|
||||
// Content hash matches the most recent version — nothing to save.
|
||||
Notification.warning(result.message ?? '内容未变更,已跳过保存');
|
||||
} else if (result.entry) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[snapshot] commit response:', result);
|
||||
if (result && result.skipped) {
|
||||
// Hash matched the most recent version — nothing to save. Use
|
||||
// autoClose:false so the warning stays visible until the user
|
||||
// dismisses it (the default 4-5s timeout is easy to miss).
|
||||
Notification.warning(
|
||||
result.message ?? '内容未变更,已跳过保存',
|
||||
{ autoClose: false }
|
||||
);
|
||||
} else if (result && result.entry) {
|
||||
await model.refresh();
|
||||
Notification.success(`已保存快照:${name}`);
|
||||
} else {
|
||||
// Unexpected response shape — surface clearly so the user is not
|
||||
// left wondering whether the save happened.
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[snapshot] unexpected commit response shape:', result);
|
||||
Notification.warning('保存响应格式异常,请查看浏览器控制台', {
|
||||
autoClose: false
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
Notification.error(`保存快照失败:${(e as Error).message}`);
|
||||
|
||||
@@ -46,18 +46,30 @@ const Panel: React.FC<PanelProps> = ({ model, app, tracker, serverSettings, onOp
|
||||
if (!model.path) {
|
||||
return <div className="jp-snapshot-panel-empty">未选中 notebook</div>;
|
||||
}
|
||||
if (model.loading && model.versions.length === 0) {
|
||||
return <div className="jp-snapshot-panel-empty">加载中…</div>;
|
||||
}
|
||||
if (model.versions.length === 0) {
|
||||
return (
|
||||
<div className="jp-snapshot-panel-container">
|
||||
<div className="jp-snapshot-panel-toolbar">
|
||||
<button
|
||||
className="jp-snapshot-panel-refresh-button"
|
||||
onClick={() => {
|
||||
void model.refresh();
|
||||
}}
|
||||
disabled={model.loading}
|
||||
title="重新拉取版本列表"
|
||||
>
|
||||
{model.loading ? '刷新中…' : '刷新'}
|
||||
</button>
|
||||
</div>
|
||||
{model.loading && model.versions.length === 0 ? (
|
||||
<div className="jp-snapshot-panel-empty">加载中…</div>
|
||||
) : model.versions.length === 0 ? (
|
||||
<div className="jp-snapshot-panel-empty">
|
||||
<div>暂无快照</div>
|
||||
<div className="jp-snapshot-panel-hint">点击工具栏「保存快照」按钮创建</div>
|
||||
<div className="jp-snapshot-panel-hint">
|
||||
点击工具栏「保存快照」按钮创建
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
</div>
|
||||
) : (
|
||||
<ul className="jp-snapshot-panel-list">
|
||||
{model.versions.map(v => (
|
||||
<li key={v.id} className="jp-snapshot-panel-item">
|
||||
@@ -110,6 +122,8 @@ const Panel: React.FC<PanelProps> = ({ model, app, tracker, serverSettings, onOp
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,6 +13,37 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.jp-snapshot-panel-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.jp-snapshot-panel-toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.jp-snapshot-panel-refresh-button {
|
||||
font-size: 0.8em;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid var(--jp-border-color2, #ccc);
|
||||
border-radius: 3px;
|
||||
background: var(--jp-layout-color2, #f5f5f5);
|
||||
color: var(--jp-ui-font-color1, #333);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.jp-snapshot-panel-refresh-button:hover:not(:disabled) {
|
||||
background: var(--jp-layout-color3, #e8e8e8);
|
||||
}
|
||||
|
||||
.jp-snapshot-panel-refresh-button:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.jp-snapshot-panel-empty {
|
||||
color: var(--jp-ui-font-color2, #555);
|
||||
padding: 12px;
|
||||
|
||||
Reference in New Issue
Block a user