diff --git a/.github/scripts/check_language_properties.py b/.github/scripts/check_language_properties.py index ea52fadd8..659ff7027 100644 --- a/.github/scripts/check_language_properties.py +++ b/.github/scripts/check_language_properties.py @@ -196,7 +196,9 @@ def check_for_differences(reference_file, file_list, branch, actor): if len(file_list) == 1: file_arr = file_list[0].split() - base_dir = os.path.abspath(os.path.join(os.getcwd(), "stirling-pdf", "src", "main", "resources")) + base_dir = os.path.abspath( + os.path.join(os.getcwd(), "stirling-pdf", "src", "main", "resources") + ) for file_path in file_arr: file_normpath = os.path.normpath(file_path) @@ -216,10 +218,19 @@ def check_for_differences(reference_file, file_list, branch, actor): or ( # only local windows command not file_normpath.startswith( - os.path.join("", "stirling-pdf", "src", "main", "resources", "messages_") + os.path.join( + "", "stirling-pdf", "src", "main", "resources", "messages_" + ) ) and not file_normpath.startswith( - os.path.join(os.getcwd(), "stirling-pdf", "src", "main", "resources", "messages_") + os.path.join( + os.getcwd(), + "stirling-pdf", + "src", + "main", + "resources", + "messages_", + ) ) ) or not file_normpath.endswith(".properties") @@ -377,7 +388,12 @@ if __name__ == "__main__": else: file_list = glob.glob( os.path.join( - os.getcwd(), "stirling-pdf", "src", "main", "resources", "messages_*.properties" + os.getcwd(), + "stirling-pdf", + "src", + "main", + "resources", + "messages_*.properties", ) ) update_missing_keys(args.reference_file, file_list) diff --git a/.github/workflows/check_properties.yml b/.github/workflows/check_properties.yml index c0662a824..eb62f7f5b 100644 --- a/.github/workflows/check_properties.yml +++ b/.github/workflows/check_properties.yml @@ -115,8 +115,11 @@ jobs: // Filter for relevant files based on the PR changes const changedFiles = files - .map(file => file.filename) - .filter(file => /^stirling-pdf\/src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file)); + .filter(file => + file.status !== "removed" && + /^stirling-pdf\/src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file.filename) + ) + .map(file => file.filename); console.log("Changed files:", changedFiles); diff --git a/.vscode/settings.json b/.vscode/settings.json index f759730f2..a2f0da613 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -86,4 +86,9 @@ "spring.initializr.defaultLanguage": "Java", "spring.initializr.defaultGroupId": "stirling.software.SPDF", "spring.initializr.defaultArtifactId": "SPDF", + "java.project.sourcePaths": [ + "stirling-pdf/src/main/java", + "common/src/main/java", + "proprietary/src/main/java" + ], } diff --git a/README.md b/README.md index 230d32e87..5551b98bb 100644 --- a/README.md +++ b/README.md @@ -117,46 +117,46 @@ Stirling-PDF currently supports 40 languages! | Language | Progress | | -------------------------------------------- | -------------------------------------- | | Arabic (العربية) (ar_AR) | ![68%](https://geps.dev/progress/68) | -| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![69%](https://geps.dev/progress/69) | +| Azerbaijani (Azərbaycan Dili) (az_AZ) | ![68%](https://geps.dev/progress/68) | | Basque (Euskara) (eu_ES) | ![40%](https://geps.dev/progress/40) | -| Bulgarian (Български) (bg_BG) | ![76%](https://geps.dev/progress/76) | +| Bulgarian (Български) (bg_BG) | ![75%](https://geps.dev/progress/75) | | Catalan (Català) (ca_CA) | ![75%](https://geps.dev/progress/75) | | Croatian (Hrvatski) (hr_HR) | ![67%](https://geps.dev/progress/67) | -| Czech (Česky) (cs_CZ) | ![78%](https://geps.dev/progress/78) | -| Danish (Dansk) (da_DK) | ![69%](https://geps.dev/progress/69) | -| Dutch (Nederlands) (nl_NL) | ![67%](https://geps.dev/progress/67) | +| Czech (Česky) (cs_CZ) | ![77%](https://geps.dev/progress/77) | +| Danish (Dansk) (da_DK) | ![68%](https://geps.dev/progress/68) | +| Dutch (Nederlands) (nl_NL) | ![66%](https://geps.dev/progress/66) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (US) (en_US) | ![100%](https://geps.dev/progress/100) | -| French (Français) (fr_FR) | ![77%](https://geps.dev/progress/77) | -| German (Deutsch) (de_DE) | ![93%](https://geps.dev/progress/93) | -| Greek (Ελληνικά) (el_GR) | ![75%](https://geps.dev/progress/75) | -| Hindi (हिंदी) (hi_IN) | ![75%](https://geps.dev/progress/75) | -| Hungarian (Magyar) (hu_HU) | ![95%](https://geps.dev/progress/95) | -| Indonesian (Bahasa Indonesia) (id_ID) | ![69%](https://geps.dev/progress/69) | -| Irish (Gaeilge) (ga_IE) | ![76%](https://geps.dev/progress/76) | +| French (Français) (fr_FR) | ![76%](https://geps.dev/progress/76) | +| German (Deutsch) (de_DE) | ![96%](https://geps.dev/progress/96) | +| Greek (Ελληνικά) (el_GR) | ![74%](https://geps.dev/progress/74) | +| Hindi (हिंदी) (hi_IN) | ![74%](https://geps.dev/progress/74) | +| Hungarian (Magyar) (hu_HU) | ![97%](https://geps.dev/progress/97) | +| Indonesian (Bahasa Indonesia) (id_ID) | ![68%](https://geps.dev/progress/68) | +| Irish (Gaeilge) (ga_IE) | ![75%](https://geps.dev/progress/75) | | Italian (Italiano) (it_IT) | ![87%](https://geps.dev/progress/87) | | Japanese (日本語) (ja_JP) | ![76%](https://geps.dev/progress/76) | | Korean (한국어) (ko_KR) | ![75%](https://geps.dev/progress/75) | | Norwegian (Norsk) (no_NB) | ![73%](https://geps.dev/progress/73) | -| Persian (فارسی) (fa_IR) | ![72%](https://geps.dev/progress/72) | -| Polish (Polski) (pl_PL) | ![80%](https://geps.dev/progress/80) | +| Persian (فارسی) (fa_IR) | ![71%](https://geps.dev/progress/71) | +| Polish (Polski) (pl_PL) | ![79%](https://geps.dev/progress/79) | | Portuguese (Português) (pt_PT) | ![76%](https://geps.dev/progress/76) | | Portuguese Brazilian (Português) (pt_BR) | ![84%](https://geps.dev/progress/84) | -| Romanian (Română) (ro_RO) | ![64%](https://geps.dev/progress/64) | +| Romanian (Română) (ro_RO) | ![63%](https://geps.dev/progress/63) | | Russian (Русский) (ru_RU) | ![76%](https://geps.dev/progress/76) | -| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![49%](https://geps.dev/progress/49) | -| Simplified Chinese (简体中文) (zh_CN) | ![95%](https://geps.dev/progress/95) | +| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![48%](https://geps.dev/progress/48) | +| Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) | | Slovakian (Slovensky) (sk_SK) | ![57%](https://geps.dev/progress/57) | | Slovenian (Slovenščina) (sl_SI) | ![79%](https://geps.dev/progress/79) | | Spanish (Español) (es_ES) | ![82%](https://geps.dev/progress/82) | -| Swedish (Svenska) (sv_SE) | ![73%](https://geps.dev/progress/73) | -| Thai (ไทย) (th_TH) | ![66%](https://geps.dev/progress/66) | +| Swedish (Svenska) (sv_SE) | ![72%](https://geps.dev/progress/72) | +| Thai (ไทย) (th_TH) | ![65%](https://geps.dev/progress/65) | | Tibetan (བོད་ཡིག་) (bo_CN) | ![0%](https://geps.dev/progress/0) | -| Traditional Chinese (繁體中文) (zh_TW) | ![84%](https://geps.dev/progress/84) | -| Turkish (Türkçe) (tr_TR) | ![82%](https://geps.dev/progress/82) | -| Ukrainian (Українська) (uk_UA) | ![79%](https://geps.dev/progress/79) | -| Vietnamese (Tiếng Việt) (vi_VN) | ![64%](https://geps.dev/progress/64) | -| Malayalam (മലയാളം) (ml_IN) | ![0%](https://geps.dev/progress/0) | +| Traditional Chinese (繁體中文) (zh_TW) | ![83%](https://geps.dev/progress/83) | +| Turkish (Türkçe) (tr_TR) | ![81%](https://geps.dev/progress/81) | +| Ukrainian (Українська) (uk_UA) | ![78%](https://geps.dev/progress/78) | +| Vietnamese (Tiếng Việt) (vi_VN) | ![63%](https://geps.dev/progress/63) | +| Malayalam (മലയാളം) (ml_IN) | ![81%](https://geps.dev/progress/81) | ## Stirling PDF Enterprise diff --git a/proprietary/src/main/java/stirling/software/proprietary/model/dto/TeamWithUserCountDTO.java b/proprietary/src/main/java/stirling/software/proprietary/model/dto/TeamWithUserCountDTO.java index 580e1148d..53e66993a 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/model/dto/TeamWithUserCountDTO.java +++ b/proprietary/src/main/java/stirling/software/proprietary/model/dto/TeamWithUserCountDTO.java @@ -10,11 +10,11 @@ public class TeamWithUserCountDTO { private Long id; private String name; private Long userCount; - + // Constructor for JPQL projection public TeamWithUserCountDTO(Long id, String name, Long userCount) { this.id = id; this.name = name; this.userCount = userCount; } -} \ No newline at end of file +} diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/InitialSecuritySetup.java b/proprietary/src/main/java/stirling/software/proprietary/security/InitialSecuritySetup.java index d36d891bc..23f1100e6 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/InitialSecuritySetup.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/InitialSecuritySetup.java @@ -2,6 +2,7 @@ package stirling.software.proprietary.security; import java.sql.SQLException; import java.util.List; +import java.util.Optional; import java.util.UUID; import org.springframework.stereotype.Component; @@ -53,10 +54,15 @@ public class InitialSecuritySetup { private void assignUsersToDefaultTeamIfMissing() { Team defaultTeam = teamService.getOrCreateDefaultTeam(); + Team internalTeam = teamService.getOrCreateInternalTeam(); List usersWithoutTeam = userService.getUsersWithoutTeam(); for (User user : usersWithoutTeam) { - user.setTeam(defaultTeam); + if (user.getUsername().equalsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) { + user.setTeam(internalTeam); + } else { + user.setTeam(defaultTeam); + } } userService.saveAll(usersWithoutTeam); // batch save @@ -108,6 +114,20 @@ public class InitialSecuritySetup { false); userService.addApiKeyToUser(Role.INTERNAL_API_USER.getRoleId()); log.info("Internal API user created: {}", Role.INTERNAL_API_USER.getRoleId()); + } else { + Optional internalApiUserOpt = + userService.findByUsernameIgnoreCase(Role.INTERNAL_API_USER.getRoleId()); + if (internalApiUserOpt.isPresent()) { + User internalApiUser = internalApiUserOpt.get(); + // move to team internal API user + if (!internalApiUser.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) { + log.info( + "Moving internal API user to team: {}", TeamService.INTERNAL_TEAM_NAME); + Team internalTeam = teamService.getOrCreateInternalTeam(); + + userService.changeUserTeam(internalApiUser, internalTeam); + } + } } userService.syncCustomApiUser(applicationProperties.getSecurity().getCustomGlobalAPIKey()); } diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java b/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java index 5bd992967..836b661eb 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java @@ -228,7 +228,7 @@ public class AccountWebController { User user = iterator.next(); if (user != null) { boolean shouldRemove = false; - + // Check if user is an INTERNAL_API_USER for (Authority authority : user.getAuthorities()) { if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) { @@ -237,12 +237,12 @@ public class AccountWebController { break; } } - + // Also check if user is part of the Internal team if (user.getTeam() != null && user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) { shouldRemove = true; } - + // Remove the user if either condition is true if (shouldRemove) { iterator.remove(); diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/TeamController.java b/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/TeamController.java index be0dda2b7..9c77af94a 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/TeamController.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/TeamController.java @@ -56,12 +56,12 @@ public class TeamController { return new RedirectView("/adminSettings?messageType=teamNameExists"); } Team team = existing.get(); - + // Prevent renaming the Internal team if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) { return new RedirectView("/adminSettings?messageType=internalTeamNotAccessible"); } - + team.setName(newName); teamRepository.save(team); return new RedirectView("/adminSettings?messageType=teamRenamed"); @@ -77,12 +77,12 @@ public class TeamController { } Team team = teamOpt.get(); - + // Prevent deleting the Internal team if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) { return new RedirectView("/adminSettings?messageType=internalTeamNotAccessible"); } - + long memberCount = userRepository.countByTeam(team); if (memberCount > 0) { return new RedirectView("/adminSettings?messageType=teamHasUsers"); @@ -91,36 +91,36 @@ public class TeamController { teamRepository.delete(team); return new RedirectView("/adminSettings?messageType=teamDeleted"); } - + @PreAuthorize("hasRole('ROLE_ADMIN')") @PostMapping("/addUser") @Transactional public RedirectView addUserToTeam( @RequestParam("teamId") Long teamId, @RequestParam("userId") Long userId) { - + // Find the team Team team = teamRepository.findById(teamId) .orElseThrow(() -> new RuntimeException("Team not found")); - + // Prevent adding users to the Internal team if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) { return new RedirectView("/teams?error=internalTeamNotAccessible"); } - + // Find the user User user = userRepository.findById(userId) .orElseThrow(() -> new RuntimeException("User not found")); - + // Check if user is in the Internal team - prevent moving them if (user.getTeam() != null && user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) { return new RedirectView("/teams/" + teamId + "?error=cannotMoveInternalUsers"); } - + // Assign user to team user.setTeam(team); userRepository.save(user); - + // Redirect back to team details page return new RedirectView("/teams/" + teamId + "?messageType=userAdded"); } diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java b/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java index 28034ca54..0499fe01b 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java @@ -246,7 +246,7 @@ public class UserController { // If the role ID is not valid, redirect with an error message return new RedirectView("/adminSettings?messageType=invalidRole", true); } - + // Use teamId if provided, otherwise use default team Long effectiveTeamId = teamId; if (effectiveTeamId == null) { @@ -261,7 +261,7 @@ public class UserController { return new RedirectView("/adminSettings?messageType=internalTeamNotAccessible", true); } } - + if (authType.equalsIgnoreCase(AuthenticationType.SSO.toString())) { userService.saveUser(username, AuthenticationType.SSO, effectiveTeamId, role); } else { @@ -309,7 +309,7 @@ public class UserController { return new RedirectView("/adminSettings?messageType=invalidRole", true); } User user = userOpt.get(); - + // Update the team if a teamId is provided if (teamId != null) { Team team = teamRepository.findById(teamId).orElse(null); @@ -318,17 +318,17 @@ public class UserController { if (TeamService.INTERNAL_TEAM_NAME.equals(team.getName())) { return new RedirectView("/adminSettings?messageType=internalTeamNotAccessible", true); } - + // Prevent moving users from Internal team if (user.getTeam() != null && TeamService.INTERNAL_TEAM_NAME.equals(user.getTeam().getName())) { return new RedirectView("/adminSettings?messageType=cannotMoveInternalUsers", true); } - + user.setTeam(team); userRepository.save(user); } } - + userService.changeRole(user, role); return new RedirectView( "/adminSettings", // Redirect to account page after adding the user diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java b/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java index 922ad57f8..d41b2aa75 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/controller/web/TeamWebController.java @@ -38,7 +38,7 @@ public class TeamWebController { public String listTeams(Model model) { // Get teams with user counts using a DTO projection List allTeamsWithCounts = teamRepository.findAllTeamsWithUserCount(); - + // Filter out the Internal team List teamsWithCounts = allTeamsWithCounts.stream() .filter(team -> !team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) @@ -58,7 +58,7 @@ public class TeamWebController { // Add data to the model model.addAttribute("teamsWithCounts", teamsWithCounts); model.addAttribute("teamLastRequest", teamLastRequest); - + return "accounts/teams"; } @@ -68,23 +68,23 @@ public class TeamWebController { // Get the team Team team = teamRepository.findById(id) .orElseThrow(() -> new RuntimeException("Team not found")); - + // Prevent access to Internal team if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) { return "redirect:/teams?error=internalTeamNotAccessible"; } - + // Get users for this team directly using the direct query List teamUsers = userRepository.findAllByTeamId(id); - + // Get all users not in this team for the Add User to Team dropdown // Exclude users that are in the Internal team List allUsers = userRepository.findAllWithTeam(); List availableUsers = allUsers.stream() - .filter(user -> (user.getTeam() == null || !user.getTeam().getId().equals(id)) && + .filter(user -> (user.getTeam() == null || !user.getTeam().getId().equals(id)) && (user.getTeam() == null || !user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME))) .toList(); - + // Get the latest session for each user in the team List userSessions = sessionRepository.findLatestSessionByTeamId(id); diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java b/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java index 517998ec2..a53eed6d1 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/database/repository/UserRepository.java @@ -29,7 +29,7 @@ public interface UserRepository extends JpaRepository { @Query(value = "SELECT u FROM User u LEFT JOIN FETCH u.team") List findAllWithTeam(); - + @Query("SELECT u FROM User u JOIN FETCH u.authorities JOIN FETCH u.team WHERE u.team.id = :teamId") List findAllByTeamId(@Param("teamId") Long teamId); diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java b/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java index c2461269a..d3e232f61 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java @@ -58,7 +58,7 @@ public class User implements Serializable { @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "user") private Set authorities = new HashSet<>(); - @ManyToOne(fetch = FetchType.LAZY) + @ManyToOne(fetch = FetchType.EAGER) @JoinColumn(name = "team_id") private Team team; diff --git a/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java b/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java index 9d1ae6fc8..50c8027f6 100644 --- a/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java +++ b/proprietary/src/main/java/stirling/software/proprietary/security/service/UserService.java @@ -371,6 +371,16 @@ public class UserService implements UserServiceInterface { databaseService.exportDatabase(); } + public void changeUserTeam(User user, Team team) + throws SQLException, UnsupportedProviderException { + if (team == null) { + team = getDefaultTeam(); + } + user.setTeam(team); + userRepository.save(user); + databaseService.exportDatabase(); + } + public boolean isPasswordCorrect(User user, String currentPassword) { return passwordEncoder.matches(currentPassword, user.getPassword()); } diff --git a/proprietary/src/main/resources/static/css/modern-tables.css b/proprietary/src/main/resources/static/css/modern-tables.css index 66bdd2aaf..156c39fed 100644 --- a/proprietary/src/main/resources/static/css/modern-tables.css +++ b/proprietary/src/main/resources/static/css/modern-tables.css @@ -384,4 +384,11 @@ padding: 0.75rem 1rem; border-radius: 0.5rem; border: 1px solid - } \ No newline at end of file +} + +.text-overflow { + max-width: 100px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} \ No newline at end of file diff --git a/proprietary/src/main/resources/templates/accounts/team-details.html b/proprietary/src/main/resources/templates/accounts/team-details.html index 81154c42d..aff0c4150 100644 --- a/proprietary/src/main/resources/templates/accounts/team-details.html +++ b/proprietary/src/main/resources/templates/accounts/team-details.html @@ -1,196 +1,200 @@ - - - - - + - - -
-
- - -
-
-
-

- - group - - Team Name -

+ + + + + + + +
+
+ + +
+
+
+

+ + group + + Team Name +

+
+ +
+
+
+
Total Members:
+
1
+
- -
-
-
-
Total Members:
-
1
-
-
- - - -
Members
- -
- - - - - - - - - - - - - - - - - - - -
IDUsernameRoleLast RequestStatus
1usernameRole2023-01-01 12:00:00 - - person - Enabled - - - person_off - Disabled - -
-
- - -
- person_off -

This team has no members yet.

- -
- - -
- -
+ + + +
Members
+ +
+ + + + + + + + + + + + + + + + + + + +
#UsernameRoleLast RequestStatus
1usernameRole + 2023-01-01 12:00:00 + + person + Enabled + + + person_off + Disabled + +
+
+ + +
+ person_off +

This team has no members yet.

+ +
+ + +
+
- - - - - - - -
- + + +
+ + \ No newline at end of file diff --git a/proprietary/src/main/resources/templates/accounts/teams.html b/proprietary/src/main/resources/templates/accounts/teams.html index 4d7c9f6a5..509c3f727 100644 --- a/proprietary/src/main/resources/templates/accounts/teams.html +++ b/proprietary/src/main/resources/templates/accounts/teams.html @@ -1,15 +1,20 @@ - + + - + + +
- +
@@ -20,7 +25,7 @@ Team Management
- +
@@ -29,19 +34,18 @@ Back to Settings
- + - +
@@ -49,7 +53,8 @@ - + @@ -58,18 +63,20 @@ - +
Team Name Total MembersLast RequestLast Request Actions
+
- + search View
+ onsubmit="return confirmDeleteTeam()"> - @@ -80,7 +87,7 @@
- +