From 8fe41b8fbbd2a7bc3c0c71648898847f5740d04c Mon Sep 17 00:00:00 2001 From: simon987 Date: Thu, 14 Feb 2019 22:04:00 -0500 Subject: [PATCH] Various little improvements. More work on permissions --- api/auth.go | 104 ++++++++++++++++++ api/main.go | 3 + main/main.go | 5 +- schema.sql | 4 +- storage/auth.go | 42 +++++-- test/schema.sql | 4 +- .../account-details.component.html | 4 + .../account-details.component.ts | 4 + web/angular/src/app/api.service.ts | 12 ++ web/angular/src/app/app-routing.module.ts | 4 +- web/angular/src/app/app.component.html | 8 ++ web/angular/src/app/app.module.ts | 2 + web/angular/src/app/auth.service.ts | 1 + .../create-project.component.html | 6 +- .../create-project.component.ts | 12 +- web/angular/src/app/logs/logs.component.css | 2 +- web/angular/src/app/logs/logs.component.html | 103 +++++++++-------- web/angular/src/app/logs/logs.component.ts | 2 +- .../manager-list/manager-list.component.css | 0 .../manager-list/manager-list.component.html | 53 +++++++++ .../manager-list/manager-list.component.ts | 70 ++++++++++++ web/angular/src/app/models/manager.ts | 3 +- .../project-list/project-list.component.html | 6 +- .../project-list/project-list.component.ts | 4 +- .../project-perms/project-perms.component.css | 7 -- .../project-perms.component.html | 3 +- .../project-perms/project-perms.component.ts | 2 +- .../worker-dashboard.component.ts | 2 +- web/angular/src/assets/i18n/en.json | 23 +++- web/angular/src/assets/i18n/fr.json | 25 ++++- web/angular/src/styles.css | 10 +- 31 files changed, 434 insertions(+), 96 deletions(-) create mode 100644 web/angular/src/app/manager-list/manager-list.component.css create mode 100644 web/angular/src/app/manager-list/manager-list.component.html create mode 100644 web/angular/src/app/manager-list/manager-list.component.ts diff --git a/api/auth.go b/api/auth.go index 0155545..f729b03 100644 --- a/api/auth.go +++ b/api/auth.go @@ -4,6 +4,7 @@ import ( "encoding/json" "github.com/Sirupsen/logrus" "github.com/simon987/task_tracker/storage" + "strconv" ) const MinPasswordLength = 8 @@ -31,6 +32,12 @@ type AccountDetails struct { Manager *storage.Manager `json:"manager,omitempty"` } +type GetAllManagersResponse struct { + Ok bool `json:"ok"` + Message string `json:"message,omitempty"` + Managers *[]storage.Manager `json:"managers"` +} + func (r *RegisterRequest) isValid() bool { return MinUsernameLength <= len(r.Username) && len(r.Username) <= MaxUsernameLength && @@ -158,3 +165,100 @@ func (api *WebAPI) AccountDetails(r *Request) { }) } } + +func (api *WebAPI) GetAllManagers(r *Request) { + + sess := api.Session.StartFasthttp(r.Ctx) + manager := sess.Get("manager") + + if manager == nil { + r.Json(GetAllManagersResponse{ + Ok: false, + Message: "Unauthorized", + }, 401) + return + } + + managers := api.Database.GetAllManagers() + + r.OkJson(GetAllManagersResponse{ + Ok: true, + Managers: managers, + }) +} + +func (api *WebAPI) PromoteManager(r *Request) { + + id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) + if err != nil || id <= 0 { + r.Json(CreateProjectResponse{ + Ok: false, + Message: "Invalid manager id", + }, 400) + return + } + + sess := api.Session.StartFasthttp(r.Ctx) + manager := sess.Get("manager") + + if !manager.(*storage.Manager).WebsiteAdmin || manager.(*storage.Manager).Id == id { + r.Json(GetAllManagersResponse{ + Ok: false, + Message: "Unauthorized", + }, 401) + return + } + + if !manager.(*storage.Manager).WebsiteAdmin { + r.Json(GetAllManagersResponse{ + Ok: false, + Message: "Unauthorized", + }, 403) + return + } + + api.Database.UpdateManager(&storage.Manager{ + Id: id, + WebsiteAdmin: true, + }) + + r.Ctx.Response.SetStatusCode(204) +} + +func (api *WebAPI) DemoteManager(r *Request) { + + id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) + if err != nil || id <= 0 { + r.Json(CreateProjectResponse{ + Ok: false, + Message: "Invalid manager id", + }, 400) + return + } + + sess := api.Session.StartFasthttp(r.Ctx) + manager := sess.Get("manager") + + if manager == nil { + r.Json(GetAllManagersResponse{ + Ok: false, + Message: "Unauthorized", + }, 401) + return + } + + if !manager.(*storage.Manager).WebsiteAdmin || manager.(*storage.Manager).Id == id { + r.Json(GetAllManagersResponse{ + Ok: false, + Message: "Unauthorized", + }, 403) + return + } + + api.Database.UpdateManager(&storage.Manager{ + Id: id, + WebsiteAdmin: false, + }) + + r.Ctx.Response.SetStatusCode(204) +} diff --git a/api/main.go b/api/main.go index 7adaf4d..0d0f813 100644 --- a/api/main.go +++ b/api/main.go @@ -106,6 +106,9 @@ func New() *WebAPI { api.router.POST("/login", LogRequestMiddleware(api.Login)) api.router.GET("/logout", LogRequestMiddleware(api.Logout)) api.router.GET("/account", LogRequestMiddleware(api.AccountDetails)) + api.router.GET("/manager/list", LogRequestMiddleware(api.GetAllManagers)) + api.router.GET("/manager/promote/:id", LogRequestMiddleware(api.PromoteManager)) + api.router.GET("/manager/demote/:id", LogRequestMiddleware(api.DemoteManager)) api.router.NotFound = func(ctx *fasthttp.RequestCtx) { diff --git a/main/main.go b/main/main.go index 0730f96..a9c3500 100644 --- a/main/main.go +++ b/main/main.go @@ -3,15 +3,14 @@ package main import ( "github.com/simon987/task_tracker/api" "github.com/simon987/task_tracker/config" - "github.com/simon987/task_tracker/storage" "math/rand" "time" ) func tmpDebugSetup() { - db := storage.Database{} - db.Reset() + //db := storage.Database{} + //db.Reset() } diff --git a/schema.sql b/schema.sql index 4bbc6a0..551c00e 100755 --- a/schema.sql +++ b/schema.sql @@ -69,7 +69,7 @@ CREATE TABLE manager ( id SERIAL PRIMARY KEY, register_time INTEGER NOT NULL, - website_admin BOOLEAN NOT NULL, + tracker_admin BOOLEAN NOT NULL, username TEXT UNIQUE NOT NULL, password BYTEA NOT NULL ); @@ -117,7 +117,7 @@ CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS $$ BEGIN IF NEW.id = 1 THEN - UPDATE manager SET website_admin= TRUE WHERE id = 1; + UPDATE manager SET tracker_admin= TRUE WHERE id = 1; end if; RETURN NEW; END; diff --git a/storage/auth.go b/storage/auth.go index 441848e..1a5f3f4 100644 --- a/storage/auth.go +++ b/storage/auth.go @@ -17,21 +17,22 @@ const ( ) type Manager struct { - Id int `json:"id"` + Id int64 `json:"id"` Username string `json:"username"` - WebsiteAdmin bool `json:"website_admin"` + WebsiteAdmin bool `json:"tracker_admin"` + RegisterTime int64 `json:"register_time"` } func (database *Database) ValidateCredentials(username []byte, password []byte) (*Manager, error) { db := database.getDB() - row := db.QueryRow(`SELECT id, password, website_admin FROM manager WHERE username=$1`, + row := db.QueryRow(`SELECT id, password, tracker_admin, register_time FROM manager WHERE username=$1`, username) manager := &Manager{} var passwordHash []byte - err := row.Scan(&manager.Id, &passwordHash, &manager.WebsiteAdmin) + err := row.Scan(&manager.Id, &passwordHash, &manager.WebsiteAdmin, &manager.RegisterTime) if err != nil { logrus.WithError(err).WithFields(logrus.Fields{ "username": username, @@ -66,11 +67,11 @@ func (database *Database) SaveManager(manager *Manager, password []byte) error { hash.Write([]byte(manager.Username)) hashedPassword := hash.Sum(nil) - row := db.QueryRow(`INSERT INTO manager (username, password, website_admin, register_time) - VALUES ($1,$2,$3, extract(epoch from now() at time zone 'utc')) RETURNING ID`, + row := db.QueryRow(`INSERT INTO manager (username, password, tracker_admin, register_time) + VALUES ($1,$2,$3, extract(epoch from now() at time zone 'utc')) RETURNING ID, register_time`, manager.Username, hashedPassword, manager.WebsiteAdmin) - err := row.Scan(&manager.Id) + err := row.Scan(&manager.Id, &manager.RegisterTime) if err != nil { logrus.WithError(err).WithFields(logrus.Fields{ "username": manager, @@ -92,16 +93,16 @@ func (database *Database) UpdateManager(manager *Manager) { db := database.getDB() - res, err := db.Exec(`UPDATE manager SET website_admin=$1 WHERE id=$2`, + res, err := db.Exec(`UPDATE manager SET tracker_admin=$1 WHERE id=$2`, manager.WebsiteAdmin, manager.Id) handleErr(err) rowsAffected, _ := res.RowsAffected() - logrus.WithError(err).WithFields(logrus.Fields{ + logrus.WithFields(logrus.Fields{ "rowsAffected": rowsAffected, "manager": manager, - }).Warning("Database.UpdateManager UPDATE") + }).Trace("Database.UpdateManager UPDATE") } func (database *Database) UpdateManagerPassword(manager *Manager, newPassword []byte) { @@ -119,10 +120,10 @@ func (database *Database) UpdateManagerPassword(manager *Manager, newPassword [] rowsAffected, _ := res.RowsAffected() - logrus.WithError(err).WithFields(logrus.Fields{ + logrus.WithFields(logrus.Fields{ "rowsAffected": rowsAffected, "id": manager.Id, - }).Warning("Database.UpdateManagerPassword UPDATE") + }).Trace("Database.UpdateManagerPassword UPDATE") } func (database *Database) ManagerHasRoleOn(manager *Manager, projectId int64) ManagerRole { @@ -140,3 +141,20 @@ func (database *Database) ManagerHasRoleOn(manager *Manager, projectId int64) Ma return role } + +func (database *Database) GetAllManagers() *[]Manager { + + db := database.getDB() + + rows, _ := db.Query(`SELECT id, register_time, tracker_admin, username FROM manager`) + + managers := make([]Manager, 0) + + for rows.Next() { + m := Manager{} + _ = rows.Scan(&m.Id, &m.RegisterTime, &m.WebsiteAdmin, &m.Username) + managers = append(managers, m) + } + + return &managers +} diff --git a/test/schema.sql b/test/schema.sql index af5a490..0ba5633 100755 --- a/test/schema.sql +++ b/test/schema.sql @@ -69,7 +69,7 @@ CREATE TABLE manager ( id SERIAL PRIMARY KEY, register_time INTEGER NOT NULL, - website_admin BOOLEAN NOT NULL, + tracker_admin BOOLEAN NOT NULL, username TEXT UNIQUE NOT NULL, password BYTEA NOT NULL ); @@ -116,7 +116,7 @@ CREATE OR REPLACE FUNCTION on_manager_insert() RETURNS TRIGGER AS $$ BEGIN IF NEW.id = 1 THEN - UPDATE manager SET website_admin= TRUE WHERE id = 1; + UPDATE manager SET tracker_admin= TRUE WHERE id = 1; end if; RETURN NEW; END; diff --git a/web/angular/src/app/account-details/account-details.component.html b/web/angular/src/app/account-details/account-details.component.html index f9e4878..d17fb77 100644 --- a/web/angular/src/app/account-details/account-details.component.html +++ b/web/angular/src/app/account-details/account-details.component.html @@ -13,6 +13,10 @@ {{"account.username" | translate}}: 
{{authService.account.username}}
+ + {{"account.register_time" | translate}}:  +
{{moment.unix(authService.account.register_time).utc().format("YYYY-MM-DD HH:mm:ss UTC")}}
+
diff --git a/web/angular/src/app/account-details/account-details.component.ts b/web/angular/src/app/account-details/account-details.component.ts index 8ace18e..02c8886 100644 --- a/web/angular/src/app/account-details/account-details.component.ts +++ b/web/angular/src/app/account-details/account-details.component.ts @@ -1,6 +1,8 @@ import {Component, OnInit} from '@angular/core'; import {AuthService} from "../auth.service"; +import * as moment from "moment" + @Component({ selector: 'app-account-details', templateUrl: './account-details.component.html', @@ -8,6 +10,8 @@ import {AuthService} from "../auth.service"; }) export class AccountDetailsComponent implements OnInit { + public moment = moment; + constructor(public authService: AuthService) { } diff --git a/web/angular/src/app/api.service.ts b/web/angular/src/app/api.service.ts index cc9429c..06c27db 100755 --- a/web/angular/src/app/api.service.ts +++ b/web/angular/src/app/api.service.ts @@ -69,4 +69,16 @@ export class ApiService { return this.http.get(this.url + `/project/requests/${project}`) } + getAllManagers() { + return this.http.get(this.url + "/manager/list") + } + + promote(managerId: number) { + return this.http.get(this.url + `/manager/promote/${managerId}`) + } + + demote(managerId: number) { + return this.http.get(this.url + `/manager/demote/${managerId}`) + } + } diff --git a/web/angular/src/app/app-routing.module.ts b/web/angular/src/app/app-routing.module.ts index cf5a1fe..65f4b76 100755 --- a/web/angular/src/app/app-routing.module.ts +++ b/web/angular/src/app/app-routing.module.ts @@ -12,6 +12,7 @@ import {LoginComponent} from "./login/login.component"; import {AccountDetailsComponent} from "./account-details/account-details.component"; import {WorkerDashboardComponent} from "./worker-dashboard/worker-dashboard.component"; import {ProjectPermsComponent} from "./project-perms/project-perms.component"; +import {ManagerListComponent} from "./manager-list/manager-list.component"; const routes: Routes = [ {path: "log", component: LogsComponent}, @@ -22,7 +23,8 @@ const routes: Routes = [ {path: "project/:id/update", component: UpdateProjectComponent}, {path: "project/:id/perms", component: ProjectPermsComponent}, {path: "new_project", component: CreateProjectComponent}, - {path: "workers", component: WorkerDashboardComponent} + {path: "workers", component: WorkerDashboardComponent}, + {path: "manager_list", component: ManagerListComponent} ]; @NgModule({ diff --git a/web/angular/src/app/app.component.html b/web/angular/src/app/app.component.html index c33d0e3..51230dc 100755 --- a/web/angular/src/app/app.component.html +++ b/web/angular/src/app/app.component.html @@ -11,6 +11,10 @@ +
+
diff --git a/web/angular/src/app/app.module.ts b/web/angular/src/app/app.module.ts index 5d6eb95..98cb5a0 100755 --- a/web/angular/src/app/app.module.ts +++ b/web/angular/src/app/app.module.ts @@ -48,6 +48,7 @@ import {LoginComponent} from './login/login.component'; import {AccountDetailsComponent} from './account-details/account-details.component'; import {WorkerDashboardComponent} from './worker-dashboard/worker-dashboard.component'; import {ProjectPermsComponent} from './project-perms/project-perms.component'; +import {ManagerListComponent} from './manager-list/manager-list.component'; export function createTranslateLoader(http: HttpClient) { @@ -68,6 +69,7 @@ export function createTranslateLoader(http: HttpClient) { AccountDetailsComponent, WorkerDashboardComponent, ProjectPermsComponent, + ManagerListComponent, ], imports: [ BrowserModule, diff --git a/web/angular/src/app/auth.service.ts b/web/angular/src/app/auth.service.ts index 5aec366..ac16069 100644 --- a/web/angular/src/app/auth.service.ts +++ b/web/angular/src/app/auth.service.ts @@ -60,6 +60,7 @@ export class AuthService { .subscribe(() => this.apiService.getAccountDetails() .subscribe((data: any) => { + this.logged = true; this.account = data.manager; this.router.navigateByUrl("/account"); }), 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 1757192..f9575ff 100644 --- a/web/angular/src/app/create-project/create-project.component.html +++ b/web/angular/src/app/create-project/create-project.component.html @@ -11,7 +11,7 @@ {{ "project.clone_url" | translate}} - @@ -23,7 +23,9 @@ - + {{"project.public" | translate}} diff --git a/web/angular/src/app/create-project/create-project.component.ts b/web/angular/src/app/create-project/create-project.component.ts index 457163e..ff32cbc 100644 --- a/web/angular/src/app/create-project/create-project.component.ts +++ b/web/angular/src/app/create-project/create-project.component.ts @@ -3,6 +3,7 @@ import {Project} from "../models/project"; import {ApiService} from "../api.service"; import {MessengerService} from "../messenger.service"; import {Router} from "@angular/router"; +import {AuthService} from "../auth.service"; @Component({ @@ -16,14 +17,21 @@ export class CreateProjectComponent implements OnInit { constructor(private apiService: ApiService, private messengerService: MessengerService, + public authService: AuthService, private router: Router) { - this.project.name = "test"; - this.project.public = true; } ngOnInit() { } + cloneUrlChange() { + let tokens = this.project.clone_url.split("/"); + + if (tokens.length > 2) { + this.project.git_repo = tokens[tokens.length - 2] + "/" + tokens[tokens.length - 1] + } + } + onSubmit() { this.apiService.createProject(this.project).subscribe( data => { diff --git a/web/angular/src/app/logs/logs.component.css b/web/angular/src/app/logs/logs.component.css index 31932f5..2a9d42e 100644 --- a/web/angular/src/app/logs/logs.component.css +++ b/web/angular/src/app/logs/logs.component.css @@ -1,5 +1,5 @@ .table-container { - height: 600px; + /*height: 600px;*/ } .mat-table { diff --git a/web/angular/src/app/logs/logs.component.html b/web/angular/src/app/logs/logs.component.html index 5e82737..d971fd1 100644 --- a/web/angular/src/app/logs/logs.component.html +++ b/web/angular/src/app/logs/logs.component.html @@ -1,56 +1,63 @@
-
- - - - - {{"logs.fatal" | translate}} - {{"logs.panic" | translate}} - {{"logs.error" | translate}} - {{"logs.warn" | translate}} - {{"logs.info" | translate}} - {{"logs.debug" | translate}} - {{"logs.trace" | translate}} - + + + {{"logs.title" | translate}} + {{"logs.subtitle" | translate}} + + + + + + + {{"logs.fatal" | translate}} + {{"logs.panic" | translate}} + {{"logs.error" | translate}} + {{"logs.warn" | translate}} + {{"logs.info" | translate}} + {{"logs.debug" | translate}} + {{"logs.trace" | translate}} + - -
+ +
- + - - {{"logs.level" | translate}} - {{("logs." + entry.level) | translate}} - - - {{"logs.time" | translate}} - {{entry.timestamp}} - - - {{"logs.message" | translate}} - {{entry.message}} - - - {{"logs.data" | translate}} - -
{{entry.data}}
-
-
+ + {{"logs.level" | translate}} + {{("logs." + entry.level) | translate}} + + + {{"logs.time" | translate}} + {{entry.timestamp}} + + + {{"logs.message" | translate}} + {{entry.message}} + + + {{"logs.data" | translate}} + +
{{entry.data}}
+
+
- - -
+ + +
- -
-
+ +
+ +
diff --git a/web/angular/src/app/logs/logs.component.ts b/web/angular/src/app/logs/logs.component.ts index a354bb8..70de835 100644 --- a/web/angular/src/app/logs/logs.component.ts +++ b/web/angular/src/app/logs/logs.component.ts @@ -39,7 +39,7 @@ export class LogsComponent implements OnInit { this.getLogs(Number(event.value)) } - private refresh() { + public refresh() { this.getLogs(this.filterLevel) } diff --git a/web/angular/src/app/manager-list/manager-list.component.css b/web/angular/src/app/manager-list/manager-list.component.css new file mode 100644 index 0000000..e69de29 diff --git a/web/angular/src/app/manager-list/manager-list.component.html b/web/angular/src/app/manager-list/manager-list.component.html new file mode 100644 index 0000000..3a5cad9 --- /dev/null +++ b/web/angular/src/app/manager-list/manager-list.component.html @@ -0,0 +1,53 @@ +
+ + + {{"manager_list.title" | translate}} + {{"manager_list.subtitle" | translate}} + + + + + + + + {{"manager_list.username" | translate}} + {{manager.username}} + + + {{"manager_list.role" | translate}} + + + supervisor_account + + + + + {{"manager_list.register_time" | translate}} + + {{moment.unix(manager.register_time).utc().format("UTC YYYY-MM-DD HH:mm:ss")}} + + + + {{"manager_list.actions" | 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 new file mode 100644 index 0000000..6f7c0ab --- /dev/null +++ b/web/angular/src/app/manager-list/manager-list.component.ts @@ -0,0 +1,70 @@ +import {Component, OnInit, ViewChild} from '@angular/core'; +import {ApiService} from "../api.service"; +import {MessengerService} from "../messenger.service"; +import {TranslateService} from "@ngx-translate/core"; +import {MatPaginator, MatSort, MatTableDataSource} from "@angular/material"; + +import * as moment from "moment" +import {AuthService} from "../auth.service"; + +@Component({ + selector: 'app-manager-list', + templateUrl: './manager-list.component.html', + styleUrls: ['./manager-list.component.css'] +}) +export class ManagerListComponent implements OnInit { + + managers = []; + data; + moment = moment; + cols = ['username', 'tracker_admin', 'register_time', 'actions']; + + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort) sort: MatSort; + + constructor(private apiService: ApiService, + private messengerService: MessengerService, + private translate: TranslateService, + private authService: AuthService + ) { + this.data = new MatTableDataSource() + } + + ngOnInit() { + this.getManagers(); + this.data.paginator = this.paginator; + this.data.sort = this.sort; + } + + canPromote(manager: Manager) { + return !manager.tracker_admin + } + + canDemote(manager: Manager) { + return manager.tracker_admin && manager.username != this.authService.account.username + } + + public promote(manager: Manager) { + this.apiService.promote(manager.id) + .subscribe(() => this.getManagers()) + } + + public demote(manager: Manager) { + this.apiService.demote(manager.id) + .subscribe(() => this.getManagers()) + } + + private getManagers() { + this.apiService.getAllManagers() + .subscribe(data => { + this.data.data = data["managers"] + }, + error => { + if (error && (error.status == 401 || error.status == 403)) { + console.log(error.error.message); + this.translate.get("manager_list.unauthorized") + .subscribe(t => this.messengerService.show(t)); + } + }) + } +} diff --git a/web/angular/src/app/models/manager.ts b/web/angular/src/app/models/manager.ts index f759998..617c786 100644 --- a/web/angular/src/app/models/manager.ts +++ b/web/angular/src/app/models/manager.ts @@ -1,5 +1,6 @@ interface Manager { id: number; username: string - website_admin: boolean; + tracker_admin: boolean + register_time: number } 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 d5b812d..ad6ace5 100755 --- a/web/angular/src/app/project-list/project-list.component.html +++ b/web/angular/src/app/project-list/project-list.component.html @@ -18,9 +18,11 @@
- -
diff --git a/web/angular/src/app/project-list/project-list.component.ts b/web/angular/src/app/project-list/project-list.component.ts index 99f3273..9213fa1 100755 --- a/web/angular/src/app/project-list/project-list.component.ts +++ b/web/angular/src/app/project-list/project-list.component.ts @@ -1,6 +1,7 @@ import {Component, OnInit} from '@angular/core'; import {ApiService} from "../api.service"; import {Project} from "../models/project"; +import {AuthService} from "../auth.service"; @Component({ selector: 'app-project-list', @@ -9,7 +10,8 @@ import {Project} from "../models/project"; }) export class ProjectListComponent implements OnInit { - constructor(private apiService: ApiService) { + constructor(private apiService: ApiService, + public authService: AuthService) { } projects: Project[]; 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 1ba73fb..8c169ea 100644 --- a/web/angular/src/app/project-perms/project-perms.component.css +++ b/web/angular/src/app/project-perms/project-perms.component.css @@ -1,10 +1,3 @@ -.unauthorized { - text-align: center; - color: #616161; - min-height: 3em; - margin-top: 2em !important; -} - .text-mono { font-family: Hack, Courier, "Courier New", monospace; color: #ff4081; 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 ea88f98..64543a0 100644 --- a/web/angular/src/app/project-perms/project-perms.component.html +++ b/web/angular/src/app/project-perms/project-perms.component.html @@ -16,7 +16,8 @@

{{w.alias}}

Id={{w.id}}, {{"perms.created" | translate}} - {{moment.unix(w.created).format("YYYY-MM-DD HH:mm:ss UTC")}} + {{moment.unix(w.created).utc().format("UTC YYYY-MM-DD HH:mm:ss")}}