Add display in sidebar for impersonation
This commit is contained in:
parent
51811132a4
commit
76c395d613
11 changed files with 259 additions and 234 deletions
|
|
@ -27,6 +27,8 @@ func AddRoutes(r *mux.Router) {
|
|||
r.Handle("/image/{uuid}", auth.NewEnsureAuth(apiImagePost)).Methods("POST")
|
||||
r.Handle("/image/{uuid}/content", auth.NewEnsureAuth(apiImageContentGet)).Methods("GET")
|
||||
r.Handle("/image/{uuid}/content", auth.NewEnsureAuth(apiImageContentPost)).Methods("POST")
|
||||
impersonation := resource.Impersonation(router)
|
||||
r.Handle("/impersonation", authenticatedHandlerJSONPost(impersonation.Create)).Methods("POST")
|
||||
lead := resource.Lead(r)
|
||||
r.Handle("/leads", authenticatedHandlerJSON(lead.List)).Methods("GET")
|
||||
r.Handle("/leads", authenticatedHandlerJSONPost(lead.Create)).Methods("POST")
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func postSignup(ctx context.Context, r *http.Request, signup reqSignup) (string,
|
|||
return "", nhttp.NewError("Failed to signup user", err)
|
||||
}
|
||||
|
||||
auth.AddUserSession(r, user)
|
||||
auth.AddUserSession(ctx, user)
|
||||
|
||||
return "/", nil
|
||||
}
|
||||
|
|
|
|||
35
auth/auth.go
35
auth/auth.go
|
|
@ -30,16 +30,37 @@ type EnsureAuth struct {
|
|||
handler AuthenticatedHandler
|
||||
}
|
||||
|
||||
func AddUserSession(r *http.Request, user *platform.User) {
|
||||
id := strconv.Itoa(int(user.ID))
|
||||
sessionManager.Put(r.Context(), "user_id", id)
|
||||
sessionManager.Put(r.Context(), "username", user.Username)
|
||||
log.Debug().Str("id", id).Str("username", user.Username).Msg("added user session")
|
||||
func AddUserSession(ctx context.Context, user *platform.User) {
|
||||
id_str := strconv.Itoa(int(user.ID))
|
||||
sessionManager.Put(ctx, "user_id", id_str)
|
||||
sessionManager.Put(ctx, "username", user.Username)
|
||||
log.Debug().Str("id", id_str).Str("username", user.Username).Msg("added user session")
|
||||
}
|
||||
func ImpersonateUser(ctx context.Context, target_user_id int) {
|
||||
target_user_id_str := strconv.Itoa(int(target_user_id))
|
||||
sessionManager.Put(ctx, "impersonated_user_id", target_user_id_str)
|
||||
}
|
||||
func ImpersonatedUser(ctx context.Context) *int32 {
|
||||
i_str := sessionManager.GetString(ctx, "impersonated_user_id")
|
||||
if i_str == "" {
|
||||
return nil
|
||||
}
|
||||
i, err := strconv.Atoi(i_str)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("impersonated_user_id", i_str).Msg("failed to parse impersonated_user_id")
|
||||
return nil
|
||||
}
|
||||
result := int32(i)
|
||||
return &result
|
||||
}
|
||||
|
||||
func GetAuthenticatedUser(r *http.Request) (*platform.User, error) {
|
||||
ctx := r.Context()
|
||||
user_id_str := sessionManager.GetString(ctx, "user_id")
|
||||
impersonated_user_id_str := sessionManager.GetString(ctx, "impersonated_user_id")
|
||||
if impersonated_user_id_str != "" {
|
||||
user_id_str = impersonated_user_id_str
|
||||
}
|
||||
if user_id_str != "" {
|
||||
user_id, err := strconv.Atoi(user_id_str)
|
||||
if err != nil {
|
||||
|
|
@ -59,7 +80,7 @@ func GetAuthenticatedUser(r *http.Request) (*platform.User, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
AddUserSession(r, user)
|
||||
AddUserSession(ctx, user)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
|
|
@ -108,7 +129,7 @@ func SigninUser(r *http.Request, username string, password string) (*platform.Us
|
|||
if user == nil {
|
||||
return nil, errors.New("No matching user")
|
||||
}
|
||||
AddUserSession(r, user)
|
||||
AddUserSession(r.Context(), user)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/auth"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
|
|
@ -47,6 +48,7 @@ type sessionURL struct {
|
|||
type sessionURLAPI struct {
|
||||
Avatar string `json:"avatar"`
|
||||
Communication string `json:"communication"`
|
||||
Impersonation string `json:"impersonation"`
|
||||
PublicreportMessage string `json:"publicreport_message"`
|
||||
ReviewTask string `json:"review_task"`
|
||||
Signal string `json:"signal"`
|
||||
|
|
@ -65,8 +67,17 @@ func (res *sessionR) Get(ctx context.Context, r *http.Request, user platform.Use
|
|||
if err != nil {
|
||||
return nil, nhttp.NewError("create user: %w", err)
|
||||
}
|
||||
var impersonating *string
|
||||
impersonating_id := auth.ImpersonatedUser(ctx)
|
||||
if impersonating_id != nil {
|
||||
i, err := res.router.IDToURI("user.ByIDGet", int(*impersonating_id))
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("create impersonating uri: %w", err)
|
||||
}
|
||||
impersonating = &i
|
||||
}
|
||||
return &session{
|
||||
Impersonating: nil,
|
||||
Impersonating: impersonating,
|
||||
NotificationCounts: sessionNotificationCounts{
|
||||
Communications: counts.Communications,
|
||||
Home: counts.Home,
|
||||
|
|
@ -81,6 +92,7 @@ func (res *sessionR) Get(ctx context.Context, r *http.Request, user platform.Use
|
|||
API: sessionURLAPI{
|
||||
Avatar: config.MakeURLNidus("/api/avatar"),
|
||||
Communication: urls.API.Communication,
|
||||
Impersonation: config.MakeURLNidus("/api/impersonation"),
|
||||
PublicreportMessage: urls.API.Publicreport.Message,
|
||||
ReviewTask: config.MakeURLNidus("/api/review-task"),
|
||||
Signal: config.MakeURLNidus("/api/signal"),
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
"github.com/aarondl/opt/omitnull"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/rs/zerolog/log"
|
||||
//"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type user struct {
|
||||
|
|
@ -159,9 +159,9 @@ func (res *userR) List(ctx context.Context, r *http.Request, u platform.User, qu
|
|||
return nil, nhttp.NewError("list users: %w", err)
|
||||
}
|
||||
results := make([]*user, len(users))
|
||||
log.Debug().Int("len", len(users)).Msg("building response")
|
||||
//log.Debug().Int("len", len(users)).Msg("building response")
|
||||
for i, v := range users {
|
||||
log.Debug().Int("i", i).Msg("making results")
|
||||
//log.Debug().Int("i", i).Msg("making results")
|
||||
resp, err := res.response(v)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("create response: %w", err)
|
||||
|
|
|
|||
|
|
@ -1,140 +0,0 @@
|
|||
.logo-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
background-color: $off-white;
|
||||
min-height: 100vh;
|
||||
transition: all 0.3s;
|
||||
width: 250px;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#sidebar.collapsed {
|
||||
width: 70px;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
/* Logo style when sidebar is collapsed */
|
||||
#sidebar.collapsed .logo-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#sidebar.collapsed .logo-img {
|
||||
max-width: 40px; /* smaller size for collapsed state */
|
||||
}
|
||||
#content {
|
||||
transition: all 0.3s;
|
||||
margin-left: 250px;
|
||||
padding: 10px;
|
||||
width: calc(100% - 250px);
|
||||
}
|
||||
|
||||
#content.expanded {
|
||||
margin-left: 70px;
|
||||
width: calc(100% - 70px);
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid $off-black;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
justify-content: center; /* Center for the logo */
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar-menu li {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.sidebar-menu li a {
|
||||
text-decoration: none;
|
||||
color: $off-black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-menu li a:hover {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.sidebar-menu .menu-icon {
|
||||
font-size: 1.2rem;
|
||||
min-width: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.sidebar-menu .menu-icon svg {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
.sidebar-menu .menu-text {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
#sidebar.collapsed .menu-text {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#sidebar.collapsed .sidebar-header h4 {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#sidebar.collapsed .sidebar-menu .menu-icon {
|
||||
min-width: 100%;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#sidebarToggle {
|
||||
position: absolute;
|
||||
left: calc(250px - 15px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1050;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: left 0.3s;
|
||||
padding: 0;
|
||||
}
|
||||
#sidebarToggle i {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
#sidebar.collapsed > #sidebarToggle {
|
||||
left: calc(70px - 15px);
|
||||
}
|
||||
|
||||
#sidebar > #sidebarToggle i {
|
||||
position: relative;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
#sidebar.collapsed > #sidebarToggle i {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted } from "vue";
|
||||
import { useSessionStore } from "@/store/session";
|
||||
import { Session } from "@/types";
|
||||
|
||||
import Sidebar from "./components/layout/Sidebar.vue";
|
||||
import MainContent from "./components/layout/MainContent.vue";
|
||||
|
|
@ -19,7 +20,9 @@ import NavigationLink from "@/components/common/NavigationLink.vue";
|
|||
|
||||
const session = useSessionStore();
|
||||
onMounted(() => {
|
||||
session.fetchSession();
|
||||
session.get().then((session: Session) => {
|
||||
console.log("session loaded", session);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -49,24 +49,25 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from "vue";
|
||||
import { ref, computed, onMounted, watch } from "vue";
|
||||
import Avatar from "@/components/Avatar.vue";
|
||||
import { useUserStore } from "@/store/user";
|
||||
import type { User } from "@/types";
|
||||
|
||||
interface Props {
|
||||
modelValue?: User | null;
|
||||
placeholder?: string;
|
||||
minChars?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: null,
|
||||
placeholder: "Search users...",
|
||||
minChars: 3,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
select: [user: User];
|
||||
input: [query: string];
|
||||
"update:modelValue": [user: User | null];
|
||||
}>();
|
||||
|
||||
const usersStore = useUserStore();
|
||||
|
|
@ -78,8 +79,25 @@ onMounted(async () => {
|
|||
if (!usersStore.all) {
|
||||
await usersStore.fetchAll();
|
||||
}
|
||||
|
||||
// Initialize search query with selected user's name if provided
|
||||
if (props.modelValue) {
|
||||
searchQuery.value = props.modelValue.display_name;
|
||||
}
|
||||
});
|
||||
|
||||
// Watch for external changes to modelValue
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
searchQuery.value = newValue.display_name;
|
||||
} else {
|
||||
searchQuery.value = "";
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const filteredUsers = computed(() => {
|
||||
if (searchQuery.value.length < props.minChars || !usersStore.all) {
|
||||
return [];
|
||||
|
|
@ -98,7 +116,11 @@ const filteredUsers = computed(() => {
|
|||
|
||||
function onInput() {
|
||||
showDropdown.value = searchQuery.value.length >= props.minChars;
|
||||
emit("input", searchQuery.value);
|
||||
|
||||
// Clear selection if user is typing
|
||||
if (props.modelValue && searchQuery.value !== props.modelValue.display_name) {
|
||||
emit("update:modelValue", null);
|
||||
}
|
||||
}
|
||||
|
||||
function onFocus() {
|
||||
|
|
@ -117,7 +139,7 @@ function onBlur() {
|
|||
function selectUser(user: User) {
|
||||
searchQuery.value = user.display_name;
|
||||
showDropdown.value = false;
|
||||
emit("select", user);
|
||||
emit("update:modelValue", user);
|
||||
}
|
||||
|
||||
function highlightMatch(text: string): string {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,152 @@
|
|||
<style scoped lang="scss">
|
||||
#content {
|
||||
transition: all 0.3s;
|
||||
margin-left: 250px;
|
||||
padding: 10px;
|
||||
width: calc(100% - 250px);
|
||||
}
|
||||
|
||||
#content.expanded {
|
||||
margin-left: 70px;
|
||||
width: calc(100% - 70px);
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
background-color: $off-white;
|
||||
min-height: 100vh;
|
||||
transition: all 0.3s;
|
||||
width: 250px;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
#sidebar.collapsed {
|
||||
width: 70px;
|
||||
padding: 20px 10px;
|
||||
}
|
||||
/* Logo style when sidebar is collapsed */
|
||||
#sidebar.collapsed .logo-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#sidebar.collapsed .logo-img {
|
||||
max-width: 40px; /* smaller size for collapsed state */
|
||||
}
|
||||
#sidebar.impersonating {
|
||||
background-color: $danger;
|
||||
}
|
||||
#sidebar.collapsed .menu-text {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
#sidebar.collapsed .sidebar-header h4 {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#sidebar.collapsed .sidebar-menu .menu-icon {
|
||||
min-width: 100%;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
#sidebarToggle {
|
||||
position: absolute;
|
||||
left: calc(250px - 15px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1050;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: left 0.3s;
|
||||
padding: 0;
|
||||
}
|
||||
#sidebarToggle i {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
#sidebar.collapsed > #sidebarToggle {
|
||||
left: calc(70px - 15px);
|
||||
}
|
||||
|
||||
#sidebar > #sidebarToggle i {
|
||||
position: relative;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
#sidebar.collapsed > #sidebarToggle i {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
.sidebar-header {
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid $off-black;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
justify-content: center; /* Center for the logo */
|
||||
}
|
||||
|
||||
.sidebar-menu {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.sidebar-menu li {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.sidebar-menu li a {
|
||||
text-decoration: none;
|
||||
color: $off-black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-menu li a:hover {
|
||||
color: $primary;
|
||||
}
|
||||
|
||||
.sidebar-menu .menu-icon {
|
||||
font-size: 1.2rem;
|
||||
min-width: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.sidebar-menu .menu-icon svg {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
}
|
||||
.sidebar-menu .menu-text {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div id="sidebar" :class="{ collapsed: isCollapsed }">
|
||||
<div
|
||||
id="sidebar"
|
||||
:class="{ collapsed: isCollapsed, impersonating: isImpersonating }"
|
||||
>
|
||||
<div class="sidebar-header">
|
||||
<div class="logo-container">
|
||||
<img class="logo" src="/static/img/nidus-logo-256-transparent.png" />
|
||||
|
|
@ -66,6 +213,7 @@ import { useSessionStore } from "@/store/session";
|
|||
|
||||
// Reactive state
|
||||
const isCollapsed = ref(false);
|
||||
const isImpersonating = ref(false);
|
||||
|
||||
const session = useSessionStore();
|
||||
|
||||
|
|
@ -135,6 +283,8 @@ onMounted(async () => {
|
|||
|
||||
await nextTick();
|
||||
|
||||
const s = await session.get();
|
||||
isImpersonating.value = !!s.impersonating;
|
||||
initializeBootstrap();
|
||||
setTooltipsForSidebar();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -12,10 +12,9 @@
|
|||
<div class="col-md-6">
|
||||
<label for="userSearch" class="form-label">Search Users</label>
|
||||
<UserSelector
|
||||
v-model="selectedUserId"
|
||||
v-model="selectedUser"
|
||||
label="Choose a user"
|
||||
placeholder="Select a user..."
|
||||
@change="onUserChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
|
|
@ -29,76 +28,9 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>User ID</th>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1001</td>
|
||||
<td>John Doe</td>
|
||||
<td>john.doe@example.com</td>
|
||||
<td><span class="badge bg-primary">Admin</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">
|
||||
Impersonate <i class="bi bi-box-arrow-in-right"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1002</td>
|
||||
<td>Jane Smith</td>
|
||||
<td>jane.smith@example.com</td>
|
||||
<td><span class="badge bg-info">Support</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">
|
||||
Impersonate <i class="bi bi-box-arrow-in-right"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1003</td>
|
||||
<td>Robert Johnson</td>
|
||||
<td>robert@example.com</td>
|
||||
<td><span class="badge bg-success">Premium User</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">
|
||||
Impersonate <i class="bi bi-box-arrow-in-right"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1004</td>
|
||||
<td>Maria Garcia</td>
|
||||
<td>maria@example.com</td>
|
||||
<td><span class="badge bg-secondary">Standard User</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-primary">
|
||||
Impersonate <i class="bi bi-box-arrow-in-right"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="alert alert-warning mt-3 mb-0"
|
||||
id="impersonationStatus"
|
||||
style="display: none"
|
||||
>
|
||||
<i class="bi bi-exclamation-triangle"></i> You are currently
|
||||
impersonating <strong>John Doe</strong>
|
||||
<button class="btn btn-sm btn-warning float-end">
|
||||
Exit Impersonation <i class="bi bi-box-arrow-left"></i>
|
||||
<div class="row mb-3">
|
||||
<button class="btn btn-danger" @click="doImpersonation" type="submit">
|
||||
Impersonate
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -113,9 +45,31 @@ import type { User } from "@/types";
|
|||
|
||||
const session = useSessionStore();
|
||||
|
||||
const selectedUserId = ref<number | null>(null);
|
||||
const selectedUser = ref<User | null>(null);
|
||||
|
||||
const onUserChange = (user: User | null) => {
|
||||
console.log("Selected user:", user);
|
||||
const doImpersonation = async () => {
|
||||
if (!selectedUser.value) {
|
||||
console.log("Can't impersonate, null user");
|
||||
return;
|
||||
}
|
||||
console.log("doing impersonation of user", selectedUser.value);
|
||||
const body = {
|
||||
id: selectedUser.value.id,
|
||||
};
|
||||
const url = session.urls!.api.impersonation;
|
||||
const response = await fetch(url, {
|
||||
body: JSON.stringify(body),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Upload failed: ${response.statusText}`);
|
||||
}
|
||||
const result = await response.json();
|
||||
console.log("impersonation", result);
|
||||
const new_session = await session.fetchSession();
|
||||
console.log("session is now", new_session);
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ export interface URLs {
|
|||
interface URLsAPI {
|
||||
avatar: string;
|
||||
communication: string;
|
||||
impersonation: string;
|
||||
publicreport_message: string;
|
||||
review_task: string;
|
||||
signal: string;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue