fixed #1281 - list role contents (#1295)

Dep on #1294
This commit is contained in:
Eugene 2025-03-22 15:31:01 +01:00 committed by GitHub
parent cea7acc918
commit 9841421211
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 266 additions and 4 deletions

View file

@ -10,9 +10,11 @@ use sea_orm::{
};
use tokio::sync::Mutex;
use uuid::Uuid;
use warpgate_common::{Role as RoleConfig, WarpgateError};
use warpgate_common::{
Role as RoleConfig, Target as TargetConfig, User as UserConfig, WarpgateError,
};
use warpgate_core::consts::BUILTIN_ADMIN_ROLE_NAME;
use warpgate_db_entities::Role;
use warpgate_db_entities::{Role, Target, User};
use super::AnySecurityScheme;
@ -118,6 +120,22 @@ enum DeleteRoleResponse {
NotFound,
}
#[derive(ApiResponse)]
enum GetRoleTargetsResponse {
#[oai(status = 200)]
Ok(Json<Vec<TargetConfig>>),
#[oai(status = 404)]
NotFound,
}
#[derive(ApiResponse)]
enum GetRoleUsersResponse {
#[oai(status = 200)]
Ok(Json<Vec<UserConfig>>),
#[oai(status = 404)]
NotFound,
}
pub struct DetailApi;
#[OpenApi]
@ -185,4 +203,58 @@ impl DetailApi {
role.delete(&*db).await?;
Ok(DeleteRoleResponse::Deleted)
}
#[oai(
path = "/role/:id/targets",
method = "get",
operation_id = "get_role_targets"
)]
async fn api_get_role_targets(
&self,
db: Data<&Arc<Mutex<DatabaseConnection>>>,
id: Path<Uuid>,
_auth: AnySecurityScheme,
) -> Result<GetRoleTargetsResponse, WarpgateError> {
let db = db.lock().await;
let Some(role) = Role::Entity::find_by_id(id.0).one(&*db).await? else {
return Ok(GetRoleTargetsResponse::NotFound);
};
let targets = role.find_related(Target::Entity).all(&*db).await?;
Ok(GetRoleTargetsResponse::Ok(Json(
targets
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, serde_json::Error>>()?,
)))
}
#[oai(
path = "/role/:id/users",
method = "get",
operation_id = "get_role_users"
)]
async fn api_get_role_users(
&self,
db: Data<&Arc<Mutex<DatabaseConnection>>>,
id: Path<Uuid>,
_auth: AnySecurityScheme,
) -> Result<GetRoleUsersResponse, WarpgateError> {
let db = db.lock().await;
let Some(role) = Role::Entity::find_by_id(id.0).one(&*db).await? else {
return Ok(GetRoleUsersResponse::NotFound);
};
let users = role.find_related(User::Entity).all(&*db).await?;
Ok(GetRoleUsersResponse::Ok(Json(
users
.into_iter()
.map(TryInto::try_into)
.collect::<Result<Vec<_>, WarpgateError>>()?,
)))
}
}

View file

@ -18,6 +18,26 @@ pub struct Model {
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl Related<super::Target::Entity> for Entity {
fn to() -> RelationDef {
super::TargetRoleAssignment::Relation::Target.def()
}
fn via() -> Option<RelationDef> {
Some(super::TargetRoleAssignment::Relation::Role.def().rev())
}
}
impl Related<super::User::Entity> for Entity {
fn to() -> RelationDef {
super::UserRoleAssignment::Relation::User.def()
}
fn via() -> Option<RelationDef> {
Some(super::UserRoleAssignment::Relation::Role.def().rev())
}
}
impl ActiveModelBehavior for ActiveModel {}
impl From<Model> for Role {

View file

@ -40,6 +40,7 @@
"format-duration": "^3.0.2",
"otpauth": "^9.3.6",
"qrcode": "^1.5.4",
"rxjs": "^7.8.2",
"sass": "1.78",
"svelte": "^5.20.0",
"svelte-check": "^4.1.4",

View file

@ -49,6 +49,7 @@
"format-duration": "^3.0.2",
"otpauth": "^9.3.6",
"qrcode": "^1.5.4",
"rxjs": "^7.8.2",
"sass": "1.78",
"svelte": "^5.20.0",
"svelte-check": "^4.1.4",

View file

@ -1,11 +1,14 @@
<script lang="ts">
import { api, type Role } from 'admin/lib/api'
import { api, type Role, type Target, type User } from 'admin/lib/api'
import AsyncButton from 'common/AsyncButton.svelte'
import { replace } from 'svelte-spa-router'
import { link, replace } from 'svelte-spa-router'
import { FormGroup, Input } from '@sveltestrap/sveltestrap'
import { stringifyError } from 'common/errors'
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
import Loadable from 'common/Loadable.svelte'
import ItemList, { type PaginatedResponse } from 'common/ItemList.svelte';
import * as rx from 'rxjs'
import EmptyState from 'common/EmptyState.svelte';
interface Props {
params: { id: string };
@ -24,6 +27,30 @@
disabled = role.name === 'warpgate:admin'
}
function loadUsers (): rx.Observable<PaginatedResponse<User>> {
return rx.from(api.getRoleUsers({
id: params.id,
})).pipe(
rx.map(targets => ({
items: targets,
offset: 0,
total: targets.length,
})),
)
}
function loadTargets (): rx.Observable<PaginatedResponse<Target>> {
return rx.from(api.getRoleTargets({
id: params.id,
})).pipe(
rx.map(targets => ({
items: targets,
offset: 0,
total: targets.length,
})),
)
}
async function update () {
try {
role = await api.updateRole({
@ -85,3 +112,50 @@
click={remove}
>Remove</AsyncButton>
</div>
<h4 class="mt-4">Assigned users</h4>
<ItemList load={loadUsers}>
{#snippet item(user)}
<a
class="list-group-item list-group-item-action"
href="/users/{user.id}"
use:link>
<div>
<strong class="me-auto">
{user.username}
</strong>
{#if user.description}
<small class="d-block text-muted">{user.description}</small>
{/if}
</div>
</a>
{/snippet}
{#snippet empty()}
<Alert color="info">This role has no users assigned to it</Alert>
{/snippet}
</ItemList>
<h4 class="mt-4">Assigned targets</h4>
<ItemList load={loadTargets}>
{#snippet item(target)}
<a
class="list-group-item list-group-item-action"
href="/targets/{target.id}"
use:link>
<div class="me-auto">
<strong>
{target.name}
</strong>
{#if target.description}
<small class="d-block text-muted">{target.description}</small>
{/if}
</div>
</a>
{/snippet}
{#snippet empty()}
<Alert color="info">This role has no targets assigned to it</Alert>
{/snippet}
</ItemList>

View file

@ -464,6 +464,94 @@
"operationId": "delete_role"
}
},
"/role/{id}/targets": {
"get": {
"parameters": [
{
"name": "id",
"schema": {
"type": "string",
"format": "uuid"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json; charset=utf-8": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Target"
}
}
}
}
},
"404": {
"description": ""
}
},
"security": [
{
"TokenSecurityScheme": []
},
{
"CookieSecurityScheme": []
}
],
"operationId": "get_role_targets"
}
},
"/role/{id}/users": {
"get": {
"parameters": [
{
"name": "id",
"schema": {
"type": "string",
"format": "uuid"
},
"in": "path",
"required": true,
"deprecated": false,
"explode": true
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json; charset=utf-8": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
}
}
}
},
"404": {
"description": ""
}
},
"security": [
{
"TokenSecurityScheme": []
},
{
"CookieSecurityScheme": []
}
],
"operationId": "get_role_users"
}
},
"/tickets": {
"get": {
"responses": {

View file

@ -128,3 +128,9 @@
<Pagination total={_total} bind:page={page} pageSize={pageSize} />
{/if}
{/await}
<style lang="scss">
.list-group:empty {
display: none;
}
</style>