Redmine へチケットを起票するボタンを追加

chktime のタスクと git 履歴から下書きを作り、まとめて Redmine へ登録できる
ようにした。外部通信はこの機能を使ったときだけで、使わなければ従来どおり
ローカルの jsonl を読むだけ。

- Redmine の REST API を叩く最小クライアント (標準ライブラリのみ)
- 下書きは「タスクから」(1タスク=1チケット) と「git 履歴から」
  (日ごと / 日 x 種別 / 期間一括) の 2 系統
- 起票は件数 x 間隔ぶん時間がかかるのでジョブにし、進捗だけ見に行く
- 作成時の status_id は Redmine 側で無視されるため、作成後に付け直す
- 連続アクセスに 403 を返すサーバがあるので 1 件ごとに間隔をあける (既定 7 秒)
- 自己署名証明書が普通なので、既定は証明書の検証なし
- API キーは redmine.json に置き、git 管理外にする

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 13:55:46 +09:00
co-authored by Claude Opus 5
parent d23bcabd76
commit a458509014
7 changed files with 1021 additions and 5 deletions
+92
View File
@@ -297,6 +297,98 @@ table { width: 100%; border-collapse: collapse; }
.gantt svg rect.g-hit { fill: transparent; cursor: pointer; }
.gantt svg rect.g-hit:hover { fill: #ffffff; opacity: .07; }
/* Redmine 起票ダイアログ */
.hidden { display: none !important; }
.modal-back {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, .55);
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.modal {
display: flex;
flex-direction: column;
width: min(880px, 94vw);
height: min(760px, 92vh);
background: var(--pane);
border: 1px solid var(--line);
border-radius: 8px;
overflow: hidden;
}
.modal-head, .modal-foot {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 14px;
background: var(--pane2);
border-bottom: 1px solid var(--line);
}
.modal-foot { border-bottom: none; border-top: 1px solid var(--line); }
.modal-head .spacer, .modal-foot .spacer { flex: 1; }
.modal-body { flex: 1; overflow: auto; padding: 12px 14px 20px; }
.modal .note { color: var(--dim); font-size: 11px; }
.modal .err-inline { color: #f7768e; font-size: 11px; }
.modal .go { border-color: var(--work); color: var(--work); }
.modal .go:disabled { border-color: var(--line); color: var(--dim); cursor: default; }
.rm-sec { margin-bottom: 14px; }
.rm-sec > h4 {
margin: 0 0 6px;
font-size: 11px;
font-weight: 500;
letter-spacing: .08em;
color: var(--dim);
text-transform: uppercase;
}
.rm-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.rm-row label { color: var(--dim); font-size: 11px; }
.modal input[type="text"], .modal input[type="password"], .modal input[type="date"], .modal select {
background: var(--bg);
border: 1px solid var(--line);
border-radius: 5px;
color: var(--fg);
padding: 3px 8px;
font: inherit;
font-size: 12px;
color-scheme: dark;
}
.modal input[type="text"], .modal input[type="password"] { flex: 1 1 220px; min-width: 140px; }
.modal input:focus, .modal select:focus { outline: none; border-color: var(--accent); }
.rm-list { border: 1px solid var(--line); border-radius: 6px; overflow: hidden; }
.rm-item { border-bottom: 1px solid var(--line); }
.rm-item:last-child { border-bottom: none; }
.rm-item > .h { display: flex; align-items: flex-start; gap: 8px; padding: 6px 10px; cursor: pointer; }
.rm-item > .h:hover { background: var(--pane2); }
.rm-item .t { flex: 1; min-width: 0; }
.rm-item .subject { font-size: 12.5px; word-break: break-word; }
.rm-item .meta { color: var(--dim); font-size: 11px; margin-top: 2px; }
.rm-item .body {
display: none;
white-space: pre-wrap;
word-break: break-word;
font-size: 11px;
color: var(--dim);
background: var(--bg);
border-top: 1px solid var(--line);
padding: 8px 10px;
max-height: 260px;
overflow: auto;
}
.rm-item.open .body { display: block; }
.rm-item.off .subject, .rm-item.off .meta { opacity: .45; text-decoration: line-through; }
.rm-res { display: flex; gap: 8px; padding: 4px 10px; border-bottom: 1px solid var(--line); font-size: 12px; }
.rm-res:last-child { border-bottom: none; }
.rm-res .id { color: var(--work); font-variant-numeric: tabular-nums; white-space: nowrap; }
.rm-res .id.ng { color: #f7768e; }
.rm-res .s { flex: 1; min-width: 0; word-break: break-word; }
.rm-prog { height: 6px; background: var(--bg); border: 1px solid var(--line); border-radius: 4px; overflow: hidden; margin: 8px 0; }
.rm-prog i { display: block; height: 100%; background: var(--work); transition: width .2s; }
/* ガント (ターン単位) */
.gantt .g-no { color: var(--dim); font-variant-numeric: tabular-nums; white-space: nowrap; }
.gantt svg rect.t-wait { fill: var(--user); }