Skip to content

Commit 51ad349

Browse files
committed
Merge remote-tracking branch 'origin/audit-log-view' into preview
2 parents a3a9716 + b84ec1c commit 51ad349

21 files changed

Lines changed: 3441 additions & 249 deletions

cabotage/client/static/main.css

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2149,6 +2149,153 @@ a.pipeline-stage-body:hover {
21492149
font-size: inherit;
21502150
}
21512151

2152+
/* Audit Log — Filters */
2153+
.audit-filters { margin-bottom: 1rem; }
2154+
.audit-filter-pills {
2155+
display: flex;
2156+
align-items: center;
2157+
gap: 0.375rem;
2158+
flex-wrap: wrap;
2159+
}
2160+
.audit-filter-remove {
2161+
all: unset;
2162+
cursor: pointer;
2163+
opacity: 0.5;
2164+
display: inline-flex;
2165+
}
2166+
.audit-filter-remove:hover { opacity: 1; }
2167+
2168+
/* Audit Log */
2169+
.audit-list {
2170+
display: flex;
2171+
flex-direction: column;
2172+
border: 1px solid var(--border-color);
2173+
border-radius: var(--rounded-box, 0.5rem);
2174+
overflow: hidden;
2175+
}
2176+
.audit-row {
2177+
display: flex;
2178+
align-items: flex-start;
2179+
gap: 0.75rem;
2180+
padding: 0.75rem 1rem;
2181+
border-bottom: 1px solid var(--border-color);
2182+
font-size: 0.8125rem;
2183+
}
2184+
.audit-row:last-child { border-bottom: none; }
2185+
.audit-row-icon { flex-shrink: 0; padding-top: 0.125rem; }
2186+
.audit-verb {
2187+
display: inline-flex;
2188+
align-items: center;
2189+
justify-content: center;
2190+
width: 1.5rem;
2191+
height: 1.5rem;
2192+
border-radius: 50%;
2193+
background: oklch(var(--bc) / 0.06);
2194+
color: oklch(var(--bc) / 0.4);
2195+
}
2196+
.audit-verb-create { background: rgba(34, 197, 94, 0.1); color: rgba(34, 197, 94, 0.7); }
2197+
.audit-verb-edit { background: rgba(59, 130, 246, 0.1); color: rgba(59, 130, 246, 0.7); }
2198+
.audit-verb-delete { background: rgba(239, 68, 68, 0.1); color: rgba(239, 68, 68, 0.7); }
2199+
.audit-verb-deploy { background: rgba(124, 58, 237, 0.1); color: rgba(124, 58, 237, 0.7); }
2200+
.audit-verb-complete { background: rgba(34, 197, 94, 0.1); color: rgba(34, 197, 94, 0.7); }
2201+
.audit-row-body { flex: 1; min-width: 0; }
2202+
.audit-row-primary {
2203+
display: flex;
2204+
align-items: center;
2205+
gap: 0.375rem;
2206+
flex-wrap: wrap;
2207+
}
2208+
.audit-actor {
2209+
display: inline-flex;
2210+
align-items: center;
2211+
gap: 0.25rem;
2212+
font-weight: 600;
2213+
color: var(--text-bright);
2214+
}
2215+
.audit-actor strong { font-weight: 600; }
2216+
.audit-action {
2217+
padding: 0.1rem 0.4rem;
2218+
border-radius: 0.25rem;
2219+
background: oklch(var(--bc) / 0.06);
2220+
font-size: 0.75rem;
2221+
font-weight: 500;
2222+
color: oklch(var(--bc) / 0.6);
2223+
}
2224+
.audit-object-type {
2225+
font-weight: 500;
2226+
color: oklch(var(--bc) / 0.5);
2227+
}
2228+
.audit-detail {
2229+
color: oklch(var(--bc) / 0.4);
2230+
font-size: 0.75rem;
2231+
}
2232+
.audit-context {
2233+
font-size: 0.7rem;
2234+
color: oklch(var(--bc) / 0.3);
2235+
font-style: italic;
2236+
}
2237+
.audit-detail-code {
2238+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
2239+
font-size: 0.75rem;
2240+
color: oklch(var(--a));
2241+
background: oklch(var(--a) / 0.06);
2242+
padding: 0.05rem 0.3rem;
2243+
border-radius: 0.2rem;
2244+
}
2245+
.audit-meta {
2246+
display: flex;
2247+
align-items: center;
2248+
gap: 0.375rem;
2249+
margin-top: 0.25rem;
2250+
flex-wrap: wrap;
2251+
}
2252+
.audit-meta-item {
2253+
display: inline-flex;
2254+
align-items: center;
2255+
gap: 0.2rem;
2256+
font-size: 0.7rem;
2257+
color: oklch(var(--bc) / 0.4);
2258+
}
2259+
.audit-row-time {
2260+
display: flex;
2261+
align-items: center;
2262+
gap: 0.5rem;
2263+
font-size: 0.6875rem;
2264+
color: oklch(var(--bc) / 0.3);
2265+
margin-top: 0.125rem;
2266+
}
2267+
.audit-remote-addr {
2268+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
2269+
font-size: 0.625rem;
2270+
color: oklch(var(--bc) / 0.2);
2271+
}
2272+
.audit-diff {
2273+
margin: 0.125rem 0 0;
2274+
padding: 0.125rem 0.5rem;
2275+
border-radius: 0.25rem;
2276+
background: oklch(var(--bc) / 0.03);
2277+
border: 1px solid oklch(var(--bc) / 0.06);
2278+
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;
2279+
font-size: 0.6875rem;
2280+
line-height: 1.5;
2281+
color: oklch(var(--bc) / 0.5);
2282+
}
2283+
.audit-diff-line {
2284+
display: block;
2285+
white-space: nowrap;
2286+
overflow: hidden;
2287+
text-overflow: ellipsis;
2288+
}
2289+
.audit-diff-field {
2290+
font-weight: 600;
2291+
}
2292+
.audit-diff-rm {
2293+
color: var(--color-error);
2294+
}
2295+
.audit-diff-add {
2296+
color: var(--color-success);
2297+
}
2298+
21522299
/* Expand Button */
21532300
.expand-btn {
21542301
display: inline-flex;

cabotage/client/templates/_macros.html

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,105 @@ <h1 class="page-title text-balance">{{ title }}</h1>
8484
</div>
8585
{% endmacro %}
8686

87+
{% macro org_tab_nav(organization, active='') %}
88+
{% set org_slug = organization.slug %}
89+
<div class="app-content-padded !py-0">
90+
<div class="tab-bar" data-tabs>
91+
<a href="{{ url_for('user.organization', org_slug=org_slug) }}"
92+
title="Projects"
93+
class="tab-item {% if active == 'projects' %}tab-active{% endif %}">{{ icon('folder', 'w-4 h-4') }} <span class="tab-label">Projects</span></a>
94+
<a href="{{ url_for('user.organization_members', org_slug=org_slug) }}"
95+
title="Members"
96+
class="tab-item {% if active == 'members' %}tab-active{% endif %}">{{ icon('users', 'w-4 h-4') }} <span class="tab-label">Members</span></a>
97+
<div class="dropdown dropdown-hover">
98+
<div tabindex="0" role="button"
99+
class="tab-item {% if active in ('observe', 'audit') %}tab-active{% endif %}">
100+
{{ icon('search', 'w-4 h-4') }}
101+
<span class="tab-label">Observe {{ icon('chevron-down', 'w-3 h-3 opacity-40') }}</span>
102+
</div>
103+
<ul tabindex="0" class="menu menu-md dropdown-content bg-base-100 rounded-box z-50 mt-1 w-48 p-2 shadow-lg border border-base-300">
104+
{% if config.MIMIR_URL %}
105+
<li><a href="{{ url_for('user.organization_observe', org_slug=org_slug) }}"
106+
class="{% if active == 'observe' %}active{% endif %}">{{ icon('bar-chart', 'w-3.5 h-3.5') }} Metrics</a></li>
107+
{% else %}
108+
<li class="disabled opacity-30"><span>{{ icon('bar-chart', 'w-3.5 h-3.5') }} Metrics</span></li>
109+
{% endif %}
110+
<li><a href="{{ url_for('user.organization_audit_log', org_slug=org_slug) }}"
111+
class="{% if active == 'audit' %}active{% endif %}">{{ icon('clock', 'w-3.5 h-3.5') }} Audit Log</a></li>
112+
</ul>
113+
</div>
114+
<a href="{{ url_for('user.organization_settings', org_slug=org_slug) }}"
115+
title="Settings"
116+
class="tab-item {% if active == 'settings' %}tab-active{% endif %}">{{ icon('settings', 'w-4 h-4') }} <span class="tab-label">Settings</span></a>
117+
</div>
118+
</div>
119+
{% endmacro %}
120+
121+
{% macro env_tab_nav(project, environment, active='') %}
122+
{% set org_slug = project.organization.slug %}
123+
{% set project_slug = project.slug %}
124+
{% set env_slug = environment.slug %}
125+
<div class="app-content-padded !py-0">
126+
<div class="tab-bar" data-tabs>
127+
<a href="{{ url_for('user.project_environment', org_slug=org_slug, project_slug=project_slug, env_slug=env_slug) }}"
128+
title="Overview"
129+
class="tab-item {% if active == 'overview' %}tab-active{% endif %}">{{ icon('activity', 'w-4 h-4') }} <span class="tab-label">Overview</span></a>
130+
<div class="dropdown dropdown-hover">
131+
<div tabindex="0" role="button"
132+
class="tab-item {% if active in ('observe', 'audit') %}tab-active{% endif %}">
133+
{{ icon('search', 'w-4 h-4') }}
134+
<span class="tab-label">Observe {{ icon('chevron-down', 'w-3 h-3 opacity-40') }}</span>
135+
</div>
136+
<ul tabindex="0" class="menu menu-md dropdown-content bg-base-100 rounded-box z-50 mt-1 w-48 p-2 shadow-lg border border-base-300">
137+
{% if config.MIMIR_URL %}
138+
<li><a href="{{ url_for('user.environment_observe', org_slug=org_slug, project_slug=project_slug, env_slug=env_slug) }}"
139+
class="{% if active == 'observe' %}active{% endif %}">{{ icon('bar-chart', 'w-3.5 h-3.5') }} Metrics</a></li>
140+
{% else %}
141+
<li class="disabled opacity-30"><span>{{ icon('bar-chart', 'w-3.5 h-3.5') }} Metrics</span></li>
142+
{% endif %}
143+
<li><a href="{{ url_for('user.environment_audit_log', org_slug=org_slug, project_slug=project_slug, env_slug=env_slug) }}"
144+
class="{% if active == 'audit' %}active{% endif %}">{{ icon('clock', 'w-3.5 h-3.5') }} Audit Log</a></li>
145+
</ul>
146+
</div>
147+
<a href="{{ url_for('user.project_environment_settings', org_slug=org_slug, project_slug=project_slug, env_slug=env_slug) }}"
148+
title="Settings"
149+
class="tab-item {% if active == 'settings' %}tab-active{% endif %}">{{ icon('settings', 'w-4 h-4') }} <span class="tab-label">Settings</span></a>
150+
</div>
151+
</div>
152+
{% endmacro %}
153+
154+
{% macro project_tab_nav(project, active='') %}
155+
{% set org_slug = project.organization.slug %}
156+
{% set project_slug = project.slug %}
157+
<div class="app-content-padded !py-0">
158+
<div class="tab-bar" data-tabs>
159+
<a href="{{ url_for('user.project', org_slug=org_slug, project_slug=project_slug) }}"
160+
title="Overview"
161+
class="tab-item {% if active == 'overview' %}tab-active{% endif %}">{{ icon('activity', 'w-4 h-4') }} <span class="tab-label">Overview</span></a>
162+
<div class="dropdown dropdown-hover">
163+
<div tabindex="0" role="button"
164+
class="tab-item {% if active in ('observe', 'audit') %}tab-active{% endif %}">
165+
{{ icon('search', 'w-4 h-4') }}
166+
<span class="tab-label">Observe {{ icon('chevron-down', 'w-3 h-3 opacity-40') }}</span>
167+
</div>
168+
<ul tabindex="0" class="menu menu-md dropdown-content bg-base-100 rounded-box z-50 mt-1 w-48 p-2 shadow-lg border border-base-300">
169+
{% if config.MIMIR_URL %}
170+
<li><a href="{{ url_for('user.project_observe', org_slug=org_slug, project_slug=project_slug) }}"
171+
class="{% if active == 'observe' %}active{% endif %}">{{ icon('bar-chart', 'w-3.5 h-3.5') }} Metrics</a></li>
172+
{% else %}
173+
<li class="disabled opacity-30"><span>{{ icon('bar-chart', 'w-3.5 h-3.5') }} Metrics</span></li>
174+
{% endif %}
175+
<li><a href="{{ url_for('user.project_audit_log', org_slug=org_slug, project_slug=project_slug) }}"
176+
class="{% if active == 'audit' %}active{% endif %}">{{ icon('clock', 'w-3.5 h-3.5') }} Audit Log</a></li>
177+
</ul>
178+
</div>
179+
<a href="{{ url_for('user.project_settings', org_slug=org_slug, project_slug=project_slug) }}"
180+
title="Settings"
181+
class="tab-item {% if active == 'settings' %}tab-active{% endif %}">{{ icon('settings', 'w-4 h-4') }} <span class="tab-label">Settings</span></a>
182+
</div>
183+
</div>
184+
{% endmacro %}
185+
87186
{% macro app_tab_nav(application, active='', env=none) %}
88187
{% set org_slug = application.project.organization.slug %}
89188
{% set project_slug = application.project.slug %}
@@ -116,6 +215,8 @@ <h1 class="page-title text-balance">{{ title }}</h1>
116215
<li class="disabled opacity-30"><span>{{ icon('file-text', 'w-3.5 h-3.5') }} Logs</span></li>
117216
{% endif %}
118217
<li class="disabled opacity-30"><span>{{ icon('bar-chart-2', 'w-3.5 h-3.5') }} Pipeline <span class="text-[0.6rem] opacity-40">(Soon)</span></span></li>
218+
<li><a href="{{ url_for('user.application_audit_log', org_slug=org_slug, project_slug=project_slug, app_slug=app_slug, env_slug=env_slug) }}"
219+
class="{% if active == 'audit' %}active{% endif %}">{{ icon('clock', 'w-3.5 h-3.5') }} Audit Log</a></li>
119220
</ul>
120221
</div>
121222
<a href="{{ url_for('user.application_images', org_slug=org_slug, project_slug=project_slug, app_slug=app_slug, env_slug=env_slug) }}"

0 commit comments

Comments
 (0)