diff --git a/api/auth.go b/api/auth.go index f01141e..d1b27a5 100644 --- a/api/auth.go +++ b/api/auth.go @@ -155,6 +155,32 @@ func (api *WebAPI) GetManagerList(r *Request) { }) } +func (api *WebAPI) GetManagerListWithRoleOn(r *Request) { + + pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) + handleErr(err, r) //todo handle invalid id + + sess := api.Session.StartFasthttp(r.Ctx) + manager := sess.Get("manager") + + if manager == nil { + r.Json(JsonResponse{ + Ok: false, + Message: "Unauthorized", + }, 401) + return + } + + managers := api.Database.GetManagerListWithRoleOn(pid) + + r.OkJson(JsonResponse{ + Ok: true, + Content: GetManagerListWithRoleOnResponse{ + Managers: managers, + }, + }) +} + func (api *WebAPI) PromoteManager(r *Request) { id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) diff --git a/api/main.go b/api/main.go index 1c49b7d..1d1875c 100644 --- a/api/main.go +++ b/api/main.go @@ -106,8 +106,10 @@ func New() *WebAPI { api.router.GET("/logout", LogRequestMiddleware(api.Logout)) api.router.GET("/account", LogRequestMiddleware(api.GetAccountDetails)) api.router.GET("/manager/list", LogRequestMiddleware(api.GetManagerList)) + api.router.GET("/manager/list_for_project/:id", LogRequestMiddleware(api.GetManagerListWithRoleOn)) api.router.GET("/manager/promote/:id", LogRequestMiddleware(api.PromoteManager)) api.router.GET("/manager/demote/:id", LogRequestMiddleware(api.DemoteManager)) + api.router.POST("/manager/set_role_for_project/:id", LogRequestMiddleware(api.SetManagerRoleOnProject)) api.router.NotFound = func(ctx *fasthttp.RequestCtx) { diff --git a/api/models.go b/api/models.go index 682c67e..f28686f 100644 --- a/api/models.go +++ b/api/models.go @@ -81,6 +81,10 @@ type GetManagerListResponse struct { Managers *[]storage.Manager `json:"managers"` } +type GetManagerListWithRoleOnResponse struct { + Managers *[]storage.ManagerRoleOn `json:"managers"` +} + type GetLogRequest struct { Level storage.LogLevel `json:"level"` Since int64 `json:"since"` @@ -263,6 +267,11 @@ func (w *CreateWorkerAccessRequest) isValid() bool { return true } +type SetManagerRoleOnProjectRequest struct { + Manager int64 `json:"manager"` + Role storage.ManagerRole `json:"role"` +} + type Info struct { Name string `json:"name"` Version string `json:"version"` diff --git a/api/project.go b/api/project.go index 650dfdc..e8e87be 100644 --- a/api/project.go +++ b/api/project.go @@ -100,7 +100,7 @@ func (api *WebAPI) CreateProject(r *Request) { return } - api.Database.SetManagerRoleOn(manager.(*storage.Manager), id, + api.Database.SetManagerRoleOn(manager.(*storage.Manager).Id, id, storage.ROLE_MANAGE_ACCESS|storage.ROLE_READ|storage.ROLE_EDIT) r.OkJson(JsonResponse{ Ok: true, @@ -403,3 +403,35 @@ func (api *WebAPI) RejectAccessRequest(r *Request) { }) } } + +func (api *WebAPI) SetManagerRoleOnProject(r *Request) { + + pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) + handleErr(err, r) //todo handle invalid id + + req := &SetManagerRoleOnProjectRequest{} + err = json.Unmarshal(r.Ctx.Request.Body(), req) + if err != nil { + r.Json(JsonResponse{ + Ok: false, + Message: "Could not parse request", + }, 400) + return + } + + sess := api.Session.StartFasthttp(r.Ctx) + manager := sess.Get("manager") + + if !isActionOnProjectAuthorized(pid, manager, storage.ROLE_MANAGE_ACCESS, api.Database) { + r.Json(JsonResponse{ + Message: "Unauthorized", + Ok: false, + }, 403) + return + } + + api.Database.SetManagerRoleOn(req.Manager, pid, req.Role) + r.OkJson(JsonResponse{ + Ok: true, + }) +} diff --git a/storage/auth.go b/storage/auth.go index 7d5aab3..d195224 100644 --- a/storage/auth.go +++ b/storage/auth.go @@ -3,6 +3,7 @@ package storage import ( "bytes" "crypto" + "database/sql" "errors" "github.com/Sirupsen/logrus" ) @@ -23,6 +24,11 @@ type Manager struct { RegisterTime int64 `json:"register_time"` } +type ManagerRoleOn struct { + Manager Manager `json:"manager"` + Role ManagerRole `json:"role"` +} + func (database *Database) ValidateCredentials(username []byte, password []byte) (*Manager, error) { db := database.getDB() @@ -142,20 +148,28 @@ func (database *Database) GetManagerRoleOn(manager *Manager, projectId int64) Ma return role } -func (database *Database) SetManagerRoleOn(manager *Manager, projectId int64, role ManagerRole) { +func (database *Database) SetManagerRoleOn(manager int64, projectId int64, role ManagerRole) { db := database.getDB() - res, err := db.Exec(`INSERT INTO manager_has_role_on_project (manager, role, project) - VALUES ($1,$2,$3) ON CONFLICT (manager, project) DO UPDATE SET role=$2`, - manager.Id, role, projectId) - handleErr(err) + var res sql.Result + var err error + if role == 0 { + res, err = db.Exec(`DELETE FROM manager_has_role_on_project WHERE manager=$1 AND project=$2`, + manager, projectId) + } else { + res, err = db.Exec(`INSERT INTO manager_has_role_on_project (manager, role, project) + VALUES ($1,$2,$3) ON CONFLICT (manager, project) DO UPDATE SET role=$2`, + manager, role, projectId) + } + + handleErr(err) rowsAffected, _ := res.RowsAffected() logrus.WithFields(logrus.Fields{ "role": role, - "manager": manager.Username, + "manager": manager, "rowsAffected": rowsAffected, "project": projectId, }).Info("Set manager role on project") @@ -177,3 +191,29 @@ func (database *Database) GetManagerList() *[]Manager { return &managers } + +func (database *Database) GetManagerListWithRoleOn(project int64) *[]ManagerRoleOn { + + db := database.getDB() + + rows, err := db.Query(`SELECT id, register_time, tracker_admin, username, role + FROM manager + LEFT JOIN manager_has_role_on_project mhrop on + manager.id = mhrop.manager + WHERE project=$1 ORDER BY id`, project) + handleErr(err) + + managers := make([]ManagerRoleOn, 0) + + for rows.Next() { + m := Manager{} + var role ManagerRole + _ = rows.Scan(&m.Id, &m.RegisterTime, &m.WebsiteAdmin, &m.Username, &role) + managers = append(managers, ManagerRoleOn{ + Manager: m, + Role: role, + }) + } + + return &managers +} diff --git a/web/angular/src/app/api.service.ts b/web/angular/src/app/api.service.ts index 0c9ad43..236b47a 100755 --- a/web/angular/src/app/api.service.ts +++ b/web/angular/src/app/api.service.ts @@ -69,10 +69,14 @@ export class ApiService { return this.http.get(this.url + `/project/access_list/${project}`) } - getAllManagers() { + getManagerList() { return this.http.get(this.url + "/manager/list") } + getManagerListWithRoleOn(project: number) { + return this.http.get(this.url + "/manager/list_for_project/" + project) + } + promote(managerId: number) { return this.http.get(this.url + `/manager/promote/${managerId}`) } @@ -89,4 +93,9 @@ export class ApiService { return this.http.post(this.url + `/project/reject_request/${pid}/${wid}`, null) } + setManagerRoleOnProject(pid: number, role: number, manager: number) { + return this.http.post(this.url + `/manager/set_role_for_project/${pid}`, + {"role": role, "manager": manager}) + } + } diff --git a/web/angular/src/app/app.component.css b/web/angular/src/app/app.component.css index 6558a6b..b1851ff 100755 --- a/web/angular/src/app/app.component.css +++ b/web/angular/src/app/app.component.css @@ -1,7 +1,3 @@ -.nav-spacer { - flex: 1 1 auto; -} - .icon { padding: 0 14px; } diff --git a/web/angular/src/app/app.component.html b/web/angular/src/app/app.component.html index 51230dc..61c121b 100755 --- a/web/angular/src/app/app.component.html +++ b/web/angular/src/app/app.component.html @@ -38,7 +38,7 @@ >{{"nav.manager_list" | translate}} - + diff --git a/web/angular/src/app/app.module.ts b/web/angular/src/app/app.module.ts index 2a423b8..f9ebf6e 100755 --- a/web/angular/src/app/app.module.ts +++ b/web/angular/src/app/app.module.ts @@ -50,6 +50,8 @@ import {WorkerDashboardComponent} from './worker-dashboard/worker-dashboard.comp import {ProjectPermsComponent} from './project-perms/project-perms.component'; import {ManagerListComponent} from './manager-list/manager-list.component'; import {ProjectSelectComponent} from './project-select/project-select.component'; +import {ManagerSelectComponent} from './manager-select/manager-select.component'; +import {ProjectIconComponent} from './project-icon/project-icon.component'; export function createTranslateLoader(http: HttpClient) { @@ -72,6 +74,8 @@ export function createTranslateLoader(http: HttpClient) { ProjectPermsComponent, ManagerListComponent, ProjectSelectComponent, + ManagerSelectComponent, + ProjectIconComponent, ], imports: [ BrowserModule, diff --git a/web/angular/src/app/auth.service.ts b/web/angular/src/app/auth.service.ts index a34ee34..b3a497b 100644 --- a/web/angular/src/app/auth.service.ts +++ b/web/angular/src/app/auth.service.ts @@ -3,6 +3,7 @@ import {ApiService} from "./api.service"; import {Credentials} from "./models/credentials"; import {MessengerService} from "./messenger.service"; import {Router} from "@angular/router"; +import {Manager} from "./models/manager"; @Injectable({ providedIn: 'root' diff --git a/web/angular/src/app/create-project/create-project.component.html b/web/angular/src/app/create-project/create-project.component.html index 8c8be77..8500417 100644 --- a/web/angular/src/app/create-project/create-project.component.html +++ b/web/angular/src/app/create-project/create-project.component.html @@ -6,13 +6,20 @@ {{"project.name" | translate}} - + {{ "project.clone_url" | translate}} + [placeholder]="'project.clone_url_placeholder' | translate" required> + + + {{"project.version" | translate}} + {{ "project.git_repo" | translate }} diff --git a/web/angular/src/app/manager-list/manager-list.component.ts b/web/angular/src/app/manager-list/manager-list.component.ts index 2eb45b1..989e8cf 100644 --- a/web/angular/src/app/manager-list/manager-list.component.ts +++ b/web/angular/src/app/manager-list/manager-list.component.ts @@ -55,7 +55,7 @@ export class ManagerListComponent implements OnInit { } private getManagers() { - this.apiService.getAllManagers() + this.apiService.getManagerList() .subscribe(data => { this.data.data = data["content"]["managers"] }, diff --git a/web/angular/src/app/manager-select/manager-select.component.css b/web/angular/src/app/manager-select/manager-select.component.css new file mode 100644 index 0000000..ef0ee71 --- /dev/null +++ b/web/angular/src/app/manager-select/manager-select.component.css @@ -0,0 +1,3 @@ +.mat-form-field { + width: 100%; +} diff --git a/web/angular/src/app/manager-select/manager-select.component.html b/web/angular/src/app/manager-select/manager-select.component.html new file mode 100644 index 0000000..a2bcb05 --- /dev/null +++ b/web/angular/src/app/manager-select/manager-select.component.html @@ -0,0 +1,16 @@ + + {{"project.manager_select" | translate}} + + + + {{"project_select.loading" | translate}} + + + supervisor_account + person + {{m.username}} + + + diff --git a/web/angular/src/app/manager-select/manager-select.component.ts b/web/angular/src/app/manager-select/manager-select.component.ts new file mode 100644 index 0000000..4d2c3ce --- /dev/null +++ b/web/angular/src/app/manager-select/manager-select.component.ts @@ -0,0 +1,30 @@ +import {Component, EventEmitter, OnInit, Output} from '@angular/core'; +import {ApiService} from "../api.service"; +import {Manager} from "../models/manager"; + +@Component({ + selector: 'manager-select', + templateUrl: './manager-select.component.html', + styleUrls: ['./manager-select.component.css'] +}) +export class ManagerSelectComponent implements OnInit { + + manager: Manager; + managerList: Manager[]; + + @Output() + managerChange = new EventEmitter(); + + constructor(private apiService: ApiService) { + } + + ngOnInit() { + } + + loadManagerList() { + this.apiService.getManagerList() + .subscribe(data => this.managerList = data["content"]["managers"]) + } + + +} diff --git a/web/angular/src/app/models/manager.ts b/web/angular/src/app/models/manager.ts index 617c786..ffa2240 100644 --- a/web/angular/src/app/models/manager.ts +++ b/web/angular/src/app/models/manager.ts @@ -1,6 +1,54 @@ -interface Manager { +export interface Manager { id: number; username: string tracker_admin: boolean register_time: number } + +export class ManagerRoleOnProject { + manager: Manager; + role: number; + + public static fromEntity(data: { role: number, manager: Manager }): ManagerRoleOnProject { + let m = new ManagerRoleOnProject(); + m.role = data.role; + m.manager = data.manager; + return m; + } + + get readRole(): boolean { + return (this.role & 1) != 0 + } + + set readRole(role: boolean) { + if (role) { + this.role |= 1 + } else { + this.role &= ~1 + } + } + + get editRole(): boolean { + return (this.role & 2) != 0 + } + + set editRole(role: boolean) { + if (role) { + this.role |= 2 + } else { + this.role &= ~2 + } + } + + get manageRole(): boolean { + return (this.role & 4) != 0 + } + + set manageRole(role: boolean) { + if (role) { + this.role |= 4 + } else { + this.role &= ~4 + } + } +} diff --git a/web/angular/src/app/project-dashboard/project-dashboard.component.ts b/web/angular/src/app/project-dashboard/project-dashboard.component.ts index 8029580..bc29c98 100644 --- a/web/angular/src/app/project-dashboard/project-dashboard.component.ts +++ b/web/angular/src/app/project-dashboard/project-dashboard.component.ts @@ -5,6 +5,8 @@ import {ActivatedRoute} from "@angular/router"; import {Chart} from "chart.js"; import {AssignedTasks, MonitoringSnapshot} from "../models/monitoring"; +import {TranslateService} from "@ngx-translate/core"; +import {MessengerService} from "../messenger.service"; @Component({ @@ -41,7 +43,10 @@ export class ProjectDashboardComponent implements OnInit { lastSnapshot: MonitoringSnapshot; assignees: AssignedTasks[]; - constructor(private apiService: ApiService, private route: ActivatedRoute) { + constructor(private apiService: ApiService, + private route: ActivatedRoute, + private translate: TranslateService, + private messenger: MessengerService) { } ngOnInit(): void { @@ -305,28 +310,32 @@ export class ProjectDashboardComponent implements OnInit { private getProject() { this.apiService.getProject(this.projectId).subscribe((data: any) => { - this.project = data.content.project; + this.project = data.content.project; - this.apiService.getMonitoringSnapshots(60, this.projectId) - .subscribe((data: any) => { - this.snapshots = data.content.snapshots; - this.lastSnapshot = this.snapshots ? this.snapshots.sort((a, b) => { - return b.time_stamp - a.time_stamp - })[0] : null; + this.apiService.getMonitoringSnapshots(60, this.projectId) + .subscribe((data: any) => { + this.snapshots = data.content.snapshots; + this.lastSnapshot = this.snapshots ? this.snapshots.sort((a, b) => { + return b.time_stamp - a.time_stamp + })[0] : null; - this.setupTimeline(); - this.setupStatusPie(); + this.setupTimeline(); + this.setupStatusPie(); - if (!this.snapshots) { - return - } + if (!this.snapshots) { + return + } - this.apiService.getAssigneeStats(this.projectId) - .subscribe((data: any) => { - this.assignees = data.content.assignees; - this.setupAssigneesPie(); - }); - }) - }) + this.apiService.getAssigneeStats(this.projectId) + .subscribe((data: any) => { + this.assignees = data.content.assignees; + this.setupAssigneesPie(); + }); + }) + }, + error => { + this.translate.get("messenger.unauthorized").subscribe(t => + this.messenger.show(t)) + }) } } diff --git a/web/angular/src/app/project-icon/project-icon.component.css b/web/angular/src/app/project-icon/project-icon.component.css new file mode 100644 index 0000000..e69de29 diff --git a/web/angular/src/app/project-icon/project-icon.component.html b/web/angular/src/app/project-icon/project-icon.component.html new file mode 100644 index 0000000..4bde839 --- /dev/null +++ b/web/angular/src/app/project-icon/project-icon.component.html @@ -0,0 +1,3 @@ +public +lock +visibility_off diff --git a/web/angular/src/app/project-icon/project-icon.component.ts b/web/angular/src/app/project-icon/project-icon.component.ts new file mode 100644 index 0000000..c478b21 --- /dev/null +++ b/web/angular/src/app/project-icon/project-icon.component.ts @@ -0,0 +1,21 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {Project} from "../models/project"; + +@Component({ + selector: 'project-icon', + templateUrl: './project-icon.component.html', + styleUrls: ['./project-icon.component.css'] +}) +export class ProjectIconComponent implements OnInit { + + + @Input() + project: Project; + + constructor() { + } + + ngOnInit() { + } + +} diff --git a/web/angular/src/app/project-list/project-list.component.css b/web/angular/src/app/project-list/project-list.component.css index 6b09a14..99e25a2 100755 --- a/web/angular/src/app/project-list/project-list.component.css +++ b/web/angular/src/app/project-list/project-list.component.css @@ -2,6 +2,6 @@ button { margin-right: 15px; } -mat-panel-title > mat-icon { +mat-panel-title > project-icon { margin-right: 1em; } diff --git a/web/angular/src/app/project-list/project-list.component.html b/web/angular/src/app/project-list/project-list.component.html index a1f3afe..8e3fffe 100755 --- a/web/angular/src/app/project-list/project-list.component.html +++ b/web/angular/src/app/project-list/project-list.component.html @@ -13,11 +13,7 @@ - public - - lock - - block + {{project.id}}{{project.name}} {{project.motd}} diff --git a/web/angular/src/app/project-perms/project-perms.component.css b/web/angular/src/app/project-perms/project-perms.component.css index eed824e..0944ea3 100644 --- a/web/angular/src/app/project-perms/project-perms.component.css +++ b/web/angular/src/app/project-perms/project-perms.component.css @@ -6,3 +6,7 @@ button { .request { color: #757575; } + +mat-checkbox { + margin-right: 10px; +} diff --git a/web/angular/src/app/project-perms/project-perms.component.html b/web/angular/src/app/project-perms/project-perms.component.html index 338fde8..44cc861 100644 --- a/web/angular/src/app/project-perms/project-perms.component.html +++ b/web/angular/src/app/project-perms/project-perms.component.html @@ -9,8 +9,9 @@ {{"perms.subtitle" | translate}} - - + +

{{"perms.workers" | translate}}

+ library_add get_app @@ -32,7 +33,33 @@ -

+

{{"perms.no_workers"|translate}}

+ +

{{"perms.managers" | translate}}

+ + + + supervisor_account + person + {{m.manager.username}} + + {{"perms.read"|translate}} + {{"perms.edit"|translate}} + {{"perms.manage"|translate}} + + +
+ +

block {{"perms.unauthorized" | translate}}

diff --git a/web/angular/src/app/project-perms/project-perms.component.ts b/web/angular/src/app/project-perms/project-perms.component.ts index c081c6a..1a6ea22 100644 --- a/web/angular/src/app/project-perms/project-perms.component.ts +++ b/web/angular/src/app/project-perms/project-perms.component.ts @@ -1,12 +1,14 @@ import {Component, OnInit} from '@angular/core'; import {ApiService} from "../api.service"; import {Project} from "../models/project"; -import {ActivatedRoute, Router} from "@angular/router"; -import {MessengerService} from "../messenger.service"; -import {TranslateService} from "@ngx-translate/core"; +import {ActivatedRoute} from "@angular/router"; import * as moment from "moment" import {WorkerAccess} from "../models/worker-access"; +import {AuthService} from "../auth.service"; +import {Manager, ManagerRoleOnProject} from "../models/manager"; +import {MessengerService} from "../messenger.service"; +import {TranslateService} from "@ngx-translate/core"; @Component({ selector: 'app-project-perms', @@ -17,14 +19,15 @@ export class ProjectPermsComponent implements OnInit { constructor(private apiService: ApiService, private route: ActivatedRoute, - private messengerService: MessengerService, private translate: TranslateService, - private router: Router) { + private messenger: MessengerService, + public auth: AuthService) { } project: Project; private projectId: number; accesses: WorkerAccess[]; + managerRoles: ManagerRoleOnProject; unauthorized: boolean = false; moment = moment; @@ -33,17 +36,24 @@ export class ProjectPermsComponent implements OnInit { this.projectId = params["id"]; this.getProject(); this.getProjectAccesses(); + this.getProjectManagers(); }) } public acceptRequest(wa: WorkerAccess) { this.apiService.acceptWorkerAccessRequest(wa.worker.id, this.projectId) - .subscribe(() => this.getProjectAccesses()) + .subscribe(() => { + this.getProjectAccesses(); + this.translate.get("perms.set").subscribe(t => this.messenger.show(t)); + }) } public rejectRequest(wa: WorkerAccess) { this.apiService.rejectWorkerAccessRequest(wa.worker.id, this.projectId) - .subscribe(() => this.getProjectAccesses()) + .subscribe(() => { + this.getProjectAccesses(); + this.translate.get("perms.set").subscribe(t => this.messenger.show(t)); + }) } private getProject() { @@ -64,7 +74,31 @@ export class ProjectPermsComponent implements OnInit { }) } + private getProjectManagers() { + this.apiService.getManagerListWithRoleOn(this.projectId) + .subscribe(data => { + this.managerRoles = data["content"]["managers"].map(d => + ManagerRoleOnProject.fromEntity(d)) + }) + } + public refresh() { - this.getProjectAccesses() + this.getProjectAccesses(); + this.getProjectManagers(); + } + + public onSelectManager(manager: Manager) { + if (manager.id != this.auth.account.id) { + this.apiService.setManagerRoleOnProject(this.projectId, 1, manager.id) + .subscribe(() => this.refresh()) + } + } + + public onRoleChange(manager: ManagerRoleOnProject) { + this.apiService.setManagerRoleOnProject(this.projectId, manager.role, manager.manager.id) + .subscribe(() => { + this.refresh(); + this.translate.get("perms.set").subscribe(t => this.messenger.show(t)); + }) } } diff --git a/web/angular/src/app/project-select/project-select.component.html b/web/angular/src/app/project-select/project-select.component.html index cbc6745..60e7c37 100644 --- a/web/angular/src/app/project-select/project-select.component.html +++ b/web/angular/src/app/project-select/project-select.component.html @@ -11,8 +11,7 @@ {{"project_select.none" | translate}} - public - lock + {{p.id}} {{p.name}} diff --git a/web/angular/src/app/update-project/update-project.component.html b/web/angular/src/app/update-project/update-project.component.html index c44c5c4..e1a6630 100644 --- a/web/angular/src/app/update-project/update-project.component.html +++ b/web/angular/src/app/update-project/update-project.component.html @@ -5,25 +5,37 @@
- + {{"project.name" | translate}} + - + {{"project.clone_url" | translate}} + [placeholder]="'project.clone_url'|translate" + > + {{"project.version" | translate}} + + + + {{"project.git_repo" | translate}} - Changes on the master branch will be tracked if webhooks - are - enabled - + [placeholder]="'project.git_repo_placeholder'|translate" + > + {{'project.git_repo_hint'|translate}}
diff --git a/web/angular/src/assets/i18n/en.json b/web/angular/src/assets/i18n/en.json index b963c6c..e4fa40b 100644 --- a/web/angular/src/assets/i18n/en.json +++ b/web/angular/src/assets/i18n/en.json @@ -51,7 +51,7 @@ }, "project": { "name": "Project name", - "clone_url": "Clone url", + "clone_url": "Git clone url", "clone_url_placeholder": "Example: \"https://github.com/simon987/task_tracker\"", "git_repo_placeholder": "Example: \"simon987/task_tracker\"", "git_repo_hint": "Changes in the master branch will be tracked if webhooks are enabled", @@ -65,7 +65,9 @@ "motd": "Message of the day", "update": "Edit", "perms": "Permissions", - "chain": "Chain tasks to" + "chain": "Chain tasks to", + "manager_select": "Give access to manager", + "version": "Git version (commit hash)" }, "dashboard": { "title": "Dashboard for", @@ -107,7 +109,14 @@ "refresh": "Refresh", "pending": "(Pending)", "assign": "Assign", - "submit": "Submit" + "submit": "Submit", + "read": "Read", + "edit": "Edit", + "manage": "Manage permissions", + "set": "Changes saved", + "workers": "Workers", + "no_workers": "No workers have explicit access to this project", + "managers": "Managers" }, "messenger": { "close": "Close", diff --git a/web/angular/src/assets/i18n/fr.json b/web/angular/src/assets/i18n/fr.json index 578611b..827db1d 100644 --- a/web/angular/src/assets/i18n/fr.json +++ b/web/angular/src/assets/i18n/fr.json @@ -66,7 +66,9 @@ "motd": "Message du jour", "update": "Mettre à jour", "perms": "Permissions", - "chain": "Enchainer les tâches vers" + "chain": "Enchainer les tâches vers", + "manager_select": "Donner accès à", + "version": "Version git (hash du commit)" }, "dashboard": { "title": "Tableau de bord pour ", @@ -100,7 +102,7 @@ }, "perms": { "title": "Permissions du projet", - "subtitle": "Les Workers doivent faire un requête d'acces pour pouvoir travailler sur les projets privés", + "subtitle": "Les Workers doivent faire une requête d'acces pour pouvoir travailler sur les projets privés", "unauthorized": "Vous devez avoir ROLE_GESTION_ACCES sur ce project pour accéder à cette page", "created": "Créé le", "grant": "Accepter la requête", @@ -109,7 +111,14 @@ "refresh": "Refraichir", "pending": "(En attente)", "assign": "Assigner", - "submit": "Soumettre" + "submit": "Soumettre", + "read": "Lecture", + "edit": "Modification", + "manage": "Gestion des permissions", + "set": "Changements enregistrés", + "workers": "Workers", + "no_workers": "Aucun Worker n'a explicitement accès à ce projet", + "managers": "Managers" }, "messenger": { "close": "Fermer", diff --git a/web/angular/src/styles.css b/web/angular/src/styles.css index b6ac0cd..dc548cb 100644 --- a/web/angular/src/styles.css +++ b/web/angular/src/styles.css @@ -99,3 +99,12 @@ pre { color: #ff4081; } +.spacer { + flex: 1 1 auto; +} + +.mat-list-item-content > mat-icon { + margin-right: 15px; +} + +