@takagaki/cortex-decisions-viewer 0.12.129 → 0.12.132
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/render.js +816 -541
- package/package.json +1 -1
package/dist/render.js
CHANGED
|
@@ -2471,6 +2471,10 @@ function sanitizeGithubOwnersNow(v) {
|
|
|
2471
2471
|
const row = { owner, appInstalled: r.appInstalled === true ? true : r.appInstalled === false ? false : null };
|
|
2472
2472
|
if (typeof r.repos === "number" && Number.isFinite(r.repos) && r.repos >= 0)
|
|
2473
2473
|
row.repos = r.repos;
|
|
2474
|
+
// その案件のテナントのアプリの slug(行の「入れる」リンクの宛先・cortex-engine #1328 T-6)。🔴 slug の形だけを残す
|
|
2475
|
+
// (画面がリンクに組み立てる)。⚠ この関数は toString で埋め込みJSへ入るので、正規表現は関数の中に逆斜線なしで書く
|
|
2476
|
+
if (typeof r.appSlug === "string" && /^[a-z0-9][a-z0-9-]{0,33}$/.test(r.appSlug))
|
|
2477
|
+
row.appSlug = r.appSlug;
|
|
2474
2478
|
out.push(row);
|
|
2475
2479
|
}
|
|
2476
2480
|
return out.length ? out : null;
|
|
@@ -5716,6 +5720,17 @@ a.ds-tile:hover { text-decoration: none; }
|
|
|
5716
5720
|
.ds-gate-ok { color: #1f7a45; }
|
|
5717
5721
|
.ds-gate-warn { color: #b45309; }
|
|
5718
5722
|
.ds-gate-muted { color: #6b7280; }
|
|
5723
|
+
/* 行の事実の札(c-07 の 2 本目): 固定 IP 経由・リアルタイム同期の稼働中/未受信。
|
|
5724
|
+
.ds-gate(届くか札は 1 つ)とは別の部品。状態の色(緑・橙)は使わず、事実の弱い札として読ませる */
|
|
5725
|
+
.ds-route-chip, .ds-rt-chip { display: inline-flex; align-items: baseline; gap: 6px; flex: 0 0 auto; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--border); font-size: 11.5px; color: #6b7280; background: #fff; }
|
|
5726
|
+
.ds-rt-chip.is-live { color: #1c7a43; border-color: #b7e0c6; background: #e8f6ed; }
|
|
5727
|
+
.ds-rt-at { font-size: 11px; color: var(--muted); font-weight: 400; }
|
|
5728
|
+
/* 一覧の下の 1 行の一手(リアルタイム同期の設定・[設定する]で手順が開く)と、ふだん見えない「その他」 */
|
|
5729
|
+
.ds-rt-help { margin: 8px 0 0; font-size: 12px; line-height: 1.7; color: #6b7280; }
|
|
5730
|
+
.ds-more { margin-top: 14px; }
|
|
5731
|
+
.ds-more > summary { cursor: pointer; color: var(--muted); font-size: 12px; }
|
|
5732
|
+
.ds-more[open] > summary { margin-bottom: 8px; }
|
|
5733
|
+
.ds-more-key { margin: 6px 0 0; font-size: 12.5px; color: var(--text); display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
|
|
5719
5734
|
/* 行の状態に添える「次に誰が何をするか」(2026-08-18)。**必ず自分の行を占める**
|
|
5720
5735
|
(flex: 1 1 100%)——状態バッジの横に押し込むと、いちばん読ませたい一文が
|
|
5721
5736
|
カード幅に負けて省略される。状態の語より弱く、しかし読める大きさで出す */
|
|
@@ -10088,6 +10103,18 @@ ${calendarAddUrl.toString()}
|
|
|
10088
10103
|
resting: function (row) { return slackRowHelpParts(row); },
|
|
10089
10104
|
},
|
|
10090
10105
|
},
|
|
10106
|
+
// 🔴 **Backlog の行の一手(c-07 の 2 本目)。** 行の中身(そのスペースのキーの有無・同期経路・
|
|
10107
|
+
// スペースの数)で決まるので関数で持つ。row は { domain, projectKey, gate }。
|
|
10108
|
+
// DS_GATE_CAP.issues は札(APIキーが未登録です)だけを持ち、help を持たせない(一手の置き場は 1 か所)。
|
|
10109
|
+
// 部品の型は 文字列 / { copy, bare } / { act: "backlog-key" | "backlog-route-on", label, space }
|
|
10110
|
+
backlog: {
|
|
10111
|
+
no_token: {
|
|
10112
|
+
resting: function (row) { return backlogRowHelpParts(row); },
|
|
10113
|
+
},
|
|
10114
|
+
unreachable: {
|
|
10115
|
+
resting: function (row) { return backlogRowHelpParts(row); },
|
|
10116
|
+
},
|
|
10117
|
+
},
|
|
10091
10118
|
github: {
|
|
10092
10119
|
// 🔴 **読めない行の中に、理由に合った一手だけを出す(#1233)。** 全体の帯は撤去した。
|
|
10093
10120
|
// 一手は組織の App の導入状況(githubRowHelpParts)で決まるので、関数で持つ
|
|
@@ -10140,10 +10167,18 @@ ${calendarAddUrl.toString()}
|
|
|
10140
10167
|
var owner = String(row.ref || "").split("/")[0];
|
|
10141
10168
|
var meta = owner ? githubOwnerMeta(owner) : null;
|
|
10142
10169
|
if (meta && meta.source === "unknown") return null;
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10170
|
+
// 🔴 リンクの宛先は、その組織について窓口が答えたアプリ(案件のテナントのアプリ・#1328 T-6)。
|
|
10171
|
+
// 正本(op=state)が答えている画面で slug が無い組織は「分からない」回なので、**宛先を出さず文だけ**にする
|
|
10172
|
+
// (当社のアプリへ落とすと、会社ごとのアプリの会社の画面で別のアプリを入れさせる)。
|
|
10173
|
+
// 当社のアプリの定数へ落とすのは、正本の答えがまだ無い画面(まとめ表だけ)のときだけ
|
|
10174
|
+
var slug = githubAppSlugOf(meta);
|
|
10175
|
+
var linkable = !!slug || !LIVE_GH_OWNERS;
|
|
10176
|
+
var pick = row.gate === "unreachable" && meta && meta.appInstalled === true;
|
|
10177
|
+
var say = pick ? "GitHub App の設定で、このリポジトリを選んでください。" : "この組織に Cortex の GitHub App を入れてください。";
|
|
10178
|
+
if (!linkable) return [say];
|
|
10179
|
+
// 部品には行の生の値を渡す(宛先の検めは githubAppLinkUrl が持つ。ここの slug はリンクを出すかの判断だけ)
|
|
10180
|
+
var raw = meta && meta.appSlug;
|
|
10181
|
+
return [say, pick ? { link: "インストール設定を開く →", kind: "settings", slug: raw } : { link: "GitHub App を入れる →", kind: "install", slug: raw }];
|
|
10147
10182
|
}
|
|
10148
10183
|
/**
|
|
10149
10184
|
* Slack の「理由不明」の行(unreachable・組織不明の仮置き)に出す一手(c-07)。
|
|
@@ -10185,9 +10220,10 @@ ${calendarAddUrl.toString()}
|
|
|
10185
10220
|
var p = el("p", { class: cls });
|
|
10186
10221
|
parts.forEach(function (part) {
|
|
10187
10222
|
if (typeof part === "string") p.appendChild(document.createTextNode(part));
|
|
10188
|
-
else if (part && part.link) p.appendChild(el("a", { href: githubAppLinkUrl(part.kind), target: "_blank", rel: "noopener", text: part.link }));
|
|
10189
|
-
|
|
10190
|
-
else if (part && part.
|
|
10223
|
+
else if (part && part.link) p.appendChild(el("a", { href: githubAppLinkUrl(part.kind, part.slug), target: "_blank", rel: "noopener", text: part.link }));
|
|
10224
|
+
// bare … 文面に値が含まれているので[コピー]だけを出す(固定 IP の依頼文)
|
|
10225
|
+
else if (part && part.copy) p.appendChild(copyInline(part.copy, !part.bare));
|
|
10226
|
+
else if (part && part.act) { var an = actNode(part.act, part.label, part.space); if (an) p.appendChild(an); }
|
|
10191
10227
|
});
|
|
10192
10228
|
return p;
|
|
10193
10229
|
}
|
|
@@ -10195,11 +10231,18 @@ ${calendarAddUrl.toString()}
|
|
|
10195
10231
|
* 行の一手に置くボタン(c-07)。**押せないボタンは作らない**(null)。
|
|
10196
10232
|
* - "connect" … [接続する]。Slack の承認画面へ送る(slackConnectInline)。投入の口が無ければ null
|
|
10197
10233
|
* - "figma-key" … [トークンを入れる]/[トークンを入れ直す]。下の「Figma のトークン」の欄の入力へ移る
|
|
10234
|
+
* - "backlog-key" … [入れる]/[入れ直す]。**そのスペースの**鍵カードを開いて入力へ移る(arg=スペース)。
|
|
10235
|
+
* 受け皿が無いスペースには作らない(別のスペースの欄へ落とさない)
|
|
10236
|
+
* - "backlog-route-on" … [切り替える]。同期経路を固定 IP 経由にする(setBacklogRoute)
|
|
10198
10237
|
* @param label 省略時は既定の語
|
|
10238
|
+
* @param arg 部品が要る材料(backlog-key のスペース)
|
|
10199
10239
|
*/
|
|
10200
|
-
function actNode(act, label) {
|
|
10240
|
+
function actNode(act, label, arg) {
|
|
10201
10241
|
if (!SITE.intake) return null;
|
|
10202
10242
|
if (act === "connect") return slackConnectInline(label || "接続する");
|
|
10243
|
+
// Backlog の行の一手(c-07 の 2 本目)。arg はスペース(ドメイン)。**受け皿が無ければ作らない**
|
|
10244
|
+
if (act === "backlog-key") return backlogKeyJump(arg, label || "入れる");
|
|
10245
|
+
if (act === "backlog-route-on") return backlogRouteOnButton(label || "切り替える");
|
|
10203
10246
|
// 鍵の欄(KEY_SLOTS.figma)が無い構成では、移る先が無いので出さない
|
|
10204
10247
|
if (act === "figma-key") return secretKindsShown().indexOf("figma") >= 0 ? figmaKeyJump(label || "トークンを入れる") : null;
|
|
10205
10248
|
return null;
|
|
@@ -10317,7 +10360,7 @@ ${calendarAddUrl.toString()}
|
|
|
10317
10360
|
*
|
|
10318
10361
|
* **ここを通さずに語を書き写さないこと。** 行バッジ・能力タイルの要約・
|
|
10319
10362
|
* 「接続を確認」の結果が同じ関数を通るので、片方だけ古くなることが構造的に起きない
|
|
10320
|
-
* (attentionSummary
|
|
10363
|
+
* (attentionSummary の項に、実際に割れた経緯がある)。
|
|
10321
10364
|
*/
|
|
10322
10365
|
function dsGateOf(s) {
|
|
10323
10366
|
if (s.gate === "no_token") {
|
|
@@ -11197,7 +11240,7 @@ ${calendarAddUrl.toString()}
|
|
|
11197
11240
|
var again = !!(x.ref && seenRef[x.ref]);
|
|
11198
11241
|
if (x.ref) seenRef[x.ref] = 1;
|
|
11199
11242
|
var rowCard = extSourceCard(x, DS_TYPE[x.type] || { icon: "🔗", label: x.type || "その他" }, { quietHelp: again });
|
|
11200
|
-
// 同じリポの issues / discussions
|
|
11243
|
+
// 同じリポの issues / discussions の行の目印(同じ dev 行から描くので、判定は 1 つの描き直しで揃う)
|
|
11201
11244
|
if (rowCard && rowCard.setAttribute && x.ref) rowCard.setAttribute("data-ref", x.ref);
|
|
11202
11245
|
return rowCard;
|
|
11203
11246
|
});
|
|
@@ -11217,28 +11260,6 @@ ${calendarAddUrl.toString()}
|
|
|
11217
11260
|
return out;
|
|
11218
11261
|
}
|
|
11219
11262
|
|
|
11220
|
-
/**
|
|
11221
|
-
* 「接続を確認」の結果を、同じリポジトリの兄弟の行(issues / discussions)へそろえる。
|
|
11222
|
-
*
|
|
11223
|
-
* 帯は撤去した(#1233)。残すのは「行は 2 本でも接続は 1 つ」なので押した行と同じ判定に
|
|
11224
|
-
* そろえる仕事だけ。**条件なしで毎回そろえる**(帯の有無・状態の変化に依らない)。
|
|
11225
|
-
* 兄弟は同じ親(一覧・確かめられなかった組織の箱)の直下にいる。
|
|
11226
|
-
*/
|
|
11227
|
-
function syncGithubSiblingRows(card, ref, ok) {
|
|
11228
|
-
var parent = card.parentNode;
|
|
11229
|
-
if (!parent || !ref) return;
|
|
11230
|
-
var gateSrc = card.querySelector(".ds-gate");
|
|
11231
|
-
var sibs = parent.querySelectorAll(":scope > .ds-card[data-ref]");
|
|
11232
|
-
for (var si = 0; gateSrc && si < sibs.length; si++) {
|
|
11233
|
-
var sib = sibs[si];
|
|
11234
|
-
if (sib === card || sib.getAttribute("data-ref") !== ref) continue;
|
|
11235
|
-
var sg = sib.querySelector(".ds-gate");
|
|
11236
|
-
if (sg) { sg.className = gateSrc.className; sg.textContent = gateSrc.textContent; }
|
|
11237
|
-
var sh = sib.querySelectorAll(".ds-gate-help");
|
|
11238
|
-
for (var hi = 0; hi < sh.length; hi++) sh[hi].hidden = ok;
|
|
11239
|
-
}
|
|
11240
|
-
}
|
|
11241
|
-
|
|
11242
11263
|
/** 確かめられなかった組織の案内。**導線は出さない** */
|
|
11243
11264
|
function githubUnsureNodes(g) {
|
|
11244
11265
|
var o = g.meta || {};
|
|
@@ -11337,6 +11358,15 @@ ${calendarAddUrl.toString()}
|
|
|
11337
11358
|
if (min < 60 * 24) return Math.floor(min / 60) + "時間前";
|
|
11338
11359
|
return dsFmtTime(iso);
|
|
11339
11360
|
}
|
|
11361
|
+
/**
|
|
11362
|
+
* 「◯分前に確認」の語。**「たった今」だけは「たった今確認」**(「たった今に確認」は日本語として崩れる)。
|
|
11363
|
+
* 読めない値は空文字(呼び側が出し分けを切る)。行・タイル・押した直後の行は全部これを通す
|
|
11364
|
+
*/
|
|
11365
|
+
function agoConfirmedText(iso) {
|
|
11366
|
+
var ago = rtAgo(iso);
|
|
11367
|
+
if (!ago) return "";
|
|
11368
|
+
return ago === "たった今" ? "たった今確認" : ago + "に確認";
|
|
11369
|
+
}
|
|
11340
11370
|
// 名前表示: url があれば新しいタブで正本を開くリンクにする
|
|
11341
11371
|
function dsNameEl(text, url) {
|
|
11342
11372
|
if (url) return el("a", { class: "ds-name", href: url, target: "_blank", rel: "noopener", text: text });
|
|
@@ -11429,11 +11459,19 @@ ${calendarAddUrl.toString()}
|
|
|
11429
11459
|
* **GitHub 側の画面が変わったらここが最初に嘘になる。** 宛先を変えるときは
|
|
11430
11460
|
* 「githubConfigureUrl」(サーバ)と揃えること。
|
|
11431
11461
|
*/
|
|
11432
|
-
function githubAppLinkUrl(kind) {
|
|
11462
|
+
function githubAppLinkUrl(kind, slug) {
|
|
11433
11463
|
// 「kind」: "install"(組織に App を入れる)/"settings"(入っている App の対象リポジトリを選ぶ)。
|
|
11434
|
-
// いまは同じ宛先(App
|
|
11435
|
-
//
|
|
11436
|
-
|
|
11464
|
+
// いまは同じ宛先(App 個別ページ)。**行の案内のリンクは全部ここを通す**(URL を 1 か所から作る)。
|
|
11465
|
+
// 「slug」: その組織について窓口が答えた、案件のテナントのアプリ(会社ごとのアプリ・cortex-engine #1328 T-6)。
|
|
11466
|
+
// 🔴 ここでも形を検める(まとめ表の行は許可リストを通らない)。無ければ当社の読む App(定数)
|
|
11467
|
+
var s = githubAppSlugOf({ appSlug: slug });
|
|
11468
|
+
var url = s ? "https://github.com/apps/" + encodeURIComponent(s) + "/installations/new" : GITHUB_APP_INSTALL_URL;
|
|
11469
|
+
return ({ install: url, settings: url })[kind] || url;
|
|
11470
|
+
}
|
|
11471
|
+
/** 行の slug(形が合わなければ null)。⚠ 埋め込みJSなので、正規表現は逆斜線なしで書く */
|
|
11472
|
+
function githubAppSlugOf(meta) {
|
|
11473
|
+
var v = meta && meta.appSlug;
|
|
11474
|
+
return typeof v === "string" && /^[a-z0-9][a-z0-9-]{0,33}$/.test(v) ? v : null;
|
|
11437
11475
|
}
|
|
11438
11476
|
|
|
11439
11477
|
/**
|
|
@@ -11857,8 +11895,8 @@ ${calendarAddUrl.toString()}
|
|
|
11857
11895
|
var v = derivePendingTile(regKey, live, LIVE_STATE);
|
|
11858
11896
|
if (!v) return null;
|
|
11859
11897
|
if (v.kind === "ok") {
|
|
11860
|
-
var ago =
|
|
11861
|
-
return { text: CAP_STATE_MARK.ok + "接続OK" + (ago ? " " + ago
|
|
11898
|
+
var ago = agoConfirmedText(v.at);
|
|
11899
|
+
return { text: CAP_STATE_MARK.ok + "接続OK" + (ago ? " " + ago : ""), cls: "ds-gate-ok" };
|
|
11862
11900
|
}
|
|
11863
11901
|
var first = v.rows[0];
|
|
11864
11902
|
var gate;
|
|
@@ -12201,13 +12239,33 @@ ${calendarAddUrl.toString()}
|
|
|
12201
12239
|
for (var i = 0; i < KEY_CARDS.length; i++) {
|
|
12202
12240
|
// **対応表に無い種別は隠さない**(keyTargetCount の既定と同じく出す側に倒す)。
|
|
12203
12241
|
// 3つ目の鍵が足されて分岐を書き忘れた場合は key-card-visibility の [全種0件] が落ちる
|
|
12204
|
-
|
|
12242
|
+
var hide = shown[KEY_CARDS[i].kind] === false;
|
|
12243
|
+
// 🔴 **Backlog の行の中のカードは、0 件の判定(上)に加えて「見せる理由があるとき」だけ見せる**
|
|
12244
|
+
//(c-07 の 2 本目)。0 件が勝つ——0 件のあいだは理由があっても出さない(順序の逆な案内になる)
|
|
12245
|
+
if (!hide && KEY_CARDS[i].compact) hide = !backlogKeyCardWanted(KEY_CARDS[i]);
|
|
12246
|
+
KEY_CARDS[i].el.hidden = hide;
|
|
12247
|
+
if (!hide && KEY_CARDS[i].compact && KEY_CARDS[i].refresh) KEY_CARDS[i].refresh();
|
|
12205
12248
|
}
|
|
12206
12249
|
eachKeySlot(function (slot) {
|
|
12207
12250
|
slot.hidden = !slot.querySelector("[data-secret-kind]:not([hidden])");
|
|
12208
12251
|
});
|
|
12209
12252
|
}
|
|
12210
12253
|
|
|
12254
|
+
/**
|
|
12255
|
+
* Backlog の行の中の鍵カードを、いま見せるか(c-07 の 2 本目・スペースごとの判定)。次の 4 つのどれか:
|
|
12256
|
+
* ① キーが要る(backlogKeyNeededForSpace=登録状況が「未登録」・足した直後で答えがまだ無い等)
|
|
12257
|
+
* ② 一手の[入れる]/[入れ直す]・「その他」の[入れ直す]が押された(forced)
|
|
12258
|
+
* ③ 触り始めた(開いた・値がある・結果の文がある)。🔴 **保存直後の「✓ 保存しました」と入力中の値を畳まない**(#223)
|
|
12259
|
+
* 使えているスペースのカードは、ふだん隠れている(DOM には常に在る=入力中の値・結果は消えない)。
|
|
12260
|
+
*/
|
|
12261
|
+
function backlogKeyCardWanted(ent) {
|
|
12262
|
+
if (ent.forced) return true;
|
|
12263
|
+
if (ent.fold && ent.fold.open) return true;
|
|
12264
|
+
if (ent.input && ent.input.value !== "") return true;
|
|
12265
|
+
if (ent.msg && ent.msg.textContent !== "") return true;
|
|
12266
|
+
return backlogKeyNeededForSpace(ent.space);
|
|
12267
|
+
}
|
|
12268
|
+
|
|
12211
12269
|
/** いま在る鍵の受け皿(.ds-keyrow)を順に渡す。置き場の正本は KEY_SLOTS */
|
|
12212
12270
|
function eachKeySlot(fn) {
|
|
12213
12271
|
var slots = KEY_SLOTS.backlog || {};
|
|
@@ -12548,6 +12606,10 @@ ${calendarAddUrl.toString()}
|
|
|
12548
12606
|
// 🔴 **Google Drive 専用の説明文(c-07・新設)。** SharePoint 等の別のアダプタの窓には出さない
|
|
12549
12607
|
// (Markdown 化の約束は Drive の取り込みの話)。追加の欄の手順 3 が持っていた文
|
|
12550
12608
|
m.modal.appendChild(el("p", { class: "modal-desc", text: "ここに並んでいるフォルダの中身を、毎晩 共有資料 にコピーして Markdown 化します。やめるには「このフォルダを外す」を押してください。" }));
|
|
12609
|
+
} else if (cap === "課題管理" && (!adapter || adapter === "backlog")) {
|
|
12610
|
+
// 🔴 **Backlog の説明文(c-07 の 2 本目・新設)。** 追加の欄の説明から外した 3 つを、ここに 1 回だけ置く:
|
|
12611
|
+
// 何が同期されるか・起票先は変わらないこと(2026-08-20 に追加の確認をやめたとき追加の欄へ移した文)・止め方
|
|
12612
|
+
m.modal.appendChild(el("p", { class: "modal-desc", text: "ここに並んでいる Backlog プロジェクトの課題・ドキュメント・Wiki を毎晩同期します。接続先を足しても、ビューアからの起票先は変わりません。やめるには「この接続先を外す」を押してください。" }));
|
|
12551
12613
|
} else if (cap === "開発") {
|
|
12552
12614
|
// 🔴 **開発の開示文は「記録に使う・決定の抽出には使わない」(#660)。** 2026-09-18 の
|
|
12553
12615
|
// オーナー決定でソースの性質は種類で固定になり、GitHub は「作業の記録」に固定された。
|
|
@@ -12587,16 +12649,17 @@ ${calendarAddUrl.toString()}
|
|
|
12587
12649
|
? ""
|
|
12588
12650
|
: (CAP_STATE_TEXT[dNow] || "状態が判定できません") + "(" + detailNow + ")";
|
|
12589
12651
|
}
|
|
12590
|
-
// 🔴 **Figma・Drive の「未接続(…が登録されていません)」は作らない(c-07)。** 0 件のとき、
|
|
12652
|
+
// 🔴 **Figma・Drive・Backlog の「未接続(…が登録されていません)」は作らない(c-07)。** 0 件のとき、
|
|
12591
12653
|
// 同じ窓に一覧の ds-empty「…はまだ登録されていません。」が出ていて二重だった。しかも一覧は
|
|
12592
12654
|
// liveList で追加に追随するが、この一文は開いた時点の値なので、1 件足すと「未接続」が足した行の
|
|
12593
|
-
// 上に残る。**一覧の 1 行だけ**を残す。off・broken の一文は残す(
|
|
12655
|
+
// 上に残る。**一覧の 1 行だけ**を残す。off・broken の一文は残す(Slack・GitHub は触らない)。
|
|
12594
12656
|
// 🔴 **「登録されていません」と言うだけの一文だけを落とす。** 「雛形のプレースホルダです」のように、
|
|
12595
12657
|
// 一覧の 1 行が言わない手がかりを運ぶ一文は残す(なぜ未接続扱いなのかが読めなくなる)
|
|
12596
12658
|
var unsetDup = noteIsUnset
|
|
12597
12659
|
&& /登録されていません/.test(noteText)
|
|
12598
12660
|
&& ((cap === "デザイン" && (!adapter || adapter === "figma"))
|
|
12599
|
-
|| (cap === "共有資料" && (!adapter || adapter === "google-drive"))
|
|
12661
|
+
|| (cap === "共有資料" && (!adapter || adapter === "google-drive"))
|
|
12662
|
+
|| (cap === "課題管理" && (!adapter || adapter === "backlog")));
|
|
12600
12663
|
if (!unsetDup) {
|
|
12601
12664
|
var noteEl = el("p", { class: "modal-desc", text: noteText });
|
|
12602
12665
|
// **消さずに hidden で伏せる。** 節点を残しておかないと、この一文を探している
|
|
@@ -12706,6 +12769,8 @@ ${calendarAddUrl.toString()}
|
|
|
12706
12769
|
KEY_SLOTS = { backlog: {}, backlogNamed: false, figma: null };
|
|
12707
12770
|
// 鍵カードも受け皿と同じく作り直す(前回の切り離されたカードを掴んだままにしない)
|
|
12708
12771
|
KEY_CARDS = [];
|
|
12772
|
+
// Backlog の節の行の控え・結果の帯・描き直しの口も作り直す(切り離された古い節を掴んだままにしない)
|
|
12773
|
+
BACKLOG_UI = { rows: [], msg: null, repaintRows: null };
|
|
12709
12774
|
// 作り直しはスナップショットから描き直すこと。応答で採った件数はここで捨てる
|
|
12710
12775
|
ADOPTED_TARGET_COUNT = null;
|
|
12711
12776
|
var internals = fleetVal("internalSources") || [];
|
|
@@ -13908,34 +13973,26 @@ ${calendarAddUrl.toString()}
|
|
|
13908
13973
|
* DS_GATE_NO_TOKEN が持っていて、どちらも dsGateOf を通して取る。ここで文面を
|
|
13909
13974
|
* 書き写すと、**同じ行の状態バッジと確認結果で語が割れる**(この節で3回直した形)。
|
|
13910
13975
|
*
|
|
13976
|
+
* **結果の描画は行の関数 1 本(cortex-engine #1328)。** 押した結果は applyProbeResult が
|
|
13977
|
+
* ストアの行と同じ形で LIVE_STATE に置き、行を描き直す。押下の処理は札・時刻・説明文を
|
|
13978
|
+
* 自分で描かない(以前の probeHelpNodes / probeVerdict は撤去した。開き直した行と形が割れ、
|
|
13979
|
+
* 押した直後に 2 回見た目が変わっていた)。
|
|
13980
|
+
*
|
|
13911
13981
|
* 他アダプタ(Backlog / Figma / Drive)を足すときの拡張点:
|
|
13912
13982
|
* - 判定と語彙は infra/lambda/shared/slack-gate.mjs 側に足す(画面は DS_GATE のまま)
|
|
13913
|
-
* -
|
|
13914
|
-
*
|
|
13915
|
-
*
|
|
13983
|
+
* - **次の一手は DS_GATE の help(行の常時表示と同じ表)に足す。**この節に判定の分岐を作らない
|
|
13984
|
+
* - 送る種別(probeTypeOf)・ストアの能力名(probeCapOfType)と、行に出す条件
|
|
13985
|
+
* (下の extSourceCard)に足す
|
|
13916
13986
|
*
|
|
13917
13987
|
* **開発(GitHub)は #234 で足した。** 判定は shared/github-gate.mjs、次の一手は
|
|
13918
13988
|
* サーバの needed(理由と設定ページのURLを1文で持つ)をそのまま出している
|
|
13919
13989
|
* ——画面に分岐が生まれないよう、言い分けを全部あちらに置いた
|
|
13920
13990
|
*
|
|
13921
13991
|
* **Google Chat は #347 で足した。** 判定は shared/google-chat-gate.mjs で、夜間の実測と
|
|
13922
|
-
*
|
|
13992
|
+
* 同じ叩き先・同じスコープで測る。次の一手は行の常時表示と
|
|
13923
13993
|
* 同じ表(DS_GATE の help)から引くだけで、画面に判定の分岐は無い
|
|
13924
13994
|
* ------------------------------------------------------------------------- */
|
|
13925
13995
|
|
|
13926
|
-
/**
|
|
13927
|
-
* 確認結果の状態バッジ。**既存の dsGateOf をそのまま通す。**
|
|
13928
|
-
*
|
|
13929
|
-
* こうしておくと no_token の社内/顧客の出し分け(DS_GATE_NO_TOKEN)が確認結果にも
|
|
13930
|
-
* そのまま効く。写経すると、同じ画面で行は「管理者の設定が必要です」・確認結果は
|
|
13931
|
-
* 「トークン未設定」になり、社内チャンネルを登録した人が**また**顧客ワークスペースの
|
|
13932
|
-
* 接続へ吸い込まれる(2026-08-18 の不具合そのもの)。
|
|
13933
|
-
*/
|
|
13934
|
-
function probeVerdict(s, r) {
|
|
13935
|
-
// reason は GitHub のテナント外の断り(#841)。無ければ従来どおり
|
|
13936
|
-
return dsGateOf({ type: s.type, workspace: s.workspace, gate: r.gate, reason: r.reason });
|
|
13937
|
-
}
|
|
13938
|
-
|
|
13939
13996
|
/**
|
|
13940
13997
|
* 確認したいアダプタの種別(「op=probe」 の 「type」)。**画面の種別をそのまま送らない**
|
|
13941
13998
|
* ——「github-issues」 / 「github-discussions」 は同じ1つのリポジトリの読み取りで、
|
|
@@ -13973,179 +14030,6 @@ ${calendarAddUrl.toString()}
|
|
|
13973
14030
|
return p;
|
|
13974
14031
|
}
|
|
13975
14032
|
|
|
13976
|
-
/**
|
|
13977
|
-
* 開発(GitHub)の確認結果の本文(#233 / #234)。
|
|
13978
|
-
*
|
|
13979
|
-
* **理由と対処はサーバが1文で持っている**(「shared/github-gate.mjs」 の 「needed」)。
|
|
13980
|
-
* 「App が入っていない」と「App は入っているがこのリポジトリが選ばれていない」の
|
|
13981
|
-
* 言い分けも、設定ページの URL もあちらが決める——**画面で分岐を書き足さない**
|
|
13982
|
-
* (書いた瞬間に判定が2箇所になり、夜間の実測とボタンでずれる)。
|
|
13983
|
-
* **ただし状態に応じた定型の1文だけは画面の表(DS_GATE_RESTING_HELP の probed)から引く**
|
|
13984
|
-
* (#346 → #356)——「request は組織オーナーの承認待ち」のような GitHub 側の意味は、
|
|
13985
|
-
* サーバの2つの書き手が別々に文を作るので、片方だけ古くなる。
|
|
13986
|
-
*
|
|
13987
|
-
* 🔴 **行の常時表示(resting)を押した結果に繰り返さない(#356)。** 0.12.22 では admin の
|
|
13988
|
-
* 4文が行と押した結果の両方に出ていた。行の1文は押した瞬間に隠れ、needed が同じ操作
|
|
13989
|
-
* (Configure から追加)を宛先つきで言うので、ここは needed が言わない1文だけ
|
|
13990
|
-
*/
|
|
13991
|
-
function githubProbeHelpNodes(r) {
|
|
13992
|
-
if (r.gate === "ok") return [];
|
|
13993
|
-
if (r.needed) {
|
|
13994
|
-
var nodes = [neededNode(r.needed)];
|
|
13995
|
-
// 状態に応じた1文は**行と同じ表から引く**(ここに文を直書きすると表と割れる)
|
|
13996
|
-
var probed = probedHelpPartsOf("github", r.gate);
|
|
13997
|
-
if (probed) nodes.push(restingHelpNode(probed, "ds-probe-help"));
|
|
13998
|
-
return nodes;
|
|
13999
|
-
}
|
|
14000
|
-
// needed が無いのは gate に落とし込めなかったとき。**生のエラーは隠さない**
|
|
14001
|
-
return [el("p", { class: "ds-probe-help", text: "GitHub の応答:" + (r.error || "不明") + "。時間をおいて再度お試しください。" })];
|
|
14002
|
-
}
|
|
14003
|
-
|
|
14004
|
-
/**
|
|
14005
|
-
* Google Chat の確認結果の本文(#347)。
|
|
14006
|
-
*
|
|
14007
|
-
* **判定は infra/lambda/shared/google-chat-gate.mjs にしか無い。** ここが返すのは
|
|
14008
|
-
* 「次の一手」だけで、状態の語(バッジ)は probeVerdict が dsGateOf から取る。
|
|
14009
|
-
*
|
|
14010
|
-
* **文を書き写さない。** 「アプリと統合から追加してください」「管理者の設定が済むと…」は
|
|
14011
|
-
* 行の常時表示(DS_GATE の 「help」)が既に持っている——写経すると、同じ行の常時表示と
|
|
14012
|
-
* 押した結果で語が割れる(この節が何度も直してきた形)。DS_GATE から引いて1文だけ出す。
|
|
14013
|
-
*
|
|
14014
|
-
* ⚠ **403(Workspace 管理者の承認待ち)は #521 で「admin_approval_pending」に分類済み。**
|
|
14015
|
-
* 機械語(「permission_denied」)はもう出さない。**「時間をおいて」とも言わない**
|
|
14016
|
-
* ——待っても直らない(管理者が承認するまで同じ応答が返る)。401 / 429 は依然
|
|
14017
|
-
* Phase 0 未採取で「unknown」のまま、サーバの機械語をそのまま出す。
|
|
14018
|
-
*/
|
|
14019
|
-
function googleChatProbeHelpNodes(s, r) {
|
|
14020
|
-
// 成功。**1行で終わり**(状態の語が既に「接続OK」と出ている)
|
|
14021
|
-
if (r.gate === "ok") return [];
|
|
14022
|
-
// not_in_space / no_credentials / admin_approval_pending は行の常時表示と同じ文面
|
|
14023
|
-
// (DS_GATE から引く。依頼文の details も gateHelpNodes 経由で同じものが出る)。
|
|
14024
|
-
// 押した瞬間に行側の 「.ds-gate-help」 は隠れるので、二重には出ない
|
|
14025
|
-
var gate = dsGateOf({ type: s.type, gate: r.gate });
|
|
14026
|
-
var nodes = gateHelpNodes(gate, "ds-probe-help");
|
|
14027
|
-
if (nodes.length) return nodes;
|
|
14028
|
-
var p = function (t) { return el("p", { class: "ds-probe-help", text: t }); };
|
|
14029
|
-
if (r.gate === "unreachable") {
|
|
14030
|
-
// 帯(「アクセス不可」)が症状を言っている。**ここは一手だけ**
|
|
14031
|
-
return [p("時間をおいて、もう一度「接続を確認」を押してください。")];
|
|
14032
|
-
}
|
|
14033
|
-
// unknown。**生の機械語を隠さない**——「不明」だけだと切り分けが始められない
|
|
14034
|
-
return [p("Google Chat の応答:" + (r.error || "不明") + "。cortex の担当者にこの語をお伝えください。")];
|
|
14035
|
-
}
|
|
14036
|
-
|
|
14037
|
-
/**
|
|
14038
|
-
* 「アクセス不可」の次の一手(0.12.2・実オンボーディングで踏んだ)。
|
|
14039
|
-
*
|
|
14040
|
-
* ## なぜ2つの導線を併記するのか
|
|
14041
|
-
*
|
|
14042
|
-
* それまでは「見つからないか、アーカイブされています。不要なら登録を解除」だけを
|
|
14043
|
-
* 出していた。**この行がまさに、まだ読めていない主役の行**なのに、
|
|
14044
|
-
* 生きているチャンネルを読めるようにする道が1本も書かれていなかった。
|
|
14045
|
-
*
|
|
14046
|
-
* 原因は**行の分類では絞れない**:
|
|
14047
|
-
*
|
|
14048
|
-
* - 非公開チャンネルは、招待されるまで「存在しない」と返る(招待で直る)
|
|
14049
|
-
* - Slack コネクトの共有チャンネルは、**URL がどちらの組織のドメインにもなりうる**。
|
|
14050
|
-
* だから「クラスメソッド」に分類された行の正しい対処が
|
|
14051
|
-
* **ワークスペース接続(OAuth)** のこともある
|
|
14052
|
-
* (実測: 招待しようとしたら「組織外のユーザー」で断られた)
|
|
14053
|
-
*
|
|
14054
|
-
* **どちらかに賭けて片方だけ出すと、外したほうの人は詰まる。** 招待だけを出すと
|
|
14055
|
-
* 何度招待しても直らないものを繰り返させ、接続だけを出すと招待で済む人に
|
|
14056
|
-
* 「直らない処方」を渡す。**両方出して、読む人に選ばせる**のが正しい
|
|
14057
|
-
* (組織不明の案内が同じ判断をしている)。
|
|
14058
|
-
*
|
|
14059
|
-
* ## 社内/顧客で出し分けない
|
|
14060
|
-
*
|
|
14061
|
-
* no_token は出し分けている(社内は画面から設定できるものが本当に無い)が、
|
|
14062
|
-
* こちらは**分類そのものが当てにならない**のが問題の本体なので、
|
|
14063
|
-
* 同じ理屈を持ち込むと直したい誤読を画面の側から作り直すことになる。
|
|
14064
|
-
*/
|
|
14065
|
-
function slackUnreachableHelpNodes() {
|
|
14066
|
-
var out = [];
|
|
14067
|
-
out.push(el("p", { class: "ds-probe-help", text: "Cortex からこのチャンネルが見えませんでした。次のどれかです。" }));
|
|
14068
|
-
var ul = el("ul", { class: "ds-probe-help ds-probe-why" });
|
|
14069
|
-
// **押したあと何をするかまで書く。** 直したあと表示が変わらないと「効かなかった」と
|
|
14070
|
-
// 読まれる——この画面の一覧は夜間のスナップショットなので、その場で確かめる手段は
|
|
14071
|
-
// 「接続を確認」をもう一度押すこと以外に無い
|
|
14072
|
-
ul.appendChild(el("li", {
|
|
14073
|
-
text: "非公開チャンネル … 下のコマンドをそのチャンネルに貼って Cortex を招待してから、もう一度「接続を確認」を押してください",
|
|
14074
|
-
}));
|
|
14075
|
-
// 🔴 **指す先の常設ボタンは撤去した(c-07)ので、同じ段にインラインの[接続する]を置く**
|
|
14076
|
-
var connectLi = el("li", {
|
|
14077
|
-
text: "お客様の Slack、または Slack コネクトで共有されたチャンネル … "
|
|
14078
|
-
+ "[接続する]から、そのワークスペースに Cortex を追加するリクエストを送ってください。"
|
|
14079
|
-
+ "接続できたら、もう一度「接続を確認」を押してください",
|
|
14080
|
-
});
|
|
14081
|
-
var connectBtn = actNode("connect");
|
|
14082
|
-
if (connectBtn) connectLi.appendChild(connectBtn);
|
|
14083
|
-
ul.appendChild(connectLi);
|
|
14084
|
-
ul.appendChild(el("li", {
|
|
14085
|
-
text: "アーカイブ・削除された、URL が違う … 「登録を解除」を押してください",
|
|
14086
|
-
}));
|
|
14087
|
-
out.push(ul);
|
|
14088
|
-
// **手で打たせない**(copyRow のコメント)。招待の候補を出したなら、
|
|
14089
|
-
// そのコマンドも同じ場所に無いと、貼る文字列を探しに行くことになる
|
|
14090
|
-
out.push(copyRow("/invite @Cortex"));
|
|
14091
|
-
return out;
|
|
14092
|
-
}
|
|
14093
|
-
|
|
14094
|
-
/**
|
|
14095
|
-
* 判定ごとの表示。**成功は1行で終わらせ、失敗は「理由+次の一手」だけを残す。**
|
|
14096
|
-
*
|
|
14097
|
-
* ## 短くした理由(2026-08-19・オーナー指示)
|
|
14098
|
-
*
|
|
14099
|
-
* 「接続を確認を押して出てくる文言が長ったらしい」。実際、成功時に4行出ていた。
|
|
14100
|
-
* **成功時は「読めている」以上の情報が要らない。**
|
|
14101
|
-
*
|
|
14102
|
-
* ## ただし失敗時は削りすぎない
|
|
14103
|
-
*
|
|
14104
|
-
* 短くしすぎると、今日と同じ「画面を見ても次に何をすればいいか決まらない」状態に戻る
|
|
14105
|
-
* (それで半日かかった)。落とすのは**内部の仕組みの説明**——「設定ファイルの表示名だけが
|
|
14106
|
-
* 書き換わります」「読み取りの対象・範囲は変わりません」のような、読む人の次の行動を
|
|
14107
|
-
* 変えない文。残すのは「何が起きているか」と「次に何をするか」だけ。
|
|
14108
|
-
*
|
|
14109
|
-
* 状態の語そのものは DS_GATE / DS_GATE_NO_TOKEN が持つ(probeVerdict 経由)ので、
|
|
14110
|
-
* ここが返すのは**それに足りない分だけ**。二重に言わない。
|
|
14111
|
-
*
|
|
14112
|
-
* @returns 本文に並べるノード(0〜2個)
|
|
14113
|
-
*/
|
|
14114
|
-
function probeHelpNodes(s, r) {
|
|
14115
|
-
var p = function (t) { return el("p", { class: "ds-probe-help", text: t }); };
|
|
14116
|
-
if (probeTypeOf(s.type) === "github") return githubProbeHelpNodes(r);
|
|
14117
|
-
if (probeTypeOf(s.type) === "google-chat") return googleChatProbeHelpNodes(s, r);
|
|
14118
|
-
// 成功。**1行で終わり。** 状態の語(「接続OK」)が既に出ているので本文は空
|
|
14119
|
-
if (r.gate === "ok") return [];
|
|
14120
|
-
if (r.gate === "not_in_channel") {
|
|
14121
|
-
// 状態の語が「Slackで招待してください」なので、説明は足さずコマンドだけ出す。
|
|
14122
|
-
// **手で打たせない**——打ち間違いは黙って失敗し、「招待したのに読まれない」に見える
|
|
14123
|
-
return [copyRow("/invite @Cortex")];
|
|
14124
|
-
}
|
|
14125
|
-
if (r.gate === "missing_scope") {
|
|
14126
|
-
// 不足スコープは頭のバッジ横に出る(probeSection)。ここは次の一手だけ
|
|
14127
|
-
return [p("招待では直りません。cortex の担当者にこのスコープ名をお伝えください。")];
|
|
14128
|
-
}
|
|
14129
|
-
if (r.gate === "no_token") {
|
|
14130
|
-
// **社内/顧客の出し分けは変えない。** 社内は画面から設定できるものが無いので
|
|
14131
|
-
// 連絡先、顧客は接続へ誘導する(2026-08-18 の不具合の再発防止)。
|
|
14132
|
-
// 長い説明は落とし、次の行動の1行だけ残す
|
|
14133
|
-
if (isInternalSlack(s)) return [p("cortex の担当者にご連絡ください。")];
|
|
14134
|
-
// 常設のボタンは撤去した(c-07)。同じ段にインラインの[接続する]を置く
|
|
14135
|
-
var connectP = p("この Slack を Cortex に接続してください。");
|
|
14136
|
-
var connectNow = actNode("connect");
|
|
14137
|
-
if (connectNow) connectP.appendChild(connectNow);
|
|
14138
|
-
return [connectP];
|
|
14139
|
-
}
|
|
14140
|
-
if (r.gate === "unreachable") {
|
|
14141
|
-
// **ここだけ本文が2つ以上になる(0.12.2)。** 原因が3つあり、しかも
|
|
14142
|
-
// 行の分類では絞れないので、候補と導線を並べる(関数側に理由がある)
|
|
14143
|
-
return slackUnreachableHelpNodes();
|
|
14144
|
-
}
|
|
14145
|
-
// unknown。**生のエラーは隠さない**——「不明」だけだと切り分けが始められない
|
|
14146
|
-
return [p("Slack の応答:" + (r.error || "不明") + "。時間をおいて再度お試しください。")];
|
|
14147
|
-
}
|
|
14148
|
-
|
|
14149
14033
|
/**
|
|
14150
14034
|
* 表示名を Slack の実名へ**自動で揃える**(2026-08-19・オーナー判断)。
|
|
14151
14035
|
*
|
|
@@ -14188,6 +14072,9 @@ ${calendarAddUrl.toString()}
|
|
|
14188
14072
|
// 一覧は夜間のスナップショットなので、その場では変わらない。行だけ追いつかせる
|
|
14189
14073
|
var nameEl = card.querySelector(".ds-name");
|
|
14190
14074
|
if (nameEl) nameEl.textContent = slackDisplayName(r.name);
|
|
14075
|
+
// 押した結果で行が描き直されていると、上の card は切り離されている。仮置き(notePendingOp)から描き直す
|
|
14076
|
+
repaintOpenLists();
|
|
14077
|
+
refreshDataSources();
|
|
14191
14078
|
}).catch(function () { /* 揃えられなくても疎通確認の結果は出したまま */ });
|
|
14192
14079
|
}
|
|
14193
14080
|
|
|
@@ -14214,32 +14101,25 @@ ${calendarAddUrl.toString()}
|
|
|
14214
14101
|
*
|
|
14215
14102
|
* @returns { btn, detail } btn は行に、detail は行の下に置く
|
|
14216
14103
|
*/
|
|
14217
|
-
function probeSection(s, card
|
|
14104
|
+
function probeSection(s, card) {
|
|
14218
14105
|
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "接続を確認" });
|
|
14219
14106
|
var wrap = el("div", { class: "ds-probe" });
|
|
14220
14107
|
var head = el("div", { class: "ds-probe-head" });
|
|
14108
|
+
// 失敗の 1 文だけをここへ出す。**結果(判定・時刻・次の一手)は行そのものが持つ**
|
|
14221
14109
|
var msg = el("span", { class: "ds-toggle-msg" });
|
|
14222
|
-
var at = el("span", { class: "ds-probe-at" });
|
|
14223
|
-
var body = el("div");
|
|
14224
14110
|
head.appendChild(msg);
|
|
14225
|
-
head.appendChild(at);
|
|
14226
14111
|
wrap.appendChild(head);
|
|
14227
|
-
wrap.appendChild(body);
|
|
14228
14112
|
|
|
14229
14113
|
btn.addEventListener("click", async function () {
|
|
14230
14114
|
var intake = SITE.intake;
|
|
14231
14115
|
if (!intake) return;
|
|
14232
|
-
//
|
|
14233
|
-
//
|
|
14116
|
+
// 押している間は**ボタンだけ**が「確認中…」になる(#222・beginBusy の項)。
|
|
14117
|
+
// 🔴 行の中身は変えない——ここで札・時刻・説明文を別の形で描くと、結果が出たあとの
|
|
14118
|
+
// 行(開き直したときと同じ形)と 2 回見た目が変わる(cortex-engine #1328 のちらつき)
|
|
14234
14119
|
var endBusy = beginBusy(btn, "確認中…");
|
|
14235
|
-
|
|
14236
|
-
// それまでは中身が空でも点線だけが全行に出ており、**何も意味していなかった**。
|
|
14237
|
-
// 押した結果があるときだけ、行と結果を分ける線になる
|
|
14238
|
-
wrap.className = "ds-probe is-shown";
|
|
14120
|
+
wrap.className = "ds-probe";
|
|
14239
14121
|
msg.className = "ds-toggle-msg";
|
|
14240
|
-
msg.textContent = "
|
|
14241
|
-
at.textContent = "";
|
|
14242
|
-
body.textContent = "";
|
|
14122
|
+
msg.textContent = "";
|
|
14243
14123
|
// **種別はサーバの語彙に畳む**(github-issues / github-discussions → github)。
|
|
14244
14124
|
// ワークスペースは Slack にしか無い概念なので、他アダプタでは載せない
|
|
14245
14125
|
var payload = { type: probeTypeOf(s.type), ref: s.ref };
|
|
@@ -14249,69 +14129,31 @@ ${calendarAddUrl.toString()}
|
|
|
14249
14129
|
var j = await res.json().catch(function () { return {}; });
|
|
14250
14130
|
if (!res.ok) {
|
|
14251
14131
|
// サーバの文言をそのまま出す(回数制限の案内・未対応のアダプタの説明を含んでいる)
|
|
14132
|
+
wrap.className = "ds-probe is-shown";
|
|
14252
14133
|
msg.className = "ds-toggle-msg is-error";
|
|
14253
14134
|
msg.textContent = "✗ " + (j.message || "確認できませんでした。");
|
|
14254
14135
|
endBusy();
|
|
14255
14136
|
return;
|
|
14256
14137
|
}
|
|
14257
|
-
msg.className = "ds-toggle-msg" + (j.gate === "ok" ? " is-ok" : " is-error");
|
|
14258
|
-
msg.textContent = j.gate === "ok" ? "✓" : "✗";
|
|
14259
|
-
var v = probeVerdict(s, j);
|
|
14260
|
-
gateEl.className = "ds-gate " + v.cls;
|
|
14261
|
-
gateEl.textContent = v.text;
|
|
14262
|
-
// 🔴 **「活動を記録中」の札も、押した結果で出し入れする(#660)。** ここを忘れると、
|
|
14263
|
-
// アクセス不可の行を直して押したときに**行は「接続OK」なのに札だけ消えたまま**に
|
|
14264
|
-
// なり、「決定の材料になっている」と読める(逆向きも同じで、届かなくなった行が
|
|
14265
|
-
// 「記録中」と言い続ける)。**判定は 「devExcludedBadgeVisible」 1 本**——
|
|
14266
|
-
// 描画時と押した後で別の条件を書くと、同じ行が 2 つのことを言う
|
|
14267
|
-
var exBadgeNow = card.querySelector(".badge.ds-not-ingested");
|
|
14268
|
-
if (exBadgeNow) {
|
|
14269
|
-
exBadgeNow.hidden = !devExcludedBadgeVisible({
|
|
14270
|
-
ingestExcluded: s.ingestExcluded, goldState: s.goldState, gate: j.gate,
|
|
14271
|
-
});
|
|
14272
|
-
}
|
|
14273
|
-
// 古い「X分前に確認」を残さない(下に出るいまの結果と、時刻が2つ並ぶ)
|
|
14274
|
-
var oldAt = card.querySelector(".ds-gate-at");
|
|
14275
|
-
if (oldAt) oldAt.remove();
|
|
14276
|
-
// 古い run の導線も残さない(#560)。「いま測った結果」の横に夜間の run のログが並ぶ形になる
|
|
14277
|
-
removeRunTrail(card);
|
|
14278
|
-
// 🔴 **古いスコープ名の札も必ず消してから出し直す。** 行には夜間の判定に添えた
|
|
14279
|
-
// 札が出ていることがあり(段B)、置き換えた「接続OK」の横に古いスコープ名が
|
|
14280
|
-
// 残ると、同じ行が2つのことを言う——この節が何度も直してきた形そのもの。
|
|
14281
|
-
// 押し直すたびに札が増えていくのも、まとめてここで断つ
|
|
14282
|
-
var oldScopes = card.querySelectorAll(".ds-probe-scope");
|
|
14283
|
-
for (var oi = 0; oi < oldScopes.length; oi++) oldScopes[oi].remove();
|
|
14284
|
-
// 不足スコープは**状態の語のすぐ横**に出す(「権限が足りません:groups:read」)。
|
|
14285
|
-
// 本文へ落とすと、いちばん要る情報が説明文に埋もれる。
|
|
14286
|
-
// ⚠ **札にするのは Slack のスコープ名だけ。** GitHub の needed は URL 込みの
|
|
14287
|
-
// 一文なので、札に押し込むとバッジの横で折り返して読めない(本文へ出す)
|
|
14288
|
-
if (j.gate === "missing_scope" && probeTypeOf(s.type) === "slack") {
|
|
14289
|
-
head.appendChild(el("code", { class: "ds-probe-scope", text: j.needed || "不明" }));
|
|
14290
|
-
}
|
|
14291
|
-
at.textContent = "いま測った結果(" + (dsFmtTime(j.checkedAt) || "たった今") + ")";
|
|
14292
|
-
probeHelpNodes(s, j).forEach(function (n) { body.appendChild(n); });
|
|
14293
|
-
// **夜間の判定に添えてあった案内は隠す。** 置き換えた判定と食い違う案内が
|
|
14294
|
-
// 下に残ると、同じ行に2つのことが書いてある状態になる。
|
|
14295
|
-
// **1つ目だけでなく全部隠す(#346)。** 理由文(.ds-gate-reason)・定型補足
|
|
14296
|
-
// (.ds-gate-resting)・DS_GATE の help のどれが付いていても、押した結果と
|
|
14297
|
-
// 食い違う案内を残さない
|
|
14298
|
-
var stale = card.querySelectorAll(".ds-gate-help");
|
|
14299
|
-
for (var si = 0; si < stale.length; si++) stale[si].hidden = true;
|
|
14300
14138
|
// 表示名を Slack の実名へ自動で揃える(差分表示も更新ボタンも出さない)。
|
|
14301
14139
|
// **GitHub は揃えない。** 行の名前は owner/repo そのもので、書き換える口
|
|
14302
14140
|
//(op=config の refresh-channel-name)は channels.json 専用
|
|
14303
14141
|
if (s.type === "slack") autoAlignName(s, j, card);
|
|
14304
|
-
//
|
|
14305
|
-
//
|
|
14306
|
-
//
|
|
14307
|
-
//
|
|
14308
|
-
|
|
14309
|
-
|
|
14310
|
-
|
|
14311
|
-
|
|
14312
|
-
|
|
14313
|
-
|
|
14142
|
+
// 🔴 **結果は、ストアの行と同じ形で LIVE_STATE に置いて、行を描き直す**(applyProbeResult)。
|
|
14143
|
+
// 判定・時刻・「活動を記録中」の札・スコープ名の札・次の一手・兄弟の行(issues /
|
|
14144
|
+
// discussions)の判定は、全部この描き直しが決める。ここで DOM を別の形で書き足すと、
|
|
14145
|
+
// 開き直した行と形が割れる(#1328)
|
|
14146
|
+
try {
|
|
14147
|
+
applyProbeResult(probeCapOfType(s.type), s.ref, s.workspace || "", {
|
|
14148
|
+
gate: j.gate,
|
|
14149
|
+
checkedAt: j.checkedAt,
|
|
14150
|
+
needed: j.needed || "",
|
|
14151
|
+
// サーバがストアへ書く値と同じ(op=probe の保存側の項)
|
|
14152
|
+
detail: j.reason || j.error || (j.gate === "ok" ? "" : j.gate),
|
|
14153
|
+
});
|
|
14154
|
+
} catch (drawErr) { /* 描き直しの不調を「通信に失敗」と言わない(測れた事実は取れている)。続く読み直しに任せる */ }
|
|
14314
14155
|
} catch (e) {
|
|
14156
|
+
wrap.className = "ds-probe is-shown";
|
|
14315
14157
|
msg.className = "ds-toggle-msg is-error";
|
|
14316
14158
|
msg.textContent = "✗ 通信に失敗しました。";
|
|
14317
14159
|
}
|
|
@@ -14320,6 +14162,84 @@ ${calendarAddUrl.toString()}
|
|
|
14320
14162
|
return { btn: btn, detail: wrap };
|
|
14321
14163
|
}
|
|
14322
14164
|
|
|
14165
|
+
/**
|
|
14166
|
+
* 押した直後の結果を、**接続状態ストアの行と同じ形**で LIVE_STATE に置き、一覧を描き直す。
|
|
14167
|
+
*
|
|
14168
|
+
* 🔴 **行の見た目を決める関数は、開き直したときと同じ 1 本(extSourceCard など)。** 押下の処理は
|
|
14169
|
+
* 札・時刻・説明文を自分で描かない。以前は押下が「いま測った結果」の枠を先に描き、直後の
|
|
14170
|
+
* reloadLiveState で行がいつもの形に描き直されて、2 回見た目が変わっていた(cortex-engine #1328)。
|
|
14171
|
+
*
|
|
14172
|
+
* 形はサーバが書く行(by が button の行)と同じに揃える。続く reloadLiveState が本物の行で
|
|
14173
|
+
* 置き換えても DOM は変わらない。**語彙に無い値は行ごと落とす**(sanitizeStateNow)——
|
|
14174
|
+
* その場合は描き直しの代わりに reloadLiveState に任せる。
|
|
14175
|
+
*
|
|
14176
|
+
* @param capId 接続状態ストアの能力名(chat / google-chat / dev / issues / materials / design)
|
|
14177
|
+
* @param r { gate, checkedAt, needed, detail }
|
|
14178
|
+
*/
|
|
14179
|
+
function applyProbeResult(capId, ref, workspace, r) {
|
|
14180
|
+
var row = {
|
|
14181
|
+
cap: capId, question: "reachable", ref: String(ref), status: String(r.gate || ""),
|
|
14182
|
+
at: r.checkedAt ? String(r.checkedAt) : new Date().toISOString(),
|
|
14183
|
+
by: "button", needed: String(r.needed || ""), detail: String(r.detail || ""),
|
|
14184
|
+
};
|
|
14185
|
+
if (workspace) row.workspace = String(workspace);
|
|
14186
|
+
var want = stateRefKey(capId, ref, workspace);
|
|
14187
|
+
var next = (LIVE_STATE || []).filter(function (x) {
|
|
14188
|
+
return !(x && x.cap === capId && x.question === "reachable" && stateRefKey(capId, x.ref, x.workspace) === want);
|
|
14189
|
+
});
|
|
14190
|
+
next.push(row);
|
|
14191
|
+
var clean = sanitizeStateNow(next);
|
|
14192
|
+
// 行が語彙に無く落ちたときは据え置き(reloadLiveState が本物を持ってくる)
|
|
14193
|
+
if (clean && clean.some(function (x) { return x.cap === capId && x.question === "reachable" && x.at === row.at && stateRefKey(capId, x.ref, x.workspace) === want; })) {
|
|
14194
|
+
// 押した結果の控え。**すでに飛んでいる読み直し(押す前の行を返す)や、保存が追いつく前の読み直しが
|
|
14195
|
+
// 届いても、行が古い判定へ戻らない**ように、本物の行が追いつくまで重ねる(「mergeProbeLocal」)
|
|
14196
|
+
PROBE_LOCAL[capId + "\t" + want] = { row: row, t: Date.now() };
|
|
14197
|
+
LIVE_STATE = clean;
|
|
14198
|
+
MERGED = null;
|
|
14199
|
+
repaintOpenLists();
|
|
14200
|
+
refreshDataSources();
|
|
14201
|
+
}
|
|
14202
|
+
// 本物の行を 1 回だけ取りに行く(据え置きのまま・「reloadLiveState」の項)
|
|
14203
|
+
reloadLiveState();
|
|
14204
|
+
}
|
|
14205
|
+
|
|
14206
|
+
/** 押した結果の行の控え(鍵は 能力+畳んだ ref)。本物の行が同じ時刻以降で届いたら捨てる */
|
|
14207
|
+
var PROBE_LOCAL = {};
|
|
14208
|
+
/**
|
|
14209
|
+
* 読み直しで届いた LIVE_STATE に、まだ追いつかれていない押した結果を重ねる。
|
|
14210
|
+
* 🔴 重ねないと、押した瞬間に飛んでいた読み直し(押す前の行)が届いた回だけ、行が古い判定に戻る(#1328)
|
|
14211
|
+
*/
|
|
14212
|
+
function mergeProbeLocal() {
|
|
14213
|
+
var keys = Object.keys(PROBE_LOCAL);
|
|
14214
|
+
if (!keys.length) return;
|
|
14215
|
+
var rows = (LIVE_STATE || []).slice();
|
|
14216
|
+
keys.forEach(function (k) {
|
|
14217
|
+
var local = PROBE_LOCAL[k].row;
|
|
14218
|
+
// 10 分たっても本物が追いつかなければ控えを捨てる(本物の行に任せる)
|
|
14219
|
+
if (Date.now() - PROBE_LOCAL[k].t > 10 * 60 * 1000) { delete PROBE_LOCAL[k]; return; }
|
|
14220
|
+
var want = stateRefKey(local.cap, local.ref, local.workspace);
|
|
14221
|
+
var caught = false;
|
|
14222
|
+
for (var i = 0; i < rows.length; i++) {
|
|
14223
|
+
var x = rows[i];
|
|
14224
|
+
if (x && x.cap === local.cap && x.question === "reachable" && stateRefKey(x.cap, x.ref, x.workspace) === want) {
|
|
14225
|
+
if (String(x.at) >= String(local.at)) caught = true;
|
|
14226
|
+
else rows.splice(i, 1);
|
|
14227
|
+
break;
|
|
14228
|
+
}
|
|
14229
|
+
}
|
|
14230
|
+
if (caught) { delete PROBE_LOCAL[k]; return; }
|
|
14231
|
+
rows.push(local);
|
|
14232
|
+
});
|
|
14233
|
+
LIVE_STATE = sanitizeStateNow(rows);
|
|
14234
|
+
}
|
|
14235
|
+
|
|
14236
|
+
/** 画面の種別 → 接続状態ストアの能力名(「接続を確認」の行) */
|
|
14237
|
+
function probeCapOfType(type) {
|
|
14238
|
+
if (String(type || "").indexOf("github") === 0) return "dev";
|
|
14239
|
+
if (type === "google-chat") return "google-chat";
|
|
14240
|
+
return "chat";
|
|
14241
|
+
}
|
|
14242
|
+
|
|
14323
14243
|
function removeChannelButton(s, card) {
|
|
14324
14244
|
return removeButton({
|
|
14325
14245
|
label: "登録を解除",
|
|
@@ -14565,7 +14485,7 @@ ${calendarAddUrl.toString()}
|
|
|
14565
14485
|
}
|
|
14566
14486
|
var ipLabel = ip ? "固定の送信元 IP(" + ip + ")" : "固定の送信元 IP";
|
|
14567
14487
|
if (keyState === "no") {
|
|
14568
|
-
return "同期は" + ipLabel + "から行います。お客様にこの IP
|
|
14488
|
+
return "同期は" + ipLabel + "から行います。お客様にこの IP の許可を依頼し、接続先の行の「API キーを入れてください」から登録すると同期が始まります。";
|
|
14569
14489
|
}
|
|
14570
14490
|
// 2 文に収める(実装レビューの指摘)。押した直後の帯(サーバの文面)も「許可を依頼」を言うが、
|
|
14571
14491
|
// 帯は数秒で読み終える結果で、この行は次に開いたときも残る状態——役目が違うので両方に置く
|
|
@@ -14635,16 +14555,9 @@ ${calendarAddUrl.toString()}
|
|
|
14635
14555
|
// 切り替えていた)はサーバが changed:false・route 無しで返すので、onDone だと
|
|
14636
14556
|
// スイッチが古い向きのまま「✓ 変更はありません」が出て、次の取り直しまで直らない。
|
|
14637
14557
|
// onSaved は res.ok なら changed を問わず流れる(冪等な op の受け口)
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
LIVE_BACKLOG_ROUTE = backlogRouteOf(j && j.route) || (next ? "fixed-ip" : "runner");
|
|
14642
|
-
if (j && j.fixedEgressIp) LIVE_FIXED_IP = String(j.fixedEgressIp);
|
|
14643
|
-
live.__rebuild();
|
|
14644
|
-
// changed:false では sendConfig が読み直しを起こさない。正本の値で画面を揃えるために自分で起こす
|
|
14645
|
-
if (j && j.changed === false) loadLiveFleet(true);
|
|
14646
|
-
},
|
|
14647
|
-
});
|
|
14558
|
+
// 🔴 **操作は setBacklogRoute に 1 本化している(タイルの[切り替える]と同じ関数)。**
|
|
14559
|
+
// 応答の値を正とする・changed:false では取り直す・確認ダイアログは出さない(A4)は、そちらが持つ
|
|
14560
|
+
await setBacklogRoute(next, msg, { after: function () { live.__rebuild(); } });
|
|
14648
14561
|
// 断られた・通信に失敗したときはこのボタンが残っている(成功時は描き直されて外れている)
|
|
14649
14562
|
btn.disabled = disabled;
|
|
14650
14563
|
btn.classList.remove("is-busy");
|
|
@@ -14654,9 +14567,332 @@ ${calendarAddUrl.toString()}
|
|
|
14654
14567
|
});
|
|
14655
14568
|
wrap.appendChild(live);
|
|
14656
14569
|
wrap.appendChild(msg);
|
|
14570
|
+
// 節が当て直すための口(「その他」の中のスイッチ・c-07 の 2 本目)
|
|
14571
|
+
wrap.__live = live;
|
|
14657
14572
|
return wrap;
|
|
14658
14573
|
}
|
|
14659
14574
|
|
|
14575
|
+
/* ---------------------------------------------------------------------------
|
|
14576
|
+
* Backlog の窓を、GitHub・Figma・Slack・Google Drive・Google Chat の窓にそろえる(c-07 の 2 本目)
|
|
14577
|
+
*
|
|
14578
|
+
* 考え方は 4 つ(GitHub の窓 #1233・4 窓 #1257 と同じ):
|
|
14579
|
+
* 1. 追加の欄は入力と[追加]だけ
|
|
14580
|
+
* 2. 前提が足りないと分かったタイルの中にだけ、理由に合った一手を 1 行で出す
|
|
14581
|
+
* 3. 窓全体を囲む箱・常設の欄を出さない(キーの欄・IP 制限のスイッチ・リアルタイムの大きな箱は引っ込めた)
|
|
14582
|
+
* 4. 説明は上部の説明文に 1 回だけ
|
|
14583
|
+
*
|
|
14584
|
+
* 部品の置き場:
|
|
14585
|
+
* - 一手 … DS_GATE_RESTING_HELP.backlog → backlogRowHelpParts(行の中身で決まる)
|
|
14586
|
+
* - キーの欄 … これまでと同じスペースごとの受け皿(KEY_SLOTS.backlog・#223)に同じカード。
|
|
14587
|
+
* 変えるのは見え方だけ(syncKeyCardVisibility の backlogKeyCardWanted)
|
|
14588
|
+
* - 同期経路の切り替え … setBacklogRoute に 1 本化。入口はタイルの一手・[接続を確認]の結果・「その他」のスイッチ
|
|
14589
|
+
* - 札(事実)… 経路札「固定 IP 経由」・リアルタイム札(appendBacklogChips)
|
|
14590
|
+
* ------------------------------------------------------------------------- */
|
|
14591
|
+
|
|
14592
|
+
/**
|
|
14593
|
+
* 節(backlogTargetsSection)がいま描いている行と、結果の帯・行の描き直しの口。
|
|
14594
|
+
* 一手の文・鍵カードの見え方・ボタンが、節の外から引くための置き場(節を作り直すたびに入れ替わる)。
|
|
14595
|
+
* 🔴 **鍵の置き場の正本は KEY_SLOTS。ここは行の控えだけ**(#223 の「節の中に受け皿の控えを持たない」は守る)。
|
|
14596
|
+
*/
|
|
14597
|
+
var BACKLOG_UI = { rows: [], msg: null, repaintRows: null };
|
|
14598
|
+
|
|
14599
|
+
/** いま見えている行のスペース(小文字・重複なし) */
|
|
14600
|
+
function backlogDomainsNow() {
|
|
14601
|
+
var out = [];
|
|
14602
|
+
var rows = BACKLOG_UI.rows || [];
|
|
14603
|
+
for (var i = 0; i < rows.length; i++) {
|
|
14604
|
+
var d = String(rows[i].domain || "").toLowerCase();
|
|
14605
|
+
if (d && out.indexOf(d) < 0) out.push(d);
|
|
14606
|
+
}
|
|
14607
|
+
return out;
|
|
14608
|
+
}
|
|
14609
|
+
|
|
14610
|
+
/** そのスペースの行のどれかが、届くかを測られているか */
|
|
14611
|
+
function backlogSpaceMeasured(domain) {
|
|
14612
|
+
var rows = BACKLOG_UI.rows || [];
|
|
14613
|
+
for (var i = 0; i < rows.length; i++) {
|
|
14614
|
+
if (String(rows[i].domain || "").toLowerCase() !== domain) continue;
|
|
14615
|
+
if (reachableStateOf("issues", issuesRefOf(rows[i].domain, rows[i].projectKey))) return true;
|
|
14616
|
+
}
|
|
14617
|
+
return false;
|
|
14618
|
+
}
|
|
14619
|
+
|
|
14620
|
+
/** 正本(secretsNow)に、そのスペースの行が来ているか(unknown の行も数える) */
|
|
14621
|
+
function backlogSecretsRowFor(domain) {
|
|
14622
|
+
var rows = LIVE_SECRETS;
|
|
14623
|
+
if (!Array.isArray(rows)) return false;
|
|
14624
|
+
for (var i = 0; i < rows.length; i++) {
|
|
14625
|
+
if (rows[i] && rows[i].kind === "backlog" && rows[i].space === domain) return true;
|
|
14626
|
+
}
|
|
14627
|
+
return false;
|
|
14628
|
+
}
|
|
14629
|
+
|
|
14630
|
+
/**
|
|
14631
|
+
* そのスペースに「API キーを入れてください」を出すか。**カードの見え方(2.4)と行の一手が同じ 1 本を通る。**
|
|
14632
|
+
* - 登録状況が「未登録」→ 出す(正本が言うので、行の札が古くても従う)
|
|
14633
|
+
* - 登録状況が分からない(複数スペースで答えが無い)とき:
|
|
14634
|
+
* · その行の札が「APIキーが未登録です」→ 出す
|
|
14635
|
+
* · 🔴 **そのスペースの答えの行が無く、どの行も測られていない → 出す**(足した直後の新しいスペース。
|
|
14636
|
+
* ここを隠すと、足した行に鍵を入れる口がどこにも無い=#223・#138 の再発)。見せる側に倒す
|
|
14637
|
+
* - 登録済み → 出さない(前夜の古い札には従わない)
|
|
14638
|
+
* @param gate その行の届くかの状態(無ければ null)
|
|
14639
|
+
*/
|
|
14640
|
+
function backlogKeyNeeded(domain, gate) {
|
|
14641
|
+
var st = secretStateOf("backlog", domain, backlogDomainsNow().length);
|
|
14642
|
+
if (st.known === "no") return true;
|
|
14643
|
+
if (st.known === "unknown") {
|
|
14644
|
+
if (gate === "no_token") return true;
|
|
14645
|
+
if (!backlogSpaceMeasured(domain) && !backlogSecretsRowFor(domain)) return true;
|
|
14646
|
+
}
|
|
14647
|
+
return false;
|
|
14648
|
+
}
|
|
14649
|
+
|
|
14650
|
+
/** そのスペースの鍵カードを見せるか(行のどれかが要るか。行が無ければ要らない) */
|
|
14651
|
+
function backlogKeyNeededForSpace(space) {
|
|
14652
|
+
var domains = backlogDomainsNow();
|
|
14653
|
+
var dom = space || (domains.length === 1 ? domains[0] : "");
|
|
14654
|
+
if (!dom) return false;
|
|
14655
|
+
var rows = BACKLOG_UI.rows || [];
|
|
14656
|
+
for (var i = 0; i < rows.length; i++) {
|
|
14657
|
+
if (String(rows[i].domain || "").toLowerCase() !== dom) continue;
|
|
14658
|
+
var r = reachableStateOf("issues", issuesRefOf(rows[i].domain, rows[i].projectKey));
|
|
14659
|
+
if (backlogKeyNeeded(dom, r ? r.status : null)) return true;
|
|
14660
|
+
}
|
|
14661
|
+
return false;
|
|
14662
|
+
}
|
|
14663
|
+
|
|
14664
|
+
/**
|
|
14665
|
+
* Backlog の行に出す一手のパーツ(無ければ null)。**スペースをまたがない**(ボタンはその行のスペースの受け皿だけを引く)。
|
|
14666
|
+
*
|
|
14667
|
+
* キーが要る → 「API キーを入れてください。[入れる]」(複数スペースならスペース名つき)
|
|
14668
|
+
* 読めない・通常経路 → 「読めません。API キーを入れ直してください。[入れ直す] Backlog に IP 制限があるなら、固定 IP 経由で読んでください。[切り替える]」
|
|
14669
|
+
* 読めない・固定 IP → 「お客様に固定の送信元 IP(…)の許可を依頼してください。[コピー] 許可済みなら、API キーを入れ直してください。[入れ直す]」
|
|
14670
|
+
* 読めない・経路不明 → 「読めません。API キーを入れ直してください。[入れ直す]」(切り替えが押せるか分からないので[切り替える]は出さない)
|
|
14671
|
+
*
|
|
14672
|
+
* 🔴 **理由の語(HTTP の番号等)には依存しない。** 401・403・404 は画面から見分けられないので、読めない行は広い方の 1 行。
|
|
14673
|
+
* IP 制限と確定できるのは[接続を確認]の応答の reason だけ(appendBacklogProbeSwitch)。
|
|
14674
|
+
* 🔴 **投入の口(SITE.intake)が無い案件では出さない**(押せない案内を置かない)。
|
|
14675
|
+
* @param row { domain, projectKey, gate }
|
|
14676
|
+
*/
|
|
14677
|
+
function backlogRowHelpParts(row) {
|
|
14678
|
+
if (!row || !SITE.intake) return null;
|
|
14679
|
+
var dom = String(row.domain || "").toLowerCase();
|
|
14680
|
+
var multi = backlogDomainsNow().length > 1;
|
|
14681
|
+
if (backlogKeyNeeded(dom, row.gate)) {
|
|
14682
|
+
return [(multi ? dom + " の " : "") + "API キーを入れてください。", { act: "backlog-key", space: dom, label: "入れる" }];
|
|
14683
|
+
}
|
|
14684
|
+
if (row.gate !== "unreachable") return null;
|
|
14685
|
+
var reenter = { act: "backlog-key", space: dom, label: "入れ直す" };
|
|
14686
|
+
var route = LIVE_BACKLOG_ROUTE;
|
|
14687
|
+
if (route === "fixed-ip") {
|
|
14688
|
+
// IP が取れていなければ IP も[コピー]も出さない(嘘の値を書かない・#516)
|
|
14689
|
+
var ip = LIVE_FIXED_IP;
|
|
14690
|
+
var head = ip
|
|
14691
|
+
? ["お客様に固定の送信元 IP(" + ip + ")の許可を依頼してください。", { copy: ip, bare: true }]
|
|
14692
|
+
: ["お客様に固定の送信元 IP の許可を依頼してください。"];
|
|
14693
|
+
return head.concat([" 許可済みなら、API キーを入れ直してください。", reenter]);
|
|
14694
|
+
}
|
|
14695
|
+
var base = ["読めません。API キーを入れ直してください。", reenter];
|
|
14696
|
+
if (route === "runner") {
|
|
14697
|
+
// 切り替えは案件全体の経路(スペースごとではない)。複数スペースでは強めに言う
|
|
14698
|
+
var scope = multi
|
|
14699
|
+
? "この案件の Backlog 全体(すべてのスペース)の読み方が変わります。"
|
|
14700
|
+
: "この案件の Backlog 全体の読み方が変わります。";
|
|
14701
|
+
return base.concat([" Backlog に IP 制限があるなら、固定 IP 経由で読んでください。" + scope, { act: "backlog-route-on", label: "切り替える" }]);
|
|
14702
|
+
}
|
|
14703
|
+
return base;
|
|
14704
|
+
}
|
|
14705
|
+
|
|
14706
|
+
/**
|
|
14707
|
+
* Backlog の行の最後に一手を 1 段だけ足す。**一手を出した回は engine の理由文を重ねない**(行の補足は最大 1 段)。
|
|
14708
|
+
* 一手が無い状態(読めている・測っていない・取り込みの失敗など)は、これまでどおり appendReachableReason に任せる。
|
|
14709
|
+
*/
|
|
14710
|
+
function appendBacklogRowHelp(row, t, ref) {
|
|
14711
|
+
var r = reachableStateOf("issues", ref);
|
|
14712
|
+
var gate = r ? r.status : null;
|
|
14713
|
+
var info = { domain: t.domain, projectKey: t.projectKey, gate: gate };
|
|
14714
|
+
// 読めない・鍵が無い札は表(DS_GATE_RESTING_HELP.backlog)から、それ以外(読めている・測っていない)は
|
|
14715
|
+
// 「鍵が要るか」だけを見る(登録状況の正本が言うので、行の札が古くても従う)
|
|
14716
|
+
var parts = gate === "no_token" || gate === "unreachable"
|
|
14717
|
+
? restingHelpPartsOf("backlog", gate, info)
|
|
14718
|
+
: backlogRowHelpParts(info);
|
|
14719
|
+
if (parts) {
|
|
14720
|
+
row.appendChild(restingHelpNode(parts, "ds-gate-help ds-gate-resting"));
|
|
14721
|
+
return row;
|
|
14722
|
+
}
|
|
14723
|
+
return appendReachableReason(row, "issues", ref);
|
|
14724
|
+
}
|
|
14725
|
+
|
|
14726
|
+
/** そのスペースの鍵の受け皿(無ければ null)。名無しの受け皿は、スペースが 1 つ以下のあいだだけ代わりを務める(ensureKeySlots の項) */
|
|
14727
|
+
function backlogKeySlotFor(space) {
|
|
14728
|
+
var slots = KEY_SLOTS.backlog || {};
|
|
14729
|
+
var d = String(space || "").toLowerCase();
|
|
14730
|
+
if (d && slots[d]) return slots[d];
|
|
14731
|
+
if (slots[""] && backlogDomainsNow().length <= 1) return slots[""];
|
|
14732
|
+
return null;
|
|
14733
|
+
}
|
|
14734
|
+
|
|
14735
|
+
/** そのスペースの鍵カードの控え(無ければ null)。**ほかのスペースのカードへ落とさない** */
|
|
14736
|
+
function backlogKeyEntryFor(space) {
|
|
14737
|
+
var d = String(space || "").toLowerCase();
|
|
14738
|
+
var list = [];
|
|
14739
|
+
for (var i = 0; i < KEY_CARDS.length; i++) {
|
|
14740
|
+
if (KEY_CARDS[i].kind === "backlog" && KEY_CARDS[i].compact) list.push(KEY_CARDS[i]);
|
|
14741
|
+
}
|
|
14742
|
+
for (var j = 0; j < list.length; j++) if (d && list[j].space === d) return list[j];
|
|
14743
|
+
if (backlogDomainsNow().length <= 1) {
|
|
14744
|
+
for (var k = 0; k < list.length; k++) if (list[k].space === "") return list[k];
|
|
14745
|
+
}
|
|
14746
|
+
return null;
|
|
14747
|
+
}
|
|
14748
|
+
|
|
14749
|
+
/** そのスペースの鍵カードを見せて、入力へ移る([入れる]・[入れ直す])。見つからなければ false */
|
|
14750
|
+
function openBacklogKeyCard(space) {
|
|
14751
|
+
var ent = backlogKeyEntryFor(space);
|
|
14752
|
+
if (!ent) return false;
|
|
14753
|
+
ent.forced = true;
|
|
14754
|
+
ent.fold.open = true;
|
|
14755
|
+
syncKeyCardVisibility();
|
|
14756
|
+
try { if (ent.input.scrollIntoView) ent.input.scrollIntoView({ block: "center" }); } catch (e) { /* 動かせなくても focus は試す */ }
|
|
14757
|
+
ent.input.focus();
|
|
14758
|
+
return true;
|
|
14759
|
+
}
|
|
14760
|
+
|
|
14761
|
+
/**
|
|
14762
|
+
* [入れる]/[入れ直す]。**そのスペースの受け皿が無ければ作らない**(別のスペースの欄へ移さない)。
|
|
14763
|
+
* 🔴 **作る条件は受け皿(KEY_SLOTS)だけ。カードの控えは押した時点で引く**——節は鍵カードより先に組み立てられる
|
|
14764
|
+
*(buildDataSources の順序)ので、行を描く時点ではカードがまだ無い。受け皿は節が先に作っている
|
|
14765
|
+
*/
|
|
14766
|
+
function backlogKeyJump(space, label) {
|
|
14767
|
+
if (secretKindsShown().indexOf("backlog") < 0) return null;
|
|
14768
|
+
if (!backlogKeySlotFor(space)) return null;
|
|
14769
|
+
var btn = el("button", { class: "ds-toggle-btn ds-act-inline", type: "button", text: label || "入れる" });
|
|
14770
|
+
btn.addEventListener("click", function () { openBacklogKeyCard(space); });
|
|
14771
|
+
return btn;
|
|
14772
|
+
}
|
|
14773
|
+
|
|
14774
|
+
/**
|
|
14775
|
+
* 同期経路の切り替え(固定 IP 経由 ⇄ 通常)。**スイッチ(「その他」)とタイルの[切り替える]が同じこの関数を呼ぶ**
|
|
14776
|
+
* ——操作を 2 本書かない(op・値・確認なし・応答の route を正とする・changed:false の取り直しは 1 つだけ持つ)。
|
|
14777
|
+
* 🔴 **確認ダイアログは出さない(#503 A4)。** 可逆(「その他」のスイッチで戻せる)・押した記録はサーバの監査に残る。
|
|
14778
|
+
* @param next true=固定 IP 経由にする
|
|
14779
|
+
* @param msgEl 結果の帯(一覧の外に置いたもの。描き直しで消えない)
|
|
14780
|
+
* @param opts { after: 成功後に呼ぶ(スイッチの描き直し), hint: 成功した帯に添える 1 文 }
|
|
14781
|
+
*/
|
|
14782
|
+
function setBacklogRoute(next, msgEl, opts) {
|
|
14783
|
+
return sendConfig({ op: "backlog-route-set", route: next ? "fixed-ip" : "runner" }, msgEl, null, null, {
|
|
14784
|
+
onSaved: function (j) {
|
|
14785
|
+
// **応答の値を正とする。** 無ければ送った向き(サーバは断らなかった=正本はその向き)
|
|
14786
|
+
LIVE_BACKLOG_ROUTE = backlogRouteOf(j && j.route) || (next ? "fixed-ip" : "runner");
|
|
14787
|
+
if (j && j.fixedEgressIp) LIVE_FIXED_IP = String(j.fixedEgressIp);
|
|
14788
|
+
if (opts && typeof opts.after === "function") opts.after();
|
|
14789
|
+
// 札・一手は行にあるので、行も描き直す(接続先の集合は触らない)
|
|
14790
|
+
if (typeof BACKLOG_UI.repaintRows === "function") BACKLOG_UI.repaintRows();
|
|
14791
|
+
if (opts && opts.hint && LIVE_BACKLOG_ROUTE === "fixed-ip" && !(j && j.changed === false)) {
|
|
14792
|
+
msgEl.appendChild(document.createTextNode(" " + opts.hint));
|
|
14793
|
+
}
|
|
14794
|
+
// changed:false では sendConfig が読み直しを起こさない。正本の値で画面を揃えるために自分で起こす
|
|
14795
|
+
if (j && j.changed === false) loadLiveFleet(true);
|
|
14796
|
+
},
|
|
14797
|
+
});
|
|
14798
|
+
}
|
|
14799
|
+
|
|
14800
|
+
/** 読めなかったタイルの[切り替える]。結果は一覧の外の帯(節の msg)に出す。成功したら行が描き直されてこのボタンは外れる */
|
|
14801
|
+
function backlogRouteOnButton(label) {
|
|
14802
|
+
var btn = el("button", { class: "ds-toggle-btn ds-act-inline", type: "button", text: label || "切り替える" });
|
|
14803
|
+
btn.addEventListener("click", async function () {
|
|
14804
|
+
if (btn.disabled) return;
|
|
14805
|
+
var msg = BACKLOG_UI.msg;
|
|
14806
|
+
if (!msg) return;
|
|
14807
|
+
btn.disabled = true;
|
|
14808
|
+
btn.classList.add("is-busy");
|
|
14809
|
+
await setBacklogRoute(true, msg, { hint: "元に戻すには、下の「その他」から同期経路を切り替えてください。" });
|
|
14810
|
+
// 断られた・通信に失敗したときはこのボタンが残っている
|
|
14811
|
+
btn.disabled = false;
|
|
14812
|
+
btn.classList.remove("is-busy");
|
|
14813
|
+
});
|
|
14814
|
+
return btn;
|
|
14815
|
+
}
|
|
14816
|
+
|
|
14817
|
+
/**
|
|
14818
|
+
* [接続を確認]の結果が IP 制限(応答の reason が ip-restricted)で、いまが通常の経路のときだけ、
|
|
14819
|
+
* 結果の文の横に[切り替える]を足す。**IP 制限と確定できるのはこの応答だけ**(行の札からは見分けられない)。
|
|
14820
|
+
*/
|
|
14821
|
+
function appendBacklogProbeSwitch(note, capId, j) {
|
|
14822
|
+
if (capId !== "issues" || !j || j.reason !== "ip-restricted") return;
|
|
14823
|
+
if (!SITE.intake || LIVE_BACKLOG_ROUTE !== "runner") return;
|
|
14824
|
+
note.appendChild(document.createTextNode(" "));
|
|
14825
|
+
note.appendChild(backlogRouteOnButton("切り替える"));
|
|
14826
|
+
}
|
|
14827
|
+
|
|
14828
|
+
/**
|
|
14829
|
+
* リアルタイム同期(Backlog Webhook)の受信の事実。専用 URL が無い案件は null(出さない)。
|
|
14830
|
+
* **ready でないうちは受信実績を読まない**(索引の行が無いのに lastEventAt だけ来ていても信じない)。
|
|
14831
|
+
* **時刻が読めない値は稼働中に数えない。**
|
|
14832
|
+
*/
|
|
14833
|
+
function backlogRealtimeFacts() {
|
|
14834
|
+
var wh = SITE.backlogWebhook;
|
|
14835
|
+
if (!wh || !wh.url || !wh.token) return null;
|
|
14836
|
+
var rt = LIVE_REALTIME;
|
|
14837
|
+
var ready = !!(rt && rt.ready);
|
|
14838
|
+
var receipts = (ready && rt.targets ? rt.targets : []).map(function (t) {
|
|
14839
|
+
return { key: t && t.projectKey ? String(t.projectKey).toLowerCase() : "", ago: t && t.lastEventAt ? rtAgo(t.lastEventAt) : "" };
|
|
14840
|
+
});
|
|
14841
|
+
return {
|
|
14842
|
+
receipts: receipts,
|
|
14843
|
+
anyLive: receipts.some(function (r) { return !!r.ago; }),
|
|
14844
|
+
viaFixedIp: !!(rt && rt.via === "fixed-ip"),
|
|
14845
|
+
};
|
|
14846
|
+
}
|
|
14847
|
+
|
|
14848
|
+
/** プロジェクトキーが同じ行が、いまの一覧にいくつあるか(受信実績はスペースを持たない) */
|
|
14849
|
+
function backlogKeyRowCount(projectKey) {
|
|
14850
|
+
var key = String(projectKey || "").toLowerCase();
|
|
14851
|
+
var n = 0;
|
|
14852
|
+
var rows = BACKLOG_UI.rows || [];
|
|
14853
|
+
for (var i = 0; i < rows.length; i++) if (String(rows[i].projectKey || "").toLowerCase() === key) n++;
|
|
14854
|
+
return n;
|
|
14855
|
+
}
|
|
14856
|
+
|
|
14857
|
+
/** その行のキーの受信時刻(読めるもの)。無ければ空文字 */
|
|
14858
|
+
function backlogRowReceiptAgo(facts, projectKey) {
|
|
14859
|
+
var key = String(projectKey || "").toLowerCase();
|
|
14860
|
+
for (var i = 0; i < facts.receipts.length; i++) {
|
|
14861
|
+
if (facts.receipts[i].key === key && facts.receipts[i].ago) return facts.receipts[i].ago;
|
|
14862
|
+
}
|
|
14863
|
+
return "";
|
|
14864
|
+
}
|
|
14865
|
+
|
|
14866
|
+
/**
|
|
14867
|
+
* 行に足す札(状態ではなく事実)。**ds-gate クラスは付けない**(「.ds-gate は届くか札 1 つ」の見張りを壊さない)。
|
|
14868
|
+
* - 経路札「固定 IP 経由」… 同期経路が固定 IP のとき(案件単位なので全行に出る)
|
|
14869
|
+
* - リアルタイム札「稼働中」… その行のキーの受信実績があるとき
|
|
14870
|
+
* - リアルタイム札「未受信」… **他の行に稼働があって**この行に受信が無いとき(増やした接続先が来ていないことに気づける・#123)
|
|
14871
|
+
* 🔴 **プロジェクトキーが複数の行で重なるときはリアルタイム札を出さない**(どのスペースの受信か決まらない=嘘を言わない)。
|
|
14872
|
+
* 投入の口が無い案件には出さない。
|
|
14873
|
+
*/
|
|
14874
|
+
function appendBacklogChips(row, t) {
|
|
14875
|
+
if (!SITE.intake) return row;
|
|
14876
|
+
if (LIVE_BACKLOG_ROUTE === "fixed-ip") {
|
|
14877
|
+
row.appendChild(el("span", { class: "ds-route-chip", title: "この案件の Backlog は、固定の送信元 IP から読んでいます", text: "固定 IP 経由" }));
|
|
14878
|
+
}
|
|
14879
|
+
var facts = backlogRealtimeFacts();
|
|
14880
|
+
if (!facts || backlogKeyRowCount(t.projectKey) > 1) return row;
|
|
14881
|
+
var ago = backlogRowReceiptAgo(facts, t.projectKey);
|
|
14882
|
+
var title = facts.viaFixedIp || LIVE_BACKLOG_ROUTE === "fixed-ip"
|
|
14883
|
+
? "反映は受信から数分後です"
|
|
14884
|
+
: "反映は受信から約1分後です(1分ごとにまとめて取り込みます)";
|
|
14885
|
+
if (ago) {
|
|
14886
|
+
row.appendChild(el("span", { class: "ds-rt-chip is-live", title: title }, [
|
|
14887
|
+
el("span", { text: "リアルタイム同期:稼働中" }),
|
|
14888
|
+
el("span", { class: "ds-rt-at", text: ago === "たった今" ? "たった今受信" : ago + "に受信" }),
|
|
14889
|
+
]));
|
|
14890
|
+
} else if (facts.anyLive) {
|
|
14891
|
+
row.appendChild(el("span", { class: "ds-rt-chip is-waiting", title: "この接続先の Webhook はまだ届いていません", text: "リアルタイム同期:未受信" }));
|
|
14892
|
+
}
|
|
14893
|
+
return row;
|
|
14894
|
+
}
|
|
14895
|
+
|
|
14660
14896
|
/**
|
|
14661
14897
|
* Backlog のAPIキーを、どのスペースの棚へ入れるか(#138)。
|
|
14662
14898
|
*
|
|
@@ -14771,6 +15007,9 @@ ${calendarAddUrl.toString()}
|
|
|
14771
15007
|
* **この印のものだけ**消す——「取り込み: 〜に成功」の横の語(別の行のもの)は残す
|
|
14772
15008
|
*/
|
|
14773
15009
|
function runTrailNodes(row, kind) {
|
|
15010
|
+
// 🔴 「その場で確認」は「◯分前に確認」の横に出さない(時刻が出ていて、重ねると同じことを 2 度言う)。
|
|
15011
|
+
// 押した直後の行と開き直した行で形が割れないよう、書き手が button の行は語を持たない
|
|
15012
|
+
if (kind === "gate" && row && row.by === "button") return [];
|
|
14774
15013
|
var word = runSourceWordOf(row);
|
|
14775
15014
|
var node = word ? el("span", { class: "ds-run-src", text: word }) : null;
|
|
14776
15015
|
if (!node) return [];
|
|
@@ -14785,15 +15024,6 @@ ${calendarAddUrl.toString()}
|
|
|
14785
15024
|
return f;
|
|
14786
15025
|
}
|
|
14787
15026
|
|
|
14788
|
-
/**
|
|
14789
|
-
* 「いま測った結果」に差し替えるとき、**「◯分前に確認」に添えた**古い出どころの語を外す
|
|
14790
|
-
* (同じ行が 2 つのことを言わない)。「取り込み: 〜に成功」の横の語は別の行のものなので残す
|
|
14791
|
-
*/
|
|
14792
|
-
function removeRunTrail(scope) {
|
|
14793
|
-
var old = scope.querySelectorAll('.ds-run-src[data-trail="gate"]');
|
|
14794
|
-
for (var i = 0; i < old.length; i++) old[i].remove();
|
|
14795
|
-
}
|
|
14796
|
-
|
|
14797
15027
|
/**
|
|
14798
15028
|
* 接続先1件の「取り込み」の表示。**測っていなければ何も出さない(null)。**
|
|
14799
15029
|
*
|
|
@@ -14886,8 +15116,8 @@ ${calendarAddUrl.toString()}
|
|
|
14886
15116
|
if (!r) return row;
|
|
14887
15117
|
var gate = dsGateForCap(capId, r.status);
|
|
14888
15118
|
row.appendChild(el("span", { class: "ds-gate " + gate.cls, text: gate.text }));
|
|
14889
|
-
var when =
|
|
14890
|
-
if (when) row.appendChild(el("span", { class: "ds-gate-at", text: when
|
|
15119
|
+
var when = agoConfirmedText(r.at);
|
|
15120
|
+
if (when) row.appendChild(el("span", { class: "ds-gate-at", text: when }));
|
|
14891
15121
|
// run の無い書き手は出どころの語を添える(#560)
|
|
14892
15122
|
runTrailNodes(r, "gate").forEach(function (n) { row.appendChild(n); });
|
|
14893
15123
|
return row;
|
|
@@ -14931,7 +15161,7 @@ ${calendarAddUrl.toString()}
|
|
|
14931
15161
|
* -------------------------------------------------------------------------
|
|
14932
15162
|
* #548 までは、この3つは中央が鍵を読まない(#105 決定5)ので、押すとその案件の同期が
|
|
14933
15163
|
* 1回起きるだけだった。オーナーがその境界を覆し、いまは「接続を確認」は Slack / GitHub と
|
|
14934
|
-
* 同じく**中央がその場で叩いて答えを返す**(即答・「
|
|
15164
|
+
* 同じく**中央がその場で叩いて答えを返す**(即答・「applyProbeResult」)。同期を1回
|
|
14935
15165
|
* 起こす従来の動きは「今すぐ同期」(送る値に 「sync: true」)に移った。
|
|
14936
15166
|
*
|
|
14937
15167
|
* 待ちの仕組み(下)は「今すぐ同期」と、Backlog が IP 制限で叩けず夜間と同じ経路へ
|
|
@@ -15020,6 +15250,7 @@ ${calendarAddUrl.toString()}
|
|
|
15020
15250
|
* 登録簿が読めない・ロール未登録・起動失敗が、全部ここへ来る)。
|
|
15021
15251
|
*/
|
|
15022
15252
|
function probeDeclined(note, key, j) {
|
|
15253
|
+
note.hidden = false;
|
|
15023
15254
|
note.textContent = (j && j.message) || "確認できませんでした。";
|
|
15024
15255
|
delete PROBE_WAITS[key];
|
|
15025
15256
|
}
|
|
@@ -15032,13 +15263,15 @@ ${calendarAddUrl.toString()}
|
|
|
15032
15263
|
* 🔴 **控えた 「at」 で待つ(🔴2・壁時計を使わない)。** 押した時刻で見ると、
|
|
15033
15264
|
* 端末の時計がずれている人には永久に届かない(または届く前に届いたことになる)。
|
|
15034
15265
|
*/
|
|
15035
|
-
function probeEnterWait(note, key, capId, ref, before, j) {
|
|
15266
|
+
function probeEnterWait(note, key, capId, ref, before, j, reason) {
|
|
15036
15267
|
PROBE_WAITS[key] = {
|
|
15268
|
+
reason: reason || "",
|
|
15037
15269
|
cap: capId, ref: ref,
|
|
15038
15270
|
at: (before && before.at) || "",
|
|
15039
15271
|
startedAt: Date.now(),
|
|
15040
15272
|
message: (j && j.message) || "",
|
|
15041
15273
|
};
|
|
15274
|
+
note.hidden = false;
|
|
15042
15275
|
probeWaitNote(note, PROBE_WAITS[key]);
|
|
15043
15276
|
// 待っているあいだは取り直しを回す(届いた行で一覧が描き直される)
|
|
15044
15277
|
refreshStateTimer();
|
|
@@ -15047,58 +15280,9 @@ ${calendarAddUrl.toString()}
|
|
|
15047
15280
|
/** 待ちの 1 文(#548)。「同期を開始しました」等、サーバの文言をそのまま出す */
|
|
15048
15281
|
function probeWaitNote(note, wait) {
|
|
15049
15282
|
note.textContent = (wait && wait.message) || "";
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
* 即答(#548 の direct)を行に映す。**「probeSection」 と同じ見た目にする**——
|
|
15054
|
-
* 夜間のスナップショットの判定といま測った判定を**同じ行に並べない**ため
|
|
15055
|
-
* (並べると「⚠️ と 接続OK が同時に出ている」になり、この節で何度も直してきた
|
|
15056
|
-
* 食い違いを画面の側から作ることになる)。置き換えたことが分かるよう、
|
|
15057
|
-
* いつ測ったかを必ず添える。
|
|
15058
|
-
*
|
|
15059
|
-
* 🔴 **札が無い行もある**(一度も測っていない接続先= 「appendReachableChip」 が
|
|
15060
|
-
* 何も足していない行)。その場合は札を作って**ボタンの前**に差し込む——後ろに置くと
|
|
15061
|
-
* 行の読み順(札 → ボタン)が崩れる。
|
|
15062
|
-
*
|
|
15063
|
-
* @param anchor 差し込む位置の目印(1つ目のボタン)
|
|
15064
|
-
*/
|
|
15065
|
-
function showProbeVerdict(row, capId, anchorIn, j) {
|
|
15066
|
-
var gate = dsGateForCap(capId, j.gate);
|
|
15067
|
-
// 差し込む位置は**行の最初のボタン**の前(「外す」が出る行では「外す」の前)。
|
|
15068
|
-
// 「接続を確認」の前に置くと、外せる行だけ「外す → 札 → ボタン」の並びになる(🟡10)
|
|
15069
|
-
var anchor = anchorIn;
|
|
15070
|
-
var btns = row.querySelectorAll(".ds-toggle-btn");
|
|
15071
|
-
for (var bi = 0; bi < btns.length; bi++) {
|
|
15072
|
-
// **行の直下にあるボタンだけ**が目印になれる(入れ子の中のボタンの前には差し込めない)
|
|
15073
|
-
if (btns[bi].parentNode === row) { anchor = btns[bi]; break; }
|
|
15074
|
-
}
|
|
15075
|
-
var chip = row.querySelector(".ds-gate");
|
|
15076
|
-
if (!chip) {
|
|
15077
|
-
chip = el("span", { class: "ds-gate" });
|
|
15078
|
-
row.insertBefore(chip, anchor);
|
|
15079
|
-
}
|
|
15080
|
-
chip.className = "ds-gate " + gate.cls;
|
|
15081
|
-
chip.textContent = gate.text;
|
|
15082
|
-
var at = row.querySelector(".ds-gate-at");
|
|
15083
|
-
if (!at) {
|
|
15084
|
-
at = el("span", { class: "ds-gate-at" });
|
|
15085
|
-
row.insertBefore(at, anchor);
|
|
15086
|
-
}
|
|
15087
|
-
at.textContent = "いま測った結果(" + (dsFmtTime(j.checkedAt) || "たった今") + ")";
|
|
15088
|
-
// 古い run の導線も外す(#560)。残すと「いま測った結果」の横に夜間の run のログが並ぶ
|
|
15089
|
-
removeRunTrail(row);
|
|
15090
|
-
// **夜間の判定に添えてあった案内は隠す。** 置き換えた判定と食い違う案内が下に残ると、
|
|
15091
|
-
// 同じ行が2つのことを言う(probeSection の stale 処理と同じ)
|
|
15092
|
-
var stale = row.querySelectorAll(".ds-gate-help");
|
|
15093
|
-
for (var si = 0; si < stale.length; si++) stale[si].hidden = true;
|
|
15094
|
-
// **押し直しで段が増えないよう、前回の結果は消してから出す**(probeSection の札と同じ)
|
|
15095
|
-
var old = row.querySelectorAll(".ds-probe-help");
|
|
15096
|
-
for (var oi = 0; oi < old.length; oi++) old[oi].remove();
|
|
15097
|
-
// 次の一手は語の表(DS_GATE_CAP)が持っていればそれ。無ければ判定器の1文(detail)
|
|
15098
|
-
// ——どちらも**1段だけ**(帯は症状・行は一手・繰り返さない)
|
|
15099
|
-
var nodes = gateHelpNodes(gate, "ds-probe-help");
|
|
15100
|
-
if (!nodes.length && j.detail) nodes = [el("p", { class: "ds-probe-help", text: String(j.detail) })];
|
|
15101
|
-
nodes.forEach(function (n) { row.appendChild(n); });
|
|
15283
|
+
// IP 制限の確定は[接続を確認]の応答だけ。待ちが持つ reason で、描き直しのたびに[切り替える]を足す
|
|
15284
|
+
//(結果は行を描く側に一本化。textContent で子を消すので二重にならない)
|
|
15285
|
+
if (wait && wait.reason === "ip-restricted") appendBacklogProbeSwitch(note, wait.cap, { reason: wait.reason });
|
|
15102
15286
|
}
|
|
15103
15287
|
|
|
15104
15288
|
/**
|
|
@@ -15129,9 +15313,11 @@ ${calendarAddUrl.toString()}
|
|
|
15129
15313
|
else note.hidden = true;
|
|
15130
15314
|
|
|
15131
15315
|
checkBtn.addEventListener("click", async function () {
|
|
15316
|
+
// 押している間は**ボタンだけ**が「確認中…」になる。行の中身は変えない(#1328)。
|
|
15317
|
+
// 前回の返事(「同期を開始しました」等)は押した時点で消す
|
|
15132
15318
|
var endBusy = beginBusy(checkBtn, "確認中…");
|
|
15133
|
-
note.hidden =
|
|
15134
|
-
note.textContent = "
|
|
15319
|
+
note.hidden = true;
|
|
15320
|
+
note.textContent = "";
|
|
15135
15321
|
// 🔴 **押す前に、いまの行の 「at」 を控える(🔴2)。** 行が無ければ空文字
|
|
15136
15322
|
//(どんな 「at」 が来ても「新しい」と読める)。使うのは IP 制限で倒れた回だけだが、
|
|
15137
15323
|
// 応答が返ってからでは「押す前の行」を控えられない
|
|
@@ -15149,7 +15335,8 @@ ${calendarAddUrl.toString()}
|
|
|
15149
15335
|
// 語が返らない回=この場では叩けなかった回。Backlog の IP 制限
|
|
15150
15336
|
//(「reason」 が 「ip-restricted」)で夜間と同じ経路へ倒したときと、
|
|
15151
15337
|
// 即答をまだ知らない古い Lambda が相手のとき。どちらも従来どおり待つ
|
|
15152
|
-
|
|
15338
|
+
// IP 制限と確定できるのはこの応答だけ(reason)。[切り替える]は待ちの文を描く側(probeWaitNote)が足す
|
|
15339
|
+
probeEnterWait(note, key, capId, ref, before, j, j.reason);
|
|
15153
15340
|
endBusy();
|
|
15154
15341
|
return;
|
|
15155
15342
|
}
|
|
@@ -15158,11 +15345,11 @@ ${calendarAddUrl.toString()}
|
|
|
15158
15345
|
delete PROBE_WAITS[key];
|
|
15159
15346
|
note.hidden = true;
|
|
15160
15347
|
note.textContent = "";
|
|
15161
|
-
|
|
15162
|
-
//
|
|
15163
|
-
|
|
15164
|
-
reloadLiveState();
|
|
15348
|
+
// 🔴 結果は行を描く関数に渡して描き直す(札・時刻・次の一手は行が決める。#1328)。
|
|
15349
|
+
// 次の一手は語の表が持っていればそれ、無ければ判定器の 1 文(detail)——どちらも行の側の規則
|
|
15350
|
+
applyProbeResult(capId, ref, "", { gate: j.gate, checkedAt: j.checkedAt, needed: "", detail: j.detail || "" });
|
|
15165
15351
|
} catch (e) {
|
|
15352
|
+
note.hidden = false;
|
|
15166
15353
|
note.textContent = "通信に失敗しました。";
|
|
15167
15354
|
delete PROBE_WAITS[key];
|
|
15168
15355
|
}
|
|
@@ -15237,7 +15424,35 @@ ${calendarAddUrl.toString()}
|
|
|
15237
15424
|
*/
|
|
15238
15425
|
function backlogPaintSigOf(list) {
|
|
15239
15426
|
return ingestSigOf(list) + "|" + (list || []).map(function (t) { return t && t.pending === true ? "p" : "-"; }).join(",")
|
|
15240
|
-
+ "|" + reachSigOf("issues", (list || []).map(function (t) { return issuesRefOf(t.domain, t.projectKey); }))
|
|
15427
|
+
+ "|" + reachSigOf("issues", (list || []).map(function (t) { return issuesRefOf(t.domain, t.projectKey); }))
|
|
15428
|
+
+ "|" + backlogFactsSigOf();
|
|
15429
|
+
}
|
|
15430
|
+
/**
|
|
15431
|
+
* 行の札・一手が読む**一覧の外の事実**の署名(c-07 の 2 本目・設計レビュー🔴1)。
|
|
15432
|
+
* 同期経路・固定 IP・そのスペースのキーの登録状況・リアルタイムの受信は、接続先の集合や「届くか」と別に変わる。
|
|
15433
|
+
* 混ぜないと、**接続先を足した後(応答の一覧を採った後)や切り替えの後に、行の札・一手が描き直されない**。
|
|
15434
|
+
*/
|
|
15435
|
+
function backlogFactsSigOf() {
|
|
15436
|
+
var secrets = [];
|
|
15437
|
+
if (Array.isArray(LIVE_SECRETS)) {
|
|
15438
|
+
for (var i = 0; i < LIVE_SECRETS.length; i++) {
|
|
15439
|
+
var r = LIVE_SECRETS[i];
|
|
15440
|
+
if (r && r.kind === "backlog") secrets.push((r.space || "") + ":" + r.state);
|
|
15441
|
+
}
|
|
15442
|
+
}
|
|
15443
|
+
var rt = LIVE_REALTIME;
|
|
15444
|
+
var rtRows = rt && rt.ready && Array.isArray(rt.targets)
|
|
15445
|
+
? rt.targets.map(function (t) { return (t && t.projectKey) + "@" + (t && t.lastEventAt); })
|
|
15446
|
+
: [];
|
|
15447
|
+
return JSON.stringify([
|
|
15448
|
+
LIVE_BACKLOG_ROUTE === undefined ? "?" : LIVE_BACKLOG_ROUTE,
|
|
15449
|
+
LIVE_FIXED_IP,
|
|
15450
|
+
secrets,
|
|
15451
|
+
(fleetVal("secretSources") || {}).backlog || "",
|
|
15452
|
+
fleetVal("secretSpaces") || null,
|
|
15453
|
+
rtRows,
|
|
15454
|
+
rt && rt.via || "",
|
|
15455
|
+
]);
|
|
15241
15456
|
}
|
|
15242
15457
|
|
|
15243
15458
|
/**
|
|
@@ -15299,6 +15514,8 @@ ${calendarAddUrl.toString()}
|
|
|
15299
15514
|
var editable = backlogTargetsEditable();
|
|
15300
15515
|
/** 追加・削除の応答で一覧を受け取ったか(受け取っていたら、そちらが正本) */
|
|
15301
15516
|
var adopted = false;
|
|
15517
|
+
/** 「その他」(1 回だけ作る)。paint() は作る前には当て直さない */
|
|
15518
|
+
var moreEl = null;
|
|
15302
15519
|
|
|
15303
15520
|
// **Slack / GitHub / Figma / Drive と同じレコードの行にする(2026-08-21・オーナー指摘)。**
|
|
15304
15521
|
// それまでは「現在の接続先」というラベル付きの独自レイアウト(.ds-current /
|
|
@@ -15336,11 +15553,13 @@ ${calendarAddUrl.toString()}
|
|
|
15336
15553
|
// 理由の1段は**行の最後**(ボタンより後ろ)に置く(appendReachableReason の項)
|
|
15337
15554
|
var issuesRef = issuesRefOf(t.domain, t.projectKey);
|
|
15338
15555
|
appendReachableChip(row, "issues", issuesRef);
|
|
15556
|
+
// 札(事実): 固定 IP 経由・リアルタイム同期の稼働中/未受信(c-07 の 2 本目)。届くか札・確認時刻の後、最初のボタンの前
|
|
15557
|
+
appendBacklogChips(row, t);
|
|
15339
15558
|
// 「接続を確認」(#523 S3)。**外す権限が無くても押せる**——読むだけの操作で、
|
|
15340
15559
|
// 押せる人を絞らない(決定7)。ボタンの並びは Slack の行と同じ(外すボタンの後ろ)
|
|
15341
15560
|
if (!editable) {
|
|
15342
15561
|
appendDispatchProbe(row, "issues", issuesRef);
|
|
15343
|
-
return
|
|
15562
|
+
return appendBacklogRowHelp(row, t, issuesRef);
|
|
15344
15563
|
}
|
|
15345
15564
|
var btn = el("button", { class: "ds-toggle-btn ds-danger", type: "button", text: "この接続先を外す" });
|
|
15346
15565
|
btn.addEventListener("click", async function () {
|
|
@@ -15362,7 +15581,8 @@ ${calendarAddUrl.toString()}
|
|
|
15362
15581
|
});
|
|
15363
15582
|
row.appendChild(btn);
|
|
15364
15583
|
appendDispatchProbe(row, "issues", issuesRef);
|
|
15365
|
-
|
|
15584
|
+
// 🔴 **一手は行の最後(ボタンより後ろ)に 1 段だけ。** 読めている行・測っていない行には何も出さない
|
|
15585
|
+
return appendBacklogRowHelp(row, t, issuesRef);
|
|
15366
15586
|
}
|
|
15367
15587
|
|
|
15368
15588
|
/**
|
|
@@ -15388,6 +15608,8 @@ ${calendarAddUrl.toString()}
|
|
|
15388
15608
|
|
|
15389
15609
|
function paint() {
|
|
15390
15610
|
while (listEl.firstChild) listEl.removeChild(listEl.firstChild);
|
|
15611
|
+
// 一手・札・鍵カードの見え方が引く行の控え(行を描く前に入れ替える)
|
|
15612
|
+
BACKLOG_UI.rows = targets;
|
|
15391
15613
|
var shown = grouped(targets);
|
|
15392
15614
|
// そのスペースの**最後の行**を覚えておく(鍵の欄はそこへ入れる)
|
|
15393
15615
|
var lastRowOf = {};
|
|
@@ -15423,6 +15645,8 @@ ${calendarAddUrl.toString()}
|
|
|
15423
15645
|
// 足した瞬間に欄が現れ、最後の1件を外した瞬間に消えるのはここが効いているから。
|
|
15424
15646
|
// 節ごとの差し替え(refreshDataSources)はモーダルが開いている間は何もしない
|
|
15425
15647
|
syncKeyCardVisibility();
|
|
15648
|
+
// 一覧の下の一手・「その他」も、行と同じ材料で当て直す(まだ作っていない最初の 1 回は飛ばす)
|
|
15649
|
+
if (typeof refreshExtras === "function" && moreEl) refreshExtras();
|
|
15426
15650
|
}
|
|
15427
15651
|
|
|
15428
15652
|
/**
|
|
@@ -15569,8 +15793,16 @@ ${calendarAddUrl.toString()}
|
|
|
15569
15793
|
* 入力中の鍵と「保存中…」は保たれる。
|
|
15570
15794
|
*/
|
|
15571
15795
|
box.__rebuild = function () {
|
|
15572
|
-
// 🔴 応答に添えられた一覧(adoptTargets
|
|
15573
|
-
|
|
15796
|
+
// 🔴 応答に添えられた一覧(adoptTargets)のほうが新しい。**接続先の集合は上書きしない。**
|
|
15797
|
+
// ただし行の札・一手が読む外の事実(経路・固定 IP・キーの登録状況・リアルタイムの受信)は
|
|
15798
|
+
// 集合と別に変わるので、署名が変わったときは**行だけ**描き直す(c-07 の 2 本目・設計レビュー🔴1)
|
|
15799
|
+
if (adopted) {
|
|
15800
|
+
var sigAdopted = backlogPaintSigOf(targets);
|
|
15801
|
+
if (sigAdopted === lastIngestSig) return;
|
|
15802
|
+
lastIngestSig = sigAdopted;
|
|
15803
|
+
paint();
|
|
15804
|
+
return;
|
|
15805
|
+
}
|
|
15574
15806
|
var next = backlogTargetsNow(internalSourceOf(REGISTERED_CAP_NAMES.issues));
|
|
15575
15807
|
// 🔴 **変化が無いと言えるのは、接続先の集合と取り込みの状態が両方同じときだけ(段C)。**
|
|
15576
15808
|
// 世代だけで判断すると、**応答を待たずにモーダルを開いた人には取り込みが永久に出ない**
|
|
@@ -15583,6 +15815,16 @@ ${calendarAddUrl.toString()}
|
|
|
15583
15815
|
ensureKeySlots();
|
|
15584
15816
|
paint();
|
|
15585
15817
|
};
|
|
15818
|
+
/**
|
|
15819
|
+
* 行だけを描き直す口(切り替え・キー保存の後に呼ぶ)。**接続先の集合は触らない。**
|
|
15820
|
+
* 応答の一覧を採った後でも、行の札・一手は最新の事実で描かれる(lastIngestSig も揃える)
|
|
15821
|
+
*/
|
|
15822
|
+
box.__repaintRows = function () {
|
|
15823
|
+
lastIngestSig = backlogPaintSigOf(targets);
|
|
15824
|
+
paint();
|
|
15825
|
+
};
|
|
15826
|
+
BACKLOG_UI.repaintRows = box.__repaintRows;
|
|
15827
|
+
BACKLOG_UI.msg = msg;
|
|
15586
15828
|
// 接続先がまだ無い案件の受け皿は、0件の1行の直後に置く(行が無いので中へ入れられない)
|
|
15587
15829
|
if (KEY_SLOTS.backlog[""]) box.appendChild(KEY_SLOTS.backlog[""]);
|
|
15588
15830
|
if (editable) {
|
|
@@ -15592,27 +15834,11 @@ ${calendarAddUrl.toString()}
|
|
|
15592
15834
|
title: "+ 接続先を追加",
|
|
15593
15835
|
placeholder: "https://<スペース>.backlog.jp/projects/<プロジェクトキー>",
|
|
15594
15836
|
requiredNote: "BacklogのURLを入力してください(プロジェクトキーが入った形。数字だけのURLは受け付けません)。",
|
|
15595
|
-
//
|
|
15596
|
-
//
|
|
15597
|
-
//
|
|
15598
|
-
//
|
|
15599
|
-
|
|
15600
|
-
//
|
|
15601
|
-
// ## 4行 → 2行に圧縮した(2026-08-21・オーナー指摘「何も変わってない気がする」)
|
|
15602
|
-
//
|
|
15603
|
-
// **この欄は URL を貼るだけ**なので、4行の説明が読む負担に見合わない。
|
|
15604
|
-
// ただし**落としてよいのは1行目だけ**だった(欄を見れば分かる内容)。残り3つは
|
|
15605
|
-
// ここ以外に置き場が無いので、**消さずに2行へ畳んでいる**:
|
|
15606
|
-
// - 「起票先は変わりません」… 追加の確認をやめたとき(2026-08-20)に
|
|
15607
|
-
// ここへ移してきたもの。落とすと知る手段が無くなる
|
|
15608
|
-
// - 「スペースごとに別のキー」… 鍵の欄からは読めない。1スペースの案件は
|
|
15609
|
-
// 行が1本しか無いので、これから2つ目を足す人に「別の鍵が要る」が届かない
|
|
15610
|
-
// - 「案件専用URLの登録」… 救出先の realtimeSyncCard() は稼働中の案件では
|
|
15611
|
-
// 手順一式を <details> に畳むので、**いちばん要る人に対して閉じている**
|
|
15612
|
-
hint: [
|
|
15613
|
-
"URL を貼ると同期先が増えます。ビューアからの起票先は変わりません(スペースごとに別のAPIキーが要ります)。",
|
|
15614
|
-
"リアルタイム同期を使うなら、追加した Backlog プロジェクトにも、このプロジェクト専用URLの登録が要ります(同じURLを使います)。",
|
|
15615
|
-
],
|
|
15837
|
+
// 🔴 **追加の欄は URL の入力と[追加]だけ(c-07 の 2 本目)。** 手順の文は置かない(hint を渡さない)。
|
|
15838
|
+
// 外した情報の行き先:
|
|
15839
|
+
// - 「起票先は変わりません」「毎晩同期します」「外すには」… 上部の説明文(openCapabilityModal)
|
|
15840
|
+
// - 「スペースごとに別のAPIキー」… 足した行の一手「◯◯ の API キーを入れてください」と、「その他」のスペースごとの行
|
|
15841
|
+
// - 「専用 URL の登録が要ります」… 追加した行のリアルタイム札(未受信)と、一覧の下の一手[設定する]
|
|
15616
15842
|
build: function (url) {
|
|
15617
15843
|
return { op: "backlog-target-add", url: url, baseGeneration: targetsGenerationOf(targets) };
|
|
15618
15844
|
},
|
|
@@ -15621,7 +15847,86 @@ ${calendarAddUrl.toString()}
|
|
|
15621
15847
|
}));
|
|
15622
15848
|
}
|
|
15623
15849
|
box.appendChild(msg);
|
|
15850
|
+
|
|
15851
|
+
// 投入の口(intake)が無い案件には、一手も「その他」も出さない(押せない案内を置かない)
|
|
15852
|
+
if (editable) {
|
|
15853
|
+
// ---- 一覧の下の一手(リアルタイム同期)と、ふだん見えない「その他」(c-07 の 2 本目)----
|
|
15854
|
+
//
|
|
15855
|
+
// 🔴 **リアルタイム同期は大きな箱を常設しない。** 稼働中は行の札だけ。受信が無い・一部だけのときだけ、
|
|
15856
|
+
// 一覧の下に 1 行の一手([設定する]で手順と専用 URL が開く)。全部稼働中なら、URL の再確認は「その他」に置く。
|
|
15857
|
+
// 🔴 **「その他」は 1 回だけ作る**(結果の帯・開閉を描き直しで消さない)。中の一覧は liveList で追いつかせる。
|
|
15858
|
+
var rtOpen = false;
|
|
15859
|
+
var rtBox = liveList("backlog-realtime", function () {
|
|
15860
|
+
var st = backlogRealtimeState();
|
|
15861
|
+
return JSON.stringify([st ? [st.total, st.live] : null, backlogFactsSigOf()]);
|
|
15862
|
+
}, function (b) {
|
|
15863
|
+
var st = backlogRealtimeState();
|
|
15864
|
+
// 稼働中の行が無い/一部だけのときだけ。全部稼働中は何も出さない(再確認は「その他」)
|
|
15865
|
+
if (!st || st.live === st.total) return;
|
|
15866
|
+
var text = st.live === 0
|
|
15867
|
+
? "リアルタイム同期(任意)を使うと、Backlog の変更をすぐ取り込めます。"
|
|
15868
|
+
: "まだ受信していない接続先があります(Backlog プロジェクトごとに Webhook の登録が要ります)。";
|
|
15869
|
+
var setup = el("div", { class: "rt-card rt-setup" }, realtimeSetupNodes());
|
|
15870
|
+
setup.hidden = !rtOpen;
|
|
15871
|
+
var openBtn = el("button", { class: "ds-toggle-btn ds-act-inline", type: "button", text: "設定する", "aria-expanded": rtOpen ? "true" : "false" });
|
|
15872
|
+
openBtn.addEventListener("click", function () {
|
|
15873
|
+
rtOpen = !rtOpen;
|
|
15874
|
+
setup.hidden = !rtOpen;
|
|
15875
|
+
openBtn.setAttribute("aria-expanded", rtOpen ? "true" : "false");
|
|
15876
|
+
});
|
|
15877
|
+
b.appendChild(el("p", { class: "ds-rt-help" }, [document.createTextNode(text), openBtn]));
|
|
15878
|
+
b.appendChild(setup);
|
|
15879
|
+
});
|
|
15880
|
+
box.appendChild(rtBox);
|
|
15881
|
+
|
|
15882
|
+
var routeWrap = backlogRouteSwitch();
|
|
15883
|
+
var keyRows = liveList("backlog-key-rows", function () {
|
|
15884
|
+
var ds = backlogDomainsNow();
|
|
15885
|
+
return JSON.stringify(ds.map(function (d) { return [d, secretStateOf("backlog", d, ds.length).known]; }));
|
|
15886
|
+
}, function (b) {
|
|
15887
|
+
var ds = backlogDomainsNow();
|
|
15888
|
+
ds.forEach(function (d) {
|
|
15889
|
+
var st = secretStateOf("backlog", d, ds.length);
|
|
15890
|
+
var word = st.known === "yes" ? "登録済みです" : st.known === "no" ? "未登録です" : "確認中です";
|
|
15891
|
+
// ボタンはそのスペースの鍵カードを開く(受け皿が無ければ作らない)
|
|
15892
|
+
b.appendChild(el("p", { class: "ds-more-key" }, [
|
|
15893
|
+
el("span", { text: d + ":API キーは" + word }),
|
|
15894
|
+
backlogKeyJump(d, st.known === "no" ? "入れる" : "入れ直す"),
|
|
15895
|
+
]));
|
|
15896
|
+
});
|
|
15897
|
+
});
|
|
15898
|
+
var rtMore = liveList("backlog-realtime-more", function () {
|
|
15899
|
+
var st = backlogRealtimeState();
|
|
15900
|
+
return JSON.stringify([st ? [st.total, st.live] : null, backlogFactsSigOf()]);
|
|
15901
|
+
}, function (b) {
|
|
15902
|
+
var st = backlogRealtimeState();
|
|
15903
|
+
if (!st || st.live !== st.total) return;
|
|
15904
|
+
var fold = el("details", { class: "rt-card rt-steps-fold" }, [
|
|
15905
|
+
el("summary", { text: "リアルタイム同期(任意)の設定手順と専用 URL(再確認・張り替え用)" }),
|
|
15906
|
+
]);
|
|
15907
|
+
realtimeSetupNodes().forEach(function (n) { fold.appendChild(n); });
|
|
15908
|
+
b.appendChild(fold);
|
|
15909
|
+
});
|
|
15910
|
+
moreEl = el("details", { class: "ds-more" }, [
|
|
15911
|
+
el("summary", { text: "その他" }),
|
|
15912
|
+
routeWrap,
|
|
15913
|
+
keyRows,
|
|
15914
|
+
rtMore,
|
|
15915
|
+
]);
|
|
15916
|
+
box.appendChild(moreEl);
|
|
15917
|
+
refreshExtras();
|
|
15918
|
+
}
|
|
15624
15919
|
return box;
|
|
15920
|
+
|
|
15921
|
+
/** 「その他」の出し入れと、中の一覧の追いつかせ。**出すのは接続先が 1 件以上、または経路が固定 IP のとき**(ON は 0 件でも戻せる) */
|
|
15922
|
+
function refreshExtras() {
|
|
15923
|
+
if (!moreEl) return;
|
|
15924
|
+
moreEl.hidden = !(keyTargetCount("課題管理") > 0 || LIVE_BACKLOG_ROUTE === "fixed-ip");
|
|
15925
|
+
rtBox.__rebuild();
|
|
15926
|
+
if (routeWrap.__live) routeWrap.__live.__rebuild();
|
|
15927
|
+
keyRows.__rebuild();
|
|
15928
|
+
rtMore.__rebuild();
|
|
15929
|
+
}
|
|
15625
15930
|
}
|
|
15626
15931
|
|
|
15627
15932
|
/** 接続先を外すときの確認。**最後の1件は、止まるものを名指しで伝える** */
|
|
@@ -15736,20 +16041,13 @@ ${calendarAddUrl.toString()}
|
|
|
15736
16041
|
// 接続先の正本(issues-config.json)は雛形から作られるので、立ち上げ直後の案件にも
|
|
15737
16042
|
// 実在する。同期ミラーの有無で門前払いすると、**新規案件がこの画面から
|
|
15738
16043
|
// Backlog に繋げない**——設定UIを置いている意味が無くなる
|
|
16044
|
+
// 🔴 **同期経路のスイッチ・リアルタイム同期は、節の中(一覧の下の一手と「その他」)に移した**
|
|
16045
|
+
// (c-07 の 2 本目)。ここでは節だけを置く
|
|
15739
16046
|
box.appendChild(backlogTargetsSection(s));
|
|
15740
16047
|
// **「この画面からは接続先を変更できません」の枝は撤去した(#516)。** ゲートが
|
|
15741
16048
|
// 「intake があるか」になり、この関数は intake の無い案件では上で return しているので、
|
|
15742
16049
|
// 到達しない枝だった(到達不能な枝は変異検証の網から漏れる)。
|
|
15743
16050
|
//
|
|
15744
|
-
// **接続先の下・リアルタイム同期の上に、同期経路のスイッチを置く(#516・設計 v2 §6)。**
|
|
15745
|
-
// #497 の「固定 IP 経由で同期できます(切り替えは運用側)」の案内と画面の IP 定数は
|
|
15746
|
-
// これに置き換わった——切り替えは担当者が自分で押す。
|
|
15747
|
-
box.appendChild(backlogRouteSwitch());
|
|
15748
|
-
// **接続先の下に置く。** リアルタイム同期は「どのプロジェクトを見るか」が
|
|
15749
|
-
// 決まった後の話で、順序が逆だと、接続先を設定していない案件が先に
|
|
15750
|
-
// Webhook を貼りに行く(貼っても届かない)
|
|
15751
|
-
var rt = realtimeSyncCard();
|
|
15752
|
-
if (rt) box.appendChild(rt);
|
|
15753
16051
|
used = true;
|
|
15754
16052
|
}
|
|
15755
16053
|
|
|
@@ -16956,7 +17254,7 @@ ${calendarAddUrl.toString()}
|
|
|
16956
17254
|
var gateEl = el("span", { class: "ds-gate " + gate.cls, text: gate.text });
|
|
16957
17255
|
// 状態ストア由来の判定には「いつ確かめたか」を必ず添える
|
|
16958
17256
|
var gateAtEl = s.gateAt
|
|
16959
|
-
? el("span", { class: "ds-gate-at", text:
|
|
17257
|
+
? el("span", { class: "ds-gate-at", text: agoConfirmedText(s.gateAt) })
|
|
16960
17258
|
: null;
|
|
16961
17259
|
// run の無い書き手は出どころの語を添える(#560)。overlay が写した 「gateRunUrl」 / 「gateBy」 から決める
|
|
16962
17260
|
var gateTrail = s.gateAt ? runTrailNodes({ runUrl: s.gateRunUrl || "", by: s.gateBy || "" }, "gate") : [];
|
|
@@ -17116,7 +17414,7 @@ ${calendarAddUrl.toString()}
|
|
|
17116
17414
|
// **origin === "explicit" でも出す。** 確認は読むだけで、どの登録ファイル由来かに
|
|
17117
17415
|
// 関係なく効く(書き込みになる「表示名を更新する」だけが由来で出し分かれる)
|
|
17118
17416
|
if (SITE.intake && probeTypeOf(s.type) && s.ref) {
|
|
17119
|
-
var probe = probeSection(s, card
|
|
17417
|
+
var probe = probeSection(s, card);
|
|
17120
17418
|
// **ボタンは行の中(「登録を解除」の隣)。** 独立した段を作ると1行が2段になる
|
|
17121
17419
|
card.appendChild(probe.btn);
|
|
17122
17420
|
appendRowHelp();
|
|
@@ -17500,6 +17798,8 @@ ${calendarAddUrl.toString()}
|
|
|
17500
17798
|
LIVE_REGISTERED = sanitizeRegisteredNow(j && j.registeredNow);
|
|
17501
17799
|
LIVE_REGISTERED_AT = startedAt;
|
|
17502
17800
|
LIVE_STATE = sanitizeStateNow(j && j.stateNow);
|
|
17801
|
+
// 押した結果が本物の行に追いつくまで重ねる(mergeProbeLocal の項)
|
|
17802
|
+
mergeProbeLocal();
|
|
17503
17803
|
// **欄の有無をそのまま覚える。** sanitize を通した後では「読めて0件」が消える
|
|
17504
17804
|
LIVE_STATE_SEEN = !!(j && Array.isArray(j.stateNow));
|
|
17505
17805
|
// **カレンダーの行だけは sanitize の前に数える**(#428・上の宣言の項)
|
|
@@ -17847,51 +18147,24 @@ ${calendarAddUrl.toString()}
|
|
|
17847
18147
|
}
|
|
17848
18148
|
|
|
17849
18149
|
/**
|
|
17850
|
-
* リアルタイム同期(Backlog Webhook
|
|
17851
|
-
*
|
|
17852
|
-
*
|
|
17853
|
-
* realtime に委ねる——画面が持っている静的データからは「ルーティング索引に自分の行が
|
|
17854
|
-
* あるか」が分からないし、2箇所で判定すると片方だけがずれる。
|
|
17855
|
-
* - via: "fixed-ip" … 固定 IP 経由で同期する案件(#516・#503 設計 v3 §6)。手順は同じ。
|
|
17856
|
-
* 反映が「約1分」ではなく「数分」になることだけを言う
|
|
17857
|
-
* - ready: false … 索引にまだ行が無い。設定しても届かないので手順を出さない
|
|
17858
|
-
* 「supported: false(固定 IP は未対応)」の枝は撤去した——固定 IP 案件でも Webhook を
|
|
17859
|
-
* 受ける(分類 A2)。その値を返す古い Lambda に当たっても、手順を出す側に倒す
|
|
18150
|
+
* リアルタイム同期(Backlog Webhook)の設定手順ひとそろい(#113)。**大きな箱では出さない**(c-07 の 2 本目):
|
|
18151
|
+
* 稼働中は行の札だけ。受信が無い・一部だけのときの一手[設定する]の中と、全部稼働中のときの「その他」の中に、
|
|
18152
|
+
* この同じ部品を置く。**URL が無い案件には呼ばない**(backlogRealtimeFacts が null)。
|
|
17860
18153
|
*
|
|
18154
|
+
* 🔴 **手順の部品は変えない**(貼り付け先・URL・コピー・チェックする 10 種・秘密の注意・反映の目安)。
|
|
18155
|
+
* チェック名は受信側が処理する 10 種を全部並べる(「等」で省くと、課題の 3 つだけにチェックして
|
|
18156
|
+
* 「設定した」と思い、Wiki とドキュメントの更新が夜間まで届かない)。増減するときは受信側
|
|
18157
|
+
*(backlog-webhook/logic.mjs の HANDLED_TYPES)と揃える——ずれると realtime-sync-card.test.mjs が赤くなる。
|
|
17861
18158
|
* **URLに載せるトークンは案件別**(SITE.backlogWebhook)。艦隊共通トークンを載せると、
|
|
17862
|
-
*
|
|
17863
|
-
*
|
|
17864
|
-
* **既に回っている案件には、手順より先に「動いている」ことを出す(#123)。** 設定手順は
|
|
17865
|
-
* まだ繋いでいない人のためのもので、繋ぎ終わった案件の担当者が知りたいのは
|
|
17866
|
-
* 「ちゃんと届いているか」。手順は畳んで残す(URLの再確認・張り替えに要る)。
|
|
18159
|
+
* この画面を見られる人が全案件ぶんの偽イベントを投げられる。
|
|
18160
|
+
* 固定 IP 経由か(#516)は **op=state の realtime.via**(受信を同期するのはサーバ側の索引が決める)で言う。
|
|
17867
18161
|
*/
|
|
17868
|
-
function
|
|
18162
|
+
function realtimeSetupNodes() {
|
|
17869
18163
|
var rt = LIVE_REALTIME;
|
|
17870
|
-
// 固定 IP 経由か(#516)。**経路の正本は op=state の realtime.via**——スイッチの状態
|
|
17871
|
-
//(LIVE_BACKLOG_ROUTE)から引かない。あちらは押した直後に先に変わるが、受信したイベントを
|
|
17872
|
-
// どちらが同期するかはサーバ側の索引が決めるので、同じ応答の値で言う
|
|
17873
18164
|
var viaFixedIp = !!(rt && rt.via === "fixed-ip");
|
|
17874
18165
|
var wh = SITE.backlogWebhook;
|
|
17875
|
-
|
|
17876
|
-
if (!wh || !wh.url || !wh.token) return null;
|
|
17877
|
-
// **索引に行があるか(ready)でカードごと隠さない(2026-08-24 オーナー決定)。**
|
|
17878
|
-
// それまでは「手順どおり設定すれば実際に効く案件だけに出す」としていたが、
|
|
17879
|
-
// ready の材料(ルーティング索引の行)は**設定を始める前には揃わない**ので、
|
|
17880
|
-
// これから設定する人ほど手順に辿り着けなかった。索引がまだ追いついていない案件も、
|
|
17881
|
-
// 取得前・取得失敗(rt が null)も、手順は出す——URLは案件専用で、先に貼っておいても
|
|
17882
|
-
// 困らない。ready のときだけ受信実績の行を足す
|
|
17883
|
-
var ready = !!(rt && rt.ready);
|
|
18166
|
+
if (!wh || !wh.url || !wh.token) return [];
|
|
17884
18167
|
var url = wh.url + (wh.url.indexOf("?") === -1 ? "?" : "&") + "t=" + encodeURIComponent(wh.token);
|
|
17885
|
-
// 接続先ごとの受信実績(#123)。**配列で受ける**——1案件=複数 Backlog プロジェクトは
|
|
17886
|
-
// 確定した将来要件(#107)で、今日は必ず1件。**時刻が読めない値は稼働中に数えない**
|
|
17887
|
-
// ——「最終受信: NaN分前」を出すくらいなら、未設定と同じ手順の画面のほうがまだ読める
|
|
17888
|
-
// **ready でないうちは受信実績を読まない。** ready の材料は索引の行(=自リポ宛だと
|
|
17889
|
-
// 確かめられた行)で、それが無いのに lastEventAt だけ来ている応答を信じると、
|
|
17890
|
-
// **届いていない案件に「✓ 稼働中」を出す**——一番出してはいけない嘘になる
|
|
17891
|
-
var targets = (ready && rt.targets ? rt.targets : []).map(function (t) {
|
|
17892
|
-
return { projectKey: t && t.projectKey ? String(t.projectKey) : "", ago: t && t.lastEventAt ? rtAgo(t.lastEventAt) : "" };
|
|
17893
|
-
});
|
|
17894
|
-
var live = targets.some(function (t) { return t.ago; });
|
|
17895
18168
|
|
|
17896
18169
|
// URLは**選択できる形**で出す(コピーボタンが使えない環境でも手で取れるように)
|
|
17897
18170
|
var field = el("input", { class: "sec-input rt-url", type: "text", readonly: "readonly", spellcheck: "false" });
|
|
@@ -17918,16 +18191,10 @@ ${calendarAddUrl.toString()}
|
|
|
17918
18191
|
} catch (e) { fallback(); }
|
|
17919
18192
|
});
|
|
17920
18193
|
|
|
17921
|
-
|
|
17922
|
-
var steps = [
|
|
18194
|
+
return [
|
|
17923
18195
|
el("ol", { class: "rt-steps" }, [
|
|
17924
18196
|
el("li", { text: "Backlog のプロジェクト設定 → インテグレーション → Webhook を開く" }),
|
|
17925
18197
|
el("li", { text: "「WebHook URL」に、下の(このプロジェクト専用の)URL を貼り付ける" }),
|
|
17926
|
-
// **チェック名は受信側が処理する10種を全部並べる。** 「等」で省くと、担当者は
|
|
17927
|
-
// 課題の3つだけにチェックして「設定した」と思い、Wikiとドキュメントの更新は
|
|
17928
|
-
// 夜間まで届かない(画面は稼働中と出るので、欠けていることに気づけない)。
|
|
17929
|
-
// ここを増減するときは受信側(backlog-webhook/logic.mjs の HANDLED_TYPES)と
|
|
17930
|
-
// 揃える——ずれると realtime-sync-card.test.mjs が赤くなる。
|
|
17931
18198
|
// **「他はチェック不要」とは言い切らない。** 未対応の type(ドキュメントの更新等)は
|
|
17932
18199
|
// 「無視した」ログから見つける設計なので、外させると発見の経路が塞がる
|
|
17933
18200
|
el("li", { text: "通知イベントで次の10個にチェックを入れて保存する(他のイベントはチェックしても取り込まれません)" }, [
|
|
@@ -17940,72 +18207,26 @@ ${calendarAddUrl.toString()}
|
|
|
17940
18207
|
]),
|
|
17941
18208
|
el("div", { class: "rt-url-row" }, [field, copyBtn]),
|
|
17942
18209
|
msg,
|
|
17943
|
-
// **この画面は顧客も見る。** URL
|
|
18210
|
+
// **この画面は顧客も見る。** URLに秘密が入っていることと、その扱いを書いておく(秘密の扱いの文は縮めない)
|
|
17944
18211
|
el("p", { class: "sec-note", text: "このURLはこのプロジェクト専用です(他のプロジェクトには届きません)。秘密のトークンを含むので、貼り付け先は、いま設定している Backlog プロジェクトの設定欄だけにしてください。" }),
|
|
17945
|
-
// 固定 IP 経由の案件は反映が「約1
|
|
17946
|
-
// **同じ行を差し替える**——2 行に分けると「約1分」と「数分」が並んで矛盾する
|
|
18212
|
+
// 固定 IP 経由の案件は反映が「約1分」にならない。**同じ行を差し替える**(2 行に分けると矛盾する)
|
|
17947
18213
|
el("p", { class: "sec-how", text: viaFixedIp
|
|
17948
18214
|
? "固定 IP 経由で同期します(反映は受信から数分後です)。初回は上の接続先の設定と、夜間同期が一度済んでいることが前提になります。"
|
|
17949
18215
|
: "反映は受信から約1分後です(1分ごとのミニバッチでまとめて取り込みます)。初回は上の接続先の設定と、夜間同期が一度済んでいることが前提になります。" }),
|
|
17950
18216
|
];
|
|
18217
|
+
}
|
|
17951
18218
|
|
|
17952
|
-
|
|
17953
|
-
|
|
17954
|
-
|
|
17955
|
-
|
|
17956
|
-
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
|
|
17962
|
-
|
|
17963
|
-
var label = targets.length > 1 && t.projectKey ? t.projectKey + ": " : "";
|
|
17964
|
-
// 受信実績がまだ無い接続先も**黙って落とさない**。1つでも動いていれば
|
|
17965
|
-
// カードは稼働中の形になるので、落とすと「増やした接続先が来ていない」に気づけない
|
|
17966
|
-
return el("p", {
|
|
17967
|
-
class: t.ago ? "rt-live-line" : "rt-live-line is-waiting",
|
|
17968
|
-
text: t.ago ? label + "最終受信: " + t.ago : label + "まだ受信していません",
|
|
17969
|
-
});
|
|
17970
|
-
}));
|
|
17971
|
-
return el("div", { class: "ops-card rt-card is-live" }, [
|
|
17972
|
-
el("div", { class: "ops-card-head" }, [
|
|
17973
|
-
el("span", { class: "ops-card-icon", text: "⚡" }),
|
|
17974
|
-
// **「(任意)」を付ける(2026-08-21・オーナー指摘「リアルタイム同期は
|
|
17975
|
-
// オプションなので、一番下に配置してほしい」)。** 設定しなくても毎晩の同期で
|
|
17976
|
-
// 反映されるので、必須の手順と並べると「まだ終わっていない」と読まれる。
|
|
17977
|
-
// ⚠ **改名箇所は2つある**(稼働中の枝と未設定の枝)。片方だけ直すと、
|
|
17978
|
-
// **これから設定する人にだけ「(任意)」が付かない**=一番読ませたい人に届かない
|
|
17979
|
-
el("h3", { class: "ops-card-title", text: "リアルタイム同期(任意)" }),
|
|
17980
|
-
el("span", { class: "rt-live-badge", text: "✓ 稼働中" }),
|
|
17981
|
-
]),
|
|
17982
|
-
lines,
|
|
17983
|
-
// 固定 IP 経由の案件は「すぐ」ではなく「数分」(手順は畳んであるので、ここでも言う)
|
|
17984
|
-
el("p", { class: "ops-card-desc", text: viaFixedIp
|
|
17985
|
-
? "Backlog の変更を固定 IP 経由で取り込んでいます(反映まで数分・毎晩の同期を待ちません)。設定は済んでいるので、このままお使いください。"
|
|
17986
|
-
: "Backlog の変更をすぐ取り込んでいます(毎晩の同期を待ちません)。設定は済んでいるので、このままお使いください。" }),
|
|
17987
|
-
det,
|
|
17988
|
-
]);
|
|
17989
|
-
}
|
|
17990
|
-
|
|
17991
|
-
// まだ受け入れ体制が整っていない案件(接続先がこれから・索引が追いついていない)には、
|
|
17992
|
-
// **次の一手だけを1行足す。** 担当者への連絡は促さない——この画面で完結する話だから
|
|
17993
|
-
var head = [
|
|
17994
|
-
el("div", { class: "ops-card-head" }, [
|
|
17995
|
-
el("span", { class: "ops-card-icon", text: "⚡" }),
|
|
17996
|
-
// **稼働中の枝と同じ見出しにする**(上の ⚠ 参照。片方だけ直すと、
|
|
17997
|
-
// これから設定する人にだけ「(任意)」が付かない)
|
|
17998
|
-
el("h3", { class: "ops-card-title", text: "リアルタイム同期(任意)" }),
|
|
17999
|
-
]),
|
|
18000
|
-
el("p", { class: "ops-card-desc", text: "Backlog で課題やコメントが変わったときに、その内容をすぐ取り込みます(毎晩の同期を待たずに反映されます)。Backlog 側で一度だけ設定してください。" }),
|
|
18001
|
-
];
|
|
18002
|
-
if (!ready) {
|
|
18003
|
-
head.push(el("p", {
|
|
18004
|
-
class: "sec-how",
|
|
18005
|
-
text: "上の「接続先」を先に設定してから貼り付けてください。設定した直後は、この画面が追いつくまで少し時間がかかることがあります。",
|
|
18006
|
-
}));
|
|
18007
|
-
}
|
|
18008
|
-
return el("div", { class: "ops-card rt-card" }, head.concat(steps));
|
|
18219
|
+
/**
|
|
18220
|
+
* リアルタイム同期の「いまの様子」。専用 URL が無い案件・投入の口が無い案件・接続先が 0 件のあいだは null(何も出さない)。
|
|
18221
|
+
* total=行の数、live=そのキーの受信実績がある行の数(ready かつ時刻が読めるものだけ)。
|
|
18222
|
+
*/
|
|
18223
|
+
function backlogRealtimeState() {
|
|
18224
|
+
var facts = backlogRealtimeFacts();
|
|
18225
|
+
var rows = BACKLOG_UI.rows || [];
|
|
18226
|
+
if (!facts || !SITE.intake || !rows.length) return null;
|
|
18227
|
+
var live = 0;
|
|
18228
|
+
for (var i = 0; i < rows.length; i++) if (backlogRowReceiptAgo(facts, rows[i].projectKey)) live++;
|
|
18229
|
+
return { facts: facts, total: rows.length, live: live };
|
|
18009
18230
|
}
|
|
18010
18231
|
|
|
18011
18232
|
/**
|
|
@@ -18181,6 +18402,33 @@ ${calendarAddUrl.toString()}
|
|
|
18181
18402
|
return !!((fleetVal("secretSources") || {})[kind]);
|
|
18182
18403
|
}
|
|
18183
18404
|
|
|
18405
|
+
/**
|
|
18406
|
+
* 鍵カードの登録状況の 1 行を、答え(secretStateOf)に合わせる。**作るときと、見せる瞬間の当て直し
|
|
18407
|
+
* (Backlog の行の中のカード・c-07 の 2 本目)が同じこの 1 本を通る**——文言が 2 か所に割れない。
|
|
18408
|
+
* 🔴 **画面に出すのは「登録済み」の 1 語だけ(#841)。** 取得元の値は仕組みの名前
|
|
18409
|
+
*(保管庫のサービス名・旧名の区別)で、読む人が自分の画面で探せない。値に関わらず同じ 1 語にし、
|
|
18410
|
+
* **生の値は data-src に置く**(判定の is-managed は生の値のまま。前方一致で見る=旧名の移行期の取りこぼし防止)。
|
|
18411
|
+
* 🔴 **「登録済み」は「読めている」を意味しない。** 「接続OK」「同期OK」とは書かない。
|
|
18412
|
+
* 🔴 **断定しない(レビュー指摘)。** 黙って消すと、利用者は「入っているのか未登録なのか」も
|
|
18413
|
+
* 「なぜ表示が無いのか」も知る手段が無い。
|
|
18414
|
+
*/
|
|
18415
|
+
function applySecretState(node, st) {
|
|
18416
|
+
if (st.known === "yes") {
|
|
18417
|
+
var src = st.src || "";
|
|
18418
|
+
node.className = "sec-state" + (src.indexOf("Secrets Manager") === 0 ? " is-managed" : "");
|
|
18419
|
+
node.setAttribute("data-src", src);
|
|
18420
|
+
node.textContent = "登録済み";
|
|
18421
|
+
} else if (st.known === "no") {
|
|
18422
|
+
node.className = "sec-state is-none";
|
|
18423
|
+
node.removeAttribute("data-src");
|
|
18424
|
+
node.textContent = "未登録";
|
|
18425
|
+
} else {
|
|
18426
|
+
node.className = "sec-state is-unknown";
|
|
18427
|
+
node.removeAttribute("data-src");
|
|
18428
|
+
node.textContent = "登録状況を確認中です(スペースごとの状況は、次回の自動チェックのあとに表示されます)";
|
|
18429
|
+
}
|
|
18430
|
+
}
|
|
18431
|
+
|
|
18184
18432
|
/**
|
|
18185
18433
|
* @param opts { space, stateSpace, spaceCount, inRow }
|
|
18186
18434
|
* space … 保存時に名乗る Backlog スペース(名乗らないときは空)
|
|
@@ -18193,6 +18441,10 @@ ${calendarAddUrl.toString()}
|
|
|
18193
18441
|
var stateSpace = (opts && opts.stateSpace) || "";
|
|
18194
18442
|
var spaceCount = (opts && opts.spaceCount) || 0;
|
|
18195
18443
|
var inRow = !!(opts && opts.inRow);
|
|
18444
|
+
// 🔴 **Backlog の行の中のカードは新しい見え方(c-07 の 2 本目)。** 見出しを持たず(行の中なので「何の鍵か」は
|
|
18445
|
+
// 一手の文が言う)、ふだんは隠れていて、キーが無いと分かったとき・[入れる]/[入れ直す]を押したとき・
|
|
18446
|
+
// 触り始めた後だけ見える(syncKeyCardVisibility)。旧カード(行の外・intake 無しの案件)と Figma は従来どおり。
|
|
18447
|
+
var compact = kind === "backlog" && inRow;
|
|
18196
18448
|
/**
|
|
18197
18449
|
* 鍵がどこまで効くのか(#229)。**置き場所だけでは伝わらないので、言葉にする。**
|
|
18198
18450
|
*
|
|
@@ -18207,7 +18459,6 @@ ${calendarAddUrl.toString()}
|
|
|
18207
18459
|
// 画面で分かるようにする(接続状況は夜間バッチのスナップショットなので、
|
|
18208
18460
|
// 保存の直後は前日の値のまま。時刻は上の節に出している)。
|
|
18209
18461
|
var st = secretStateOf(kind, stateSpace, spaceCount);
|
|
18210
|
-
var src = st.known === "yes" ? st.src : "";
|
|
18211
18462
|
var cardTitle = meta.title;
|
|
18212
18463
|
// 🔴 **「登録済み」は「読めている」を意味しない。** 鍵が入っていても、そのアカウントから
|
|
18213
18464
|
// プロジェクトが見えなければ同期は失敗する。**「接続OK」「同期OK」とは書かないこと**
|
|
@@ -18221,16 +18472,8 @@ ${calendarAddUrl.toString()}
|
|
|
18221
18472
|
//(保管庫のサービス名・旧名の区別)で、読む人が自分の画面で探せない。値に関わらず
|
|
18222
18473
|
// 同じ 1 語にし、**生の値は data-src に置く**(判定の is-managed は生の値のまま)。
|
|
18223
18474
|
// 知らない値が来ても生で出さない——project-wording.test.mjs が画面の語を見張る
|
|
18224
|
-
var state =
|
|
18225
|
-
|
|
18226
|
-
"data-src": src,
|
|
18227
|
-
text: "登録済み" })
|
|
18228
|
-
: st.known === "no"
|
|
18229
|
-
? el("p", { class: "sec-state is-none", text: "未登録" })
|
|
18230
|
-
// **断定しない(レビュー指摘)。** 黙って消すと、利用者は
|
|
18231
|
-
// 「入っているのか未登録なのか」も「なぜ表示が無いのか」も知る手段が無い
|
|
18232
|
-
: el("p", { class: "sec-state is-unknown",
|
|
18233
|
-
text: "登録状況を確認中です(スペースごとの状況は、次回の自動チェックのあとに表示されます)" });
|
|
18475
|
+
var state = el("p", { class: "sec-state" });
|
|
18476
|
+
applySecretState(state, st);
|
|
18234
18477
|
|
|
18235
18478
|
var input = el("input", { class: "sec-input", type: "password", placeholder: "トークンを貼り付け", autocomplete: "off" });
|
|
18236
18479
|
var btn = el("button", { class: "ds-toggle-btn", type: "button", text: "保存" });
|
|
@@ -18274,6 +18517,12 @@ ${calendarAddUrl.toString()}
|
|
|
18274
18517
|
// 入れた値は画面に残さない(肩越しに見えるのを防ぐ)
|
|
18275
18518
|
input.value = "";
|
|
18276
18519
|
endBusy();
|
|
18520
|
+
// 🔴 **Backlog の行の中のカードは、保存したら行の一手を当て直す**(「API キーを入れてください」を
|
|
18521
|
+
// 登録状況の正本に合わせて消す・c-07 の 2 本目)。正本(secretsNow)は取り直しで届く
|
|
18522
|
+
if (compact) {
|
|
18523
|
+
if (typeof BACKLOG_UI.repaintRows === "function") BACKLOG_UI.repaintRows();
|
|
18524
|
+
loadLiveFleet(true);
|
|
18525
|
+
}
|
|
18277
18526
|
} catch (e) {
|
|
18278
18527
|
msg.className = "ds-toggle-msg is-error";
|
|
18279
18528
|
msg.textContent = "✗ 通信に失敗しました。";
|
|
@@ -18315,10 +18564,14 @@ ${calendarAddUrl.toString()}
|
|
|
18315
18564
|
// input のノードを直接触るし、テストも card.querySelector("input.sec-input") で掴む。
|
|
18316
18565
|
// ⚠ **<summary> は button ではない**ので、「保存」を掴む既存の取り方は変わらない。
|
|
18317
18566
|
var inputFold = el("details", { class: "sec-input-fold" }, [
|
|
18318
|
-
|
|
18567
|
+
// Backlog の行の中のカードは、一手の語([入れる]・[入れ直す])と揃える。Figma は「登録する」のまま
|
|
18568
|
+
el("summary", { class: "sec-input-summary", text: st.known === "yes" ? "入れ直す" : (compact ? "入れる" : "登録する") }),
|
|
18319
18569
|
]);
|
|
18320
18570
|
inputFold.appendChild(input);
|
|
18321
18571
|
inputFold.appendChild(el("div", { class: "ds-toggle" }, [btn, msg]));
|
|
18572
|
+
// 🔴 **値の扱いの 1 文は、開いた中に残す**(顧客に鍵の貼り付けを求める画面でいちばん落としてはいけない説明・#101)。
|
|
18573
|
+
// 短くした版(行の中のカード)。「他のプロジェクトには影響しません」は仕組みの事実で、利用者が選ぶものではないので外す
|
|
18574
|
+
if (compact) inputFold.appendChild(el("p", { class: "sec-note", text: "入れた値は保存だけでき、あとから表示されません。" }));
|
|
18322
18575
|
|
|
18323
18576
|
// ライブ取得後に状態の行だけを差し替えるための目印(カードごと作り直すと入力が消える)
|
|
18324
18577
|
//
|
|
@@ -18329,8 +18582,10 @@ ${calendarAddUrl.toString()}
|
|
|
18329
18582
|
// 押すまで見えないと成立しない
|
|
18330
18583
|
// - .sec-preferred … 「誰のアカウントで発行するのか」を**手順より先に**伝える行。
|
|
18331
18584
|
// 未登録のときだけ出る=これから貼る人にだけ出るので、畳むと存在理由が消える
|
|
18585
|
+
var preferred = meta.selfServe ? el("p", { class: "sec-preferred", text: meta.selfServe.text }) : null;
|
|
18332
18586
|
var card = el("div", { class: "ops-card" + (inRow ? " ds-keycard" : ""), "data-secret-kind": kind }, [
|
|
18333
|
-
|
|
18587
|
+
// 行の中の Backlog カードは見出しを持たない(何の鍵かは一手の文が言う)
|
|
18588
|
+
compact ? null : el("div", { class: "ops-card-head" }, [
|
|
18334
18589
|
el("span", { class: "ops-card-icon", text: meta.icon }),
|
|
18335
18590
|
el("h3", { class: "ops-card-title", text: cardTitle }),
|
|
18336
18591
|
]),
|
|
@@ -18344,19 +18599,39 @@ ${calendarAddUrl.toString()}
|
|
|
18344
18599
|
// **未登録のときだけ、先に試すべき手を出す。** 既に動いているカードに
|
|
18345
18600
|
// 「まずこれをやれ」と出すと、直す必要が無いものを触らせることになる。
|
|
18346
18601
|
// **「確認中」にも出さない**——入っているかもしれないものを触らせない
|
|
18347
|
-
st.known === "no"
|
|
18602
|
+
st.known === "no" ? preferred : null,
|
|
18348
18603
|
inputFold,
|
|
18349
18604
|
guidanceBox,
|
|
18350
18605
|
// **扱いの説明はカードに持たせる。** 節の見出しの下に置いていたときは、
|
|
18351
18606
|
// #101 でカードだけがモーダルへ移り、説明だけが画面から消えた。
|
|
18352
18607
|
// 顧客にトークンの貼り付けを求める画面で、貼った値がどう扱われるかの
|
|
18353
18608
|
// 説明が落ちるのは、いちばん落としてはいけない種類の欠落
|
|
18354
|
-
|
|
18609
|
+
// 行の中の Backlog カードは、同じ 1 文を開いた中(上の inputFold)に短くして持つ
|
|
18610
|
+
compact ? null : el("p", { class: "sec-note", text: "入れた値はここには表示されません(保存だけできます)。保存先はプロジェクトごとに分かれているので、他のプロジェクトには影響しません。" }),
|
|
18355
18611
|
]);
|
|
18356
18612
|
// **作った鍵カードは1枚残らずここに載せる。** 出し入れ(接続先が0件のときに畳む)は
|
|
18357
18613
|
// DOM 検索ではなくこの控えで回す——カードは能力モーダルの中身で、タイルを押すまで
|
|
18358
|
-
// 文書に入っていないため(syncKeyCardVisibility
|
|
18359
|
-
|
|
18614
|
+
// 文書に入っていないため(syncKeyCardVisibility の項)。
|
|
18615
|
+
// compact(Backlog の行の中)のカードは、見え方の規則(backlogKeyCardWanted)が要る材料を一緒に持つ:
|
|
18616
|
+
// space=状態を引くスペース(行のドメイン。名無しの受け皿は空文字)・forced=一手・「その他」から開かれた・
|
|
18617
|
+
// fold/input/msg=触り始めたかの判定と、入力への移動・refresh=見せる瞬間の状態の当て直し
|
|
18618
|
+
var entry = { kind: kind, el: card, compact: compact, space: stateSpace, forced: false, fold: inputFold, input: input, msg: msg };
|
|
18619
|
+
if (compact) {
|
|
18620
|
+
entry.refresh = function () {
|
|
18621
|
+
// 作ったときの答えは古いことがある(取り直しが後から届く)。見せる瞬間に状態の行と summary と
|
|
18622
|
+
// 「自分で繋げばよい」の 1 行を当て直す(入れた直後に前の答えのまま出さない・#227)
|
|
18623
|
+
var now = secretStateOf(kind, stateSpace, backlogDomainsNow().length);
|
|
18624
|
+
applySecretState(state, now);
|
|
18625
|
+
var sum = inputFold.querySelector(".sec-input-summary");
|
|
18626
|
+
if (sum) sum.textContent = now.known === "yes" ? "入れ直す" : "入れる";
|
|
18627
|
+
if (preferred) {
|
|
18628
|
+
var want = now.known === "no";
|
|
18629
|
+
if (want && !preferred.parentNode) card.insertBefore(preferred, inputFold);
|
|
18630
|
+
else if (!want && preferred.parentNode) preferred.parentNode.removeChild(preferred);
|
|
18631
|
+
}
|
|
18632
|
+
};
|
|
18633
|
+
}
|
|
18634
|
+
KEY_CARDS.push(entry);
|
|
18360
18635
|
return card;
|
|
18361
18636
|
}
|
|
18362
18637
|
|