mirror of
https://github.com/SuiteCRM/SuiteCRM-Core.git
synced 2025-08-29 17:46:02 +08:00
Implement user action menu
Signed-off-by: Dillon-Brown <dillon.brown@salesagility.com>
This commit is contained in:
parent
f488ae2c8c
commit
aa242f970c
15 changed files with 190 additions and 76 deletions
|
@ -53,3 +53,4 @@ dunglas_angular_csrf:
|
|||
- { path: ^/(_(profiler|wdt))/ }
|
||||
- { path: ^/api }
|
||||
- { path: ^/session-status$ }
|
||||
- { path: ^/current-user$ }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export interface CurrentUserModel {
|
||||
id: string;
|
||||
name: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import {ActionLinkModel} from './action-link-model';
|
|||
import {CurrentUserModel} from './current-user-model';
|
||||
import {AllMenuModel} from './all-menu-model';
|
||||
import {LogoModel} from '../logo/logo-model';
|
||||
import {GroupedTab, NavbarModuleMap} from '@base/facades/navigation/navigation.facade';
|
||||
import {GroupedTab, NavbarModuleMap, UserActionMenu} from '@base/facades/navigation/navigation.facade';
|
||||
import {LanguageListStringMap, LanguageStringMap} from '@base/facades/language/language.facade';
|
||||
import {MenuItem} from '@components/navbar/navbar.abstract';
|
||||
import {UserPreferenceMap} from '@base/facades/user-preference/user-preference.facade';
|
||||
|
@ -26,7 +26,9 @@ export interface NavbarModel {
|
|||
appListStrings: LanguageListStringMap,
|
||||
menuItemThreshold: number,
|
||||
groupedTabs: GroupedTab[],
|
||||
userPreferences: UserPreferenceMap
|
||||
userPreferences: UserPreferenceMap,
|
||||
userActionMenu: UserActionMenu[],
|
||||
currentUser: CurrentUserModel
|
||||
): void;
|
||||
|
||||
buildGroupTabMenu(
|
||||
|
@ -46,4 +48,10 @@ export interface NavbarModel {
|
|||
modStrings: LanguageListStringMap,
|
||||
appListStrings: LanguageListStringMap,
|
||||
menuItemThreshold: number): void;
|
||||
|
||||
buildUserActionMenu(
|
||||
appStrings: LanguageStringMap,
|
||||
userActionMenu: UserActionMenu[],
|
||||
currentUser: CurrentUserModel
|
||||
): void;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {NavbarModel} from './navbar-model';
|
||||
import {LogoAbstract} from '../logo/logo-abstract';
|
||||
import {GroupedTab, NavbarModuleMap} from '@base/facades/navigation/navigation.facade';
|
||||
import {GroupedTab, NavbarModuleMap, UserActionMenu} from '@base/facades/navigation/navigation.facade';
|
||||
import {LanguageListStringMap, LanguageStringMap} from '@base/facades/language/language.facade';
|
||||
|
||||
import {CurrentUserModel} from './current-user-model';
|
||||
|
@ -31,35 +31,11 @@ export class NavbarAbstract implements NavbarModel {
|
|||
authenticated = true;
|
||||
logo = new LogoAbstract();
|
||||
useGroupTabs = false;
|
||||
globalActions: ActionLinkModel[] = [
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Employees'
|
||||
}
|
||||
},
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Admin'
|
||||
}
|
||||
},
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'Support Forums'
|
||||
}
|
||||
},
|
||||
{
|
||||
link: {
|
||||
url: '',
|
||||
label: 'About'
|
||||
}
|
||||
}
|
||||
];
|
||||
globalActions: ActionLinkModel[] = [];
|
||||
currentUser: CurrentUserModel = {
|
||||
id: '1',
|
||||
name: 'Will Rennie',
|
||||
id: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
};
|
||||
all = {
|
||||
modules: [],
|
||||
|
@ -72,10 +48,48 @@ export class NavbarAbstract implements NavbarModel {
|
|||
*/
|
||||
public resetMenu(): void {
|
||||
this.menu = [];
|
||||
this.globalActions = [];
|
||||
this.all.modules = [];
|
||||
this.all.extra = [];
|
||||
}
|
||||
|
||||
public buildUserActionMenu(
|
||||
appStrings: LanguageStringMap,
|
||||
userActionMenu: UserActionMenu[],
|
||||
currentUser: CurrentUserModel
|
||||
): void {
|
||||
this.currentUser.id = currentUser.id;
|
||||
this.currentUser.firstName = currentUser.firstName;
|
||||
this.currentUser.lastName = currentUser.lastName;
|
||||
|
||||
if (userActionMenu) {
|
||||
userActionMenu.forEach((subMenu) => {
|
||||
let name = subMenu.name;
|
||||
let url = subMenu.url;
|
||||
let urlParams;
|
||||
|
||||
if (name == 'logout') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name !== 'training') {
|
||||
urlParams = this.getModuleFromUrlParams(url);
|
||||
url = ROUTE_PREFIX + '/' + (urlParams.module).toLowerCase() + '/' + (urlParams.action).toLowerCase();
|
||||
}
|
||||
|
||||
let label = appStrings[subMenu.labelKey];
|
||||
|
||||
this.globalActions.push({
|
||||
link: {
|
||||
url: url,
|
||||
label: label,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build navbar
|
||||
* @param tabs
|
||||
|
@ -86,6 +100,8 @@ export class NavbarAbstract implements NavbarModel {
|
|||
* @param menuItemThreshold
|
||||
* @param groupedTabs
|
||||
* @param userPreferences
|
||||
* @param userActionMenu
|
||||
* @param currentUser
|
||||
*/
|
||||
public build(
|
||||
tabs: string[],
|
||||
|
@ -95,7 +111,9 @@ export class NavbarAbstract implements NavbarModel {
|
|||
appListStrings: LanguageListStringMap,
|
||||
menuItemThreshold: number,
|
||||
groupedTabs: GroupedTab[],
|
||||
userPreferences: UserPreferenceMap
|
||||
userPreferences: UserPreferenceMap,
|
||||
userActionMenu: UserActionMenu[],
|
||||
currentUser: CurrentUserModel,
|
||||
): void {
|
||||
|
||||
this.resetMenu();
|
||||
|
@ -104,6 +122,8 @@ export class NavbarAbstract implements NavbarModel {
|
|||
return;
|
||||
}
|
||||
|
||||
this.buildUserActionMenu(appStrings, userActionMenu, currentUser);
|
||||
|
||||
const navigationParadigm = userPreferences.navigation_paradigm;
|
||||
|
||||
if (navigationParadigm.toString() === 'm') {
|
||||
|
@ -238,7 +258,7 @@ export class NavbarAbstract implements NavbarModel {
|
|||
moduleUrl = null;
|
||||
}
|
||||
|
||||
const menuItem = {
|
||||
return {
|
||||
link: {
|
||||
label: (appStrings && appStrings[moduleLabel]) || moduleLabel,
|
||||
url: moduleUrl,
|
||||
|
@ -248,8 +268,6 @@ export class NavbarAbstract implements NavbarModel {
|
|||
icon: '',
|
||||
submenu: this.buildGroupedMenu(groupedModules, modules, appStrings, modStrings, appListStrings)
|
||||
};
|
||||
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -355,4 +373,17 @@ export class NavbarAbstract implements NavbarModel {
|
|||
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param search
|
||||
*/
|
||||
private getModuleFromUrlParams(search) {
|
||||
const hashes = search.slice(search.indexOf('?') + 1).split('&')
|
||||
const params = {}
|
||||
hashes.map(hash => {
|
||||
const [key, val] = hash.split('=')
|
||||
params[key] = decodeURIComponent(val)
|
||||
})
|
||||
return params
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,12 +90,10 @@
|
|||
<li class="global-link-item">
|
||||
<a class="nav-link primary-global-link dropdown-toggle" ngbDropdownToggle>
|
||||
<scrm-image class="global-action-icon sicon-2x" image="user"></scrm-image>
|
||||
{{ navbar.currentUser.name }}
|
||||
{{ navbar.currentUser.firstName }} {{navbar.currentUser.lastName}}
|
||||
</a>
|
||||
<div aria-labelledby="navbarDropdownMenuLink"
|
||||
class="dropdown-menu global-links-dropdown" ngbDropdownMenu>
|
||||
<a class="dropdown-item global-links-sublink" ngbDropdownItem
|
||||
href="#/Users/EditView/{{ navbar.currentUser.id }}">Profile</a>
|
||||
<ng-template [ngIf]="navbar.globalActions">
|
||||
<a class="dropdown-item global-links-sublink" ngbDropdownItem
|
||||
*ngFor="let globalAction of navbar.globalActions"
|
||||
|
@ -258,12 +256,10 @@
|
|||
<li class="global-link-item">
|
||||
<a class="nav-link primary-global-link dropdown-toggle" ngbDropdownToggle>
|
||||
<scrm-image class="global-action-icon sicon-2x" image="user"></scrm-image>
|
||||
{{ navbar.currentUser.name }}
|
||||
{{ navbar.currentUser.firstName }} {{navbar.currentUser.lastName}}
|
||||
</a>
|
||||
<div aria-labelledby="navbarDropdownMenuLink"
|
||||
class="dropdown-menu global-links-dropdown dropdown-menu-right" ngbDropdownMenu>
|
||||
<a class="dropdown-item global-links-sublink" ngbDropdownItem
|
||||
href="#/Users/EditView/{{ navbar.currentUser.id }}">Profile</a>
|
||||
<ng-template [ngIf]="navbar.globalActions">
|
||||
<a class="dropdown-item global-links-sublink" ngbDropdownItem
|
||||
*ngFor="let globalAction of navbar.globalActions"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Component, HostListener, OnInit} from '@angular/core';
|
||||
import {ApiService} from '../../services/api/api.service';
|
||||
import {ApiService} from '@services/api/api.service';
|
||||
import {NavbarModel} from './navbar-model';
|
||||
import {NavbarAbstract} from './navbar.abstract';
|
||||
import {combineLatest, Observable} from 'rxjs';
|
||||
|
@ -43,6 +43,8 @@ export class NavbarUiComponent implements OnInit {
|
|||
appListStrings$: Observable<LanguageListStringMap> = this.languageFacade.appListStrings$;
|
||||
userPreferences$: Observable<UserPreferenceMap> = this.userPreferenceFacade.userPreferences$;
|
||||
groupedTabs$: Observable<any> = this.navigationFacade.groupedTabs$;
|
||||
userActionMenu$: Observable<any> = this.navigationFacade.userActionMenu$;
|
||||
currentUser$: Observable<any> = this.authService.currentUser$;
|
||||
|
||||
vm$ = combineLatest([
|
||||
this.tabs$,
|
||||
|
@ -51,9 +53,11 @@ export class NavbarUiComponent implements OnInit {
|
|||
this.appListStrings$,
|
||||
this.modStrings$,
|
||||
this.userPreferences$,
|
||||
this.groupedTabs$
|
||||
this.groupedTabs$,
|
||||
this.userActionMenu$,
|
||||
this.currentUser$
|
||||
]).pipe(
|
||||
map(([tabs, modules, appStrings, appListStrings, modStrings, userPreferences, groupedTabs]) => {
|
||||
map(([tabs, modules, appStrings, appListStrings, modStrings, userPreferences, groupedTabs, userActionMenu, currentUser]) => {
|
||||
|
||||
this.navbar.build(
|
||||
tabs,
|
||||
|
@ -63,7 +67,9 @@ export class NavbarUiComponent implements OnInit {
|
|||
appListStrings,
|
||||
this.menuItemThreshold,
|
||||
groupedTabs,
|
||||
userPreferences
|
||||
userPreferences,
|
||||
userActionMenu,
|
||||
currentUser
|
||||
)
|
||||
|
||||
return {
|
||||
|
@ -109,11 +115,7 @@ export class NavbarUiComponent implements OnInit {
|
|||
@HostListener('window:resize', ['$event'])
|
||||
onResize(event: any) {
|
||||
const innerWidth = event.target.innerWidth;
|
||||
if (innerWidth <= 768) {
|
||||
this.mobileNavbar = true;
|
||||
} else {
|
||||
this.mobileNavbar = false;
|
||||
}
|
||||
this.mobileNavbar = innerWidth <= 768;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
|
|
@ -35,6 +35,7 @@ export class AuthGuard implements CanActivate {
|
|||
take(1),
|
||||
map((user: any) => {
|
||||
if (user && user.active === true) {
|
||||
this.authService.setCurrentUser(user);
|
||||
return true;
|
||||
}
|
||||
// Re-direct to login
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {Injectable} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {HttpClient, HttpErrorResponse, HttpHeaders, HttpParams} from '@angular/common/http';
|
||||
import {BehaviorSubject, Observable, throwError} from 'rxjs';
|
||||
import {catchError, finalize, take} from 'rxjs/operators';
|
||||
import {BehaviorSubject, throwError} from 'rxjs';
|
||||
import {catchError, distinctUntilChanged, finalize, take} from 'rxjs/operators';
|
||||
import {LoginUiComponent} from '@components/login/login.component';
|
||||
import {User} from '@services/user/user';
|
||||
import {MessageService} from '@services/message/message.service';
|
||||
|
@ -15,10 +15,10 @@ import {SystemConfigFacade} from "@base/facades/system-config/system-config.faca
|
|||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
public currentUser$: Observable<User>;
|
||||
private currentUserSubject: BehaviorSubject<User>;
|
||||
defaultTimeout: string = '3600';
|
||||
private currentUserSubject = new BehaviorSubject<User>({} as User);
|
||||
public currentUser$ = this.currentUserSubject.asObservable().pipe(distinctUntilChanged());
|
||||
public isUserLoggedIn: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
defaultTimeout: string = '3600';
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
|
@ -29,8 +29,14 @@ export class AuthService {
|
|||
private bnIdle: BnNgIdleService,
|
||||
protected systemConfigFacade: SystemConfigFacade
|
||||
) {
|
||||
this.currentUserSubject = new BehaviorSubject<User>(null);
|
||||
this.currentUser$ = this.currentUserSubject.asObservable();
|
||||
}
|
||||
|
||||
getCurrentUser(): User {
|
||||
return this.currentUserSubject.value;
|
||||
}
|
||||
|
||||
setCurrentUser(data) {
|
||||
this.currentUserSubject.next(data);
|
||||
}
|
||||
|
||||
doLogin(
|
||||
|
@ -56,6 +62,7 @@ export class AuthService {
|
|||
).subscribe((response: any) => {
|
||||
onSuccess(caller, response);
|
||||
this.isUserLoggedIn.next(true);
|
||||
this.setCurrentUser(response);
|
||||
|
||||
let duration = response.duration;
|
||||
|
||||
|
@ -76,7 +83,7 @@ export class AuthService {
|
|||
})
|
||||
}, (error: HttpErrorResponse) => {
|
||||
onError(caller, error);
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
export class User {
|
||||
id: string;
|
||||
username: string;
|
||||
password: string;
|
||||
salutation?: string;
|
||||
status: string;
|
||||
isAdmin: boolean;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
|
|
|
@ -164,14 +164,16 @@ class NavbarHandler extends LegacyHandler implements NavigationProviderInterface
|
|||
*/
|
||||
protected function fetchUserActionMenu(): array
|
||||
{
|
||||
global $current_user;
|
||||
|
||||
$actions['LBL_PROFILE'] = [
|
||||
'name' => 'profile',
|
||||
'labelKey' => 'LBL_PROFILE',
|
||||
'url' => 'index.php?module=Users&action=EditView&record=1',
|
||||
'url' => 'index.php?module=Users&action=EditView&record=' . $current_user->id,
|
||||
'icon' => '',
|
||||
];
|
||||
|
||||
//order matters
|
||||
// Order matters
|
||||
$actionLabelMap = [
|
||||
'LBL_PROFILE' => 'profile',
|
||||
'LBL_EMPLOYEES' => 'employees',
|
||||
|
@ -209,7 +211,9 @@ class NavbarHandler extends LegacyHandler implements NavigationProviderInterface
|
|||
$userActionMenu = [];
|
||||
|
||||
foreach ($actionKeys as $key) {
|
||||
$userActionMenu[] = $actions[$key];
|
||||
if (isset($actions[$key])) {
|
||||
$userActionMenu[] = $actions[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($userActionMenu);
|
||||
|
|
|
@ -8,6 +8,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
|||
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
|
||||
|
||||
/**
|
||||
|
@ -43,10 +44,24 @@ class SecurityController extends AbstractController
|
|||
|
||||
/**
|
||||
* @Route("/session-status", name="app_session_status", methods={"GET"})
|
||||
* @throws Exception
|
||||
* @param Security $security
|
||||
* @return JsonResponse
|
||||
*/
|
||||
public function sessionStatus(): JsonResponse
|
||||
public function sessionStatus(Security $security): JsonResponse
|
||||
{
|
||||
return new JsonResponse(['active' => true], Response::HTTP_OK);
|
||||
$user = $security->getUser();
|
||||
$id = $user->getId();
|
||||
$firstName = $user->getFirstName();
|
||||
$lastName = $user->getLastName();
|
||||
|
||||
$data =
|
||||
[
|
||||
'active' => true,
|
||||
'id' => $id,
|
||||
'firstName' => $firstName,
|
||||
'lastName' => $lastName
|
||||
];
|
||||
|
||||
return new JsonResponse($data, Response::HTTP_OK);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
|
|||
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
|
||||
use App\Service\NavigationProviderInterface;
|
||||
use App\Entity\Navbar;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
|
||||
final class NavbarItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface
|
||||
{
|
||||
|
@ -14,13 +15,20 @@ final class NavbarItemDataProvider implements ItemDataProviderInterface, Restric
|
|||
*/
|
||||
private $navigationService;
|
||||
|
||||
/**
|
||||
* @var Security
|
||||
*/
|
||||
private $security;
|
||||
|
||||
/**
|
||||
* NavbarItemDataProvider constructor.
|
||||
* @param NavigationProviderInterface $navigationService
|
||||
* @param Security $security
|
||||
*/
|
||||
public function __construct(NavigationProviderInterface $navigationService)
|
||||
public function __construct(NavigationProviderInterface $navigationService, Security $security)
|
||||
{
|
||||
$this->navigationService = $navigationService;
|
||||
$this->security = $security;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -46,8 +54,8 @@ final class NavbarItemDataProvider implements ItemDataProviderInterface, Restric
|
|||
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?Navbar
|
||||
{
|
||||
$navbar = $this->navigationService->getNavbar();
|
||||
// This should be updated once we have authentication.
|
||||
$navbar->userID = 1;
|
||||
$user = $this->security->getUser();
|
||||
$navbar->userID = $user->getId();
|
||||
|
||||
return $navbar;
|
||||
}
|
||||
|
|
|
@ -2,16 +2,30 @@
|
|||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Core\Annotation\ApiProperty;
|
||||
use ApiPlatform\Core\Annotation\ApiResource;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* @ApiResource(
|
||||
* attributes={"security"="is_granted('ROLE_USER')"},
|
||||
* itemOperations={
|
||||
* "get"
|
||||
* },
|
||||
* collectionOperations={
|
||||
* },
|
||||
* graphql={
|
||||
* "item_query",
|
||||
* },
|
||||
* )
|
||||
* @ORM\Entity(repositoryClass="App\Repository\UserRepository")
|
||||
* @ORM\Table(name="users")
|
||||
*/
|
||||
class User implements UserInterface
|
||||
{
|
||||
/**
|
||||
* @ApiProperty(identifier=true)
|
||||
* @ORM\Id()
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
|
@ -52,12 +66,14 @@ class User implements UserInterface
|
|||
private $sugar_login;
|
||||
|
||||
/**
|
||||
* @ApiProperty
|
||||
* @var string
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
private $first_name;
|
||||
|
||||
/**
|
||||
* @ApiProperty
|
||||
* @var string
|
||||
* @ORM\Column(type="string")
|
||||
*/
|
||||
|
@ -261,7 +277,10 @@ class User implements UserInterface
|
|||
return $this->is_admin;
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
/**
|
||||
* @return string|int
|
||||
*/
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
@ -308,6 +327,22 @@ class User implements UserInterface
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getFirstName(): string
|
||||
{
|
||||
return $this->first_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getLastName(): string
|
||||
{
|
||||
return $this->last_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserInterface
|
||||
*/
|
||||
|
|
|
@ -176,9 +176,17 @@ class LoginFormAuthenticator extends AbstractFormLoginAuthenticator implements P
|
|||
*/
|
||||
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
|
||||
{
|
||||
$user = $token->getUser();
|
||||
$id = $user->getId();
|
||||
$firstName = $user->getFirstName();
|
||||
$lastName = $user->getLastName();
|
||||
|
||||
$data = [
|
||||
'status' => 'success',
|
||||
'duration' => $this->session->getMetadataBag()->getLifetime()
|
||||
'duration' => $this->session->getMetadataBag()->getLifetime(),
|
||||
'id' => $id,
|
||||
'firstName' => $firstName,
|
||||
'lastName' => $lastName
|
||||
];
|
||||
|
||||
return new JsonResponse($data, Response::HTTP_OK);
|
||||
|
|
|
@ -213,7 +213,7 @@ final class NavbarTest extends Unit
|
|||
[
|
||||
'name' => 'profile',
|
||||
'labelKey' => 'LBL_PROFILE',
|
||||
'url' => 'index.php?module=Users&action=EditView&record=1',
|
||||
'url' => 'index.php?module=Users&action=EditView&record=',
|
||||
'icon' => '',
|
||||
],
|
||||
[
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue