概要 — 何をする画面か
役割 と チーム は、人を分類するための 情報用ラベル です。画面と操作へのアクセスを開く唯一の要素は レベル(user_level) です。ユーザーが何を見られるか、何を保存・削除できるかは 100% レベル で決まります。役割(「DBA」「DevOps」「セキュリティ」)やチーム(「インフラチーム」「セキュリティチーム」)は 表示・組織のためのタグ であり、権限は一切付与しません。
これにより権限モデルはシンプルに保たれます。誰かに管理者権限を与えるには レベルを上げます — 役割を変えるのではありません。ユーザーに「セキュリティ」のタグを付けてもセキュリティ画面は開きません。そのためにはレベルがスタッフ(5)以上である必要があります。
この記事では次の3点を扱います。
- 役割(
infr_role) — 6種類の職務ラベル(DBA、DevOps、セキュリティ、管理者、スタッフ、開発者)。 - チーム(
infr_team) — 組織単位。ユーザーの所属と資産の担当割り当てに使います。 - 所有者ピッカー — ユーザーとチームのどちらでも割り当てられる統合ドロップダウン。単一の情報源(SOT)は
App\Support\Ownersです。インスタンス・ハードウェア・ストレージの「担当(所有者)」項目がこれを使います。
アクセス制御 = レベル(
user_level)のみ。役割とチームはプログラム的に権限を開きません。レベルの体系は アクセスレベル(RBAC) を参照してください。
こんなときに使う
- 新しいメンバーを登録し、職務(役割)と所属(チーム)のタグを付けるとき。
- 組織変更 — チームを作成する、リーダーを変更するとき。
- サーバー・ハードウェア・ストレージの資産に 担当(所有者) を人またはチームで割り当て、「これは誰の責任か」を明確にするとき。
- 監査・引き継ぎ — 「この資産の担当は誰か」を調べるとき。
画面構成
/admin/users(管理 ▸ ユーザー・権限)には2つのタブがあります。
| タブ | 内容 |
|---|---|
| ユーザー | ユーザー一覧(ログイン ID · 氏名 · メール · ロール · チーム · レベル · 状態 · 最終ログイン)+ モーダル CRUD |
| チーム | チーム一覧(名称 · 色 · リーダー · 説明 · メンバー数)+ モーダル CRUD |
ユーザー編集モーダルにはレベル/ロール/チームの選択欄があり、その下に次のヒントが表示されます。
レベルがアクセス権を決めます — デモ(閲覧)、スタッフ(運用)、管理者(すべて)。ロールは表示用のラベルです。
使い方
役割 — 職務ラベル
役割マスタは6件で、InfrRoleSeeder が key を基準に冪等に upsert します。
| key | ラベル | 色トークン | seq |
|---|---|---|---|
dba |
DBA | info | 10 |
devops |
DevOps | accent | 20 |
security |
セキュリティ | crit | 30 |
admin |
管理者 | warn | 40 |
staff |
スタッフ | muted | 50 |
developer |
開発者 | good | 60 |
- 1ユーザーが持てる役割は 最大1つ です(
users.role_id、NULL 可)。未設定でも構いません。 - 役割は、上部バーのユーザードロップダウンと一覧のバッジにラベル・色として表示されるだけです。
- 表示ラベルは
App\Support\RolePolicy::roleLabel($roleId)が60秒キャッシュで解決します(変更後はRolePolicy::flush()を呼びます)。
役割の
admin(ラベル「管理者」)は 管理者レベル(10)とは何の関係もありません。役割は単なるラベルなので、役割が「管理者」でもレベルがデモ(1)のユーザーは何も保存できません。逆に、レベル10の管理者は役割が空でも管理機能をすべて使えます。
チーム — 組織単位
チームは チーム タブで自由に CRUD できます。シードされる4チームは次のとおりです。
| 名称 | 色 | 説明 |
|---|---|---|
| インフラチーム | accent | サーバー・ネットワーク・ストレージ運用 |
| セキュリティチーム | crit | 検知 · 対応 · 遮断 |
| 開発チーム | good | サービス開発 · デプロイ |
| DBA チーム | info | データベース運用 · チューニング |
チーム編集項目:
| 項目 | 意味 |
|---|---|
名称(name) |
必須。チーム名 |
色(color) |
バッジの色調(accent/crit/good/info/muted …) |
リーダー(lead_user_id) |
ユーザー1名(任意) |
説明(description) |
自由記述 |
有効(is_active) |
無効のチームは所有者ピッカーに表示されません |
- 1ユーザーが所属できるチームは 最大1つ です(
users.team_id、NULL 可)。 - チーム削除時: そのチームのメンバーの
team_idはnullになります(ユーザー自体は残り、未所属になります)。owner_type='team'でそのチームを参照していた資産は別の話なので、資産そのものは削除されません。
所有者ピッカー — ユーザーとチームの統合
資産(サーバーインスタンス、ハードウェア、ストレージ)には「担当(所有者)」項目があり、人でもチームでも 指定できます。この統合ピッカーのルールは App\Support\Owners(SOT)にあります。
保存形式 = 2カラム
資産テーブルは所有者を2つのカラムに保存します。
| カラム | 意味 |
|---|---|
owner_id |
ユーザーまたはチームの整数 PK |
owner_type |
'user' または 'team' |
フロントエンドの受け渡し形式 = 単一の ref 文字列
ブラウザ(フォーム・フィルター)は owner_type と owner_id を1つの文字列としてやり取りします。
user:5 → user id 5
team:3 → team id 3
ブートストラップの選択肢(Owners::options())は optgroup 描画用に2つのグループを返します(20秒キャッシュ)。クローズされたユーザー(is_close=1)と無効のチーム(is_active=0)は除外されます。
{
"users": [ { "ref": "user:5", "id": 5, "name": "Jane Doe" } ],
"teams": [ { "ref": "team:3", "id": 3, "name": "Infra Team" } ]
}
サーバー側の分解・再構築
| ヘルパー | 挙動 |
|---|---|
Owners::split($ref) |
"user:5" → ['user', 5]。不正な形式 → ['user', 0](= 未割り当て) |
Owners::ref($ownerId, $ownerType) |
(5, 'user') → "user:5"。ownerId が空 → null |
Owners::resolveName($ownerId, $ownerType) |
カード・一覧用の表示名(該当なしなら null、20秒キャッシュ) |
Owners::flush() |
ユーザー・チーム変更後にキャッシュを無効化 |
たとえばインスタンスの保存コントローラーは次のようにしています。
// 所有者 ref("user:5"/"team:3")→ owner_id + owner_type
[$ownerType, $ownerId] = Owners::split($request->input('owner_ref'));
$payload['owner_id'] = $ownerId ?: null;
$payload['owner_type'] = $ownerId ? $ownerType : 'user'; // 未設定時の既定は 'user'
同じ ref は所有者による 絞り込み にも使われます。一覧クエリは owner パラメーター(user:5/team:3)を split() で分解し、WHERE owner_id = ? AND owner_type = ? に絞り込みます。
所有者ピッカーは インスタンス、ハードウェア、ストレージ・ファイル の各資産画面でまったく同じものが再利用されています。チームを所有者に指定すると、責任の所在がチーム全体になります(誰か1人が抜けても揺らぎません)。
アバター · イニシャル · 色
ラベルの横に表示されるアバターは、氏名から RolePolicy が生成します。
- イニシャル(
initials):CJK・韓国語 → 先頭の1文字、ラテン文字の氏名 → 最初と最後の単語の頭文字(2文字)。 - 色相(
hue):crc32(name) % 360により HSL 背景色を安定して決めます。 - レベルの色調(
tierTone):管理者(10以上)= crit · スタッフ/運用(2以上)= accent · デモ = muted。
データソース
| テーブル | 用途 |
|---|---|
infr_role |
役割マスタ(情報用ラベル6件)— key·name·color·seq·is_active |
infr_team |
チームマスタ — name·color·lead_user_id·description·is_active·seq |
users |
ユーザー — user_level(アクセスを決定)、role_id(NULL 可)、team_id(NULL 可) |
| 資産テーブル | 所有者を owner_id + owner_type(user/team)の2カラムで保存(インスタンス/ハードウェア/ストレージなど) |
SOT ヘルパー:
App\Support\Owners— 統合された所有者ピッカー(options/split/ref/resolveName/flush)。App\Support\RolePolicy— レベルのラベル・色調、役割ラベル(60秒キャッシュ)、アバターのイニシャル・色。
アクセス権限
- 画面全体への入口は管理者専用です。
/admin/*はグループミドルウェアinfr.level:10で保護され、UserController/TeamController自身もセッションのuser_level >= 10(RolePolicy::ADMIN)を再確認します。デモ(1)とスタッフ(5)は 403 になります。 - レベルのしきい値(参考):書き込み ≥ 2(
RolePolicy::WRITE、デモを遮断)· 管理 ≥ 10(ADMIN)· 開発・観測 ≥ 9(DEV、管理者10を含む)。 - ユーザー保存時、
user_levelは1,5,10のいずれかに制限されます。 - ユーザーとチームの変更はすべて
infr_audit_logに監査記録されます(team.create/team.update/team.delete、ユーザーの保存・削除など)。
ガード(最後の管理者の保護)
| 状況 | 結果 |
|---|---|
| 最後の有効な管理者を降格・無効化・削除する | 拒否 — 「最後の有効な管理者は降格・無効化・削除できません。」 |
| 自分の管理者レベルを下げる・無効化する | 拒否 — 「自分の管理者権限を降格・無効化することはできません。」 |
| 自分自身を削除する | 拒否 — 「自分自身を削除することはできません。」 |
| ログイン ID が既に使われている | 拒否 — 「そのログイン ID は既に使われています。」 |
トラブルシューティング · FAQ
Q. 「セキュリティ」の役割を付けたのにセキュリティ画面が開きません。 役割はラベルであり、権限を開きません。セキュリティ画面(ほとんどが閲覧)には レベル が必要です — スタッフ(5)以上に引き上げてください。RBAC レベル を参照してください。
Q. 所有者のドロップダウンに出てこないユーザー・チームがあります。
クローズされたユーザー(is_close=1)と無効のチーム(is_active=0)はピッカーから除外されます。該当のユーザー・チームを再度有効にしてください。一覧が古いままの場合は20秒キャッシュの期限切れを待ちます(Owners::flush() で即座に無効化できます)。
Q. チームを削除すると、そのメンバーも消えますか。
いいえ。ユーザーは残り、team_id が null(未所属)になるだけです。資産の owner_type='team' 参照は別なので、資産も削除されません(所有者の表示が空になるだけです)。
Q. 所有者を保存したのに名前が表示されません。
resolveName() は20秒キャッシュです。名称を変更した直後であれば、まもなく反映されます。あわせて ref の形式が user:5/team:3 になっているか確認してください。形式が壊れていると split() は未割り当て(user:0)として扱います。
Q. 1人のユーザーに役割を2つ持たせられますか。
いいえ。role_id は単一の値(NULL 可)です。組織的なグルーピングも必要なら、チーム(これも単一の値)を使ってください。
関連項目
- アクセスレベル(RBAC) — 実際にアクセスを決める唯一の軸
- ユーザー・チーム管理画面 —
/admin/usersの CRUD 詳細 - インスタンス — 所有者ピッカーを使う代表的な資産画面
- ハードウェア — 物理資産の所有者
- ストレージ・ファイル — ストレージ資産の所有者
- 監査ログ — ユーザー・チーム・所有者の変更履歴
