mirror of
https://github.com/warp-tech/warpgate.git
synced 2025-09-12 09:34:35 +08:00
parent
cea7acc918
commit
9841421211
7 changed files with 266 additions and 4 deletions
|
@ -10,9 +10,11 @@ use sea_orm::{
|
||||||
};
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use uuid::Uuid;
|
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_core::consts::BUILTIN_ADMIN_ROLE_NAME;
|
||||||
use warpgate_db_entities::Role;
|
use warpgate_db_entities::{Role, Target, User};
|
||||||
|
|
||||||
use super::AnySecurityScheme;
|
use super::AnySecurityScheme;
|
||||||
|
|
||||||
|
@ -118,6 +120,22 @@ enum DeleteRoleResponse {
|
||||||
NotFound,
|
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;
|
pub struct DetailApi;
|
||||||
|
|
||||||
#[OpenApi]
|
#[OpenApi]
|
||||||
|
@ -185,4 +203,58 @@ impl DetailApi {
|
||||||
role.delete(&*db).await?;
|
role.delete(&*db).await?;
|
||||||
Ok(DeleteRoleResponse::Deleted)
|
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>>()?,
|
||||||
|
)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,26 @@ pub struct Model {
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
pub enum Relation {}
|
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 ActiveModelBehavior for ActiveModel {}
|
||||||
|
|
||||||
impl From<Model> for Role {
|
impl From<Model> for Role {
|
||||||
|
|
1
warpgate-web/package-lock.json
generated
1
warpgate-web/package-lock.json
generated
|
@ -40,6 +40,7 @@
|
||||||
"format-duration": "^3.0.2",
|
"format-duration": "^3.0.2",
|
||||||
"otpauth": "^9.3.6",
|
"otpauth": "^9.3.6",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
|
"rxjs": "^7.8.2",
|
||||||
"sass": "1.78",
|
"sass": "1.78",
|
||||||
"svelte": "^5.20.0",
|
"svelte": "^5.20.0",
|
||||||
"svelte-check": "^4.1.4",
|
"svelte-check": "^4.1.4",
|
||||||
|
|
|
@ -49,6 +49,7 @@
|
||||||
"format-duration": "^3.0.2",
|
"format-duration": "^3.0.2",
|
||||||
"otpauth": "^9.3.6",
|
"otpauth": "^9.3.6",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
|
"rxjs": "^7.8.2",
|
||||||
"sass": "1.78",
|
"sass": "1.78",
|
||||||
"svelte": "^5.20.0",
|
"svelte": "^5.20.0",
|
||||||
"svelte-check": "^4.1.4",
|
"svelte-check": "^4.1.4",
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<script lang="ts">
|
<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 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 { FormGroup, Input } from '@sveltestrap/sveltestrap'
|
||||||
import { stringifyError } from 'common/errors'
|
import { stringifyError } from 'common/errors'
|
||||||
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
|
import Alert from 'common/sveltestrap-s5-ports/Alert.svelte'
|
||||||
import Loadable from 'common/Loadable.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 {
|
interface Props {
|
||||||
params: { id: string };
|
params: { id: string };
|
||||||
|
@ -24,6 +27,30 @@
|
||||||
disabled = role.name === 'warpgate:admin'
|
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 () {
|
async function update () {
|
||||||
try {
|
try {
|
||||||
role = await api.updateRole({
|
role = await api.updateRole({
|
||||||
|
@ -85,3 +112,50 @@
|
||||||
click={remove}
|
click={remove}
|
||||||
>Remove</AsyncButton>
|
>Remove</AsyncButton>
|
||||||
</div>
|
</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>
|
||||||
|
|
|
@ -464,6 +464,94 @@
|
||||||
"operationId": "delete_role"
|
"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": {
|
"/tickets": {
|
||||||
"get": {
|
"get": {
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|
|
@ -128,3 +128,9 @@
|
||||||
<Pagination total={_total} bind:page={page} pageSize={pageSize} />
|
<Pagination total={_total} bind:page={page} pageSize={pageSize} />
|
||||||
{/if}
|
{/if}
|
||||||
{/await}
|
{/await}
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.list-group:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
Loading…
Add table
Reference in a new issue