Update tab access by user role (#549)

* update types

* update user org type

* update tabs
This commit is contained in:
sua yoo 2023-02-02 22:26:22 -08:00 committed by GitHub
parent 16ca8ecefd
commit 10c96ed2ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 201 additions and 163 deletions

View File

@ -13,7 +13,14 @@ describe("browsertrix-app", () => {
name: "Test User", name: "Test User",
is_verified: false, is_verified: false,
is_superuser: false, is_superuser: false,
orgs: [{ id: "test_org_id", name: "test org" }], orgs: [
{
id: "test_org_id",
name: "test org",
role: 10,
email: "test@org.org",
},
],
}) })
); );
}); });

View File

@ -15,7 +15,7 @@ import APIRouter from "./utils/APIRouter";
import AuthService from "./utils/AuthService"; import AuthService from "./utils/AuthService";
import type { LoggedInEvent } from "./utils/AuthService"; import type { LoggedInEvent } from "./utils/AuthService";
import type { ViewState } from "./utils/APIRouter"; import type { ViewState } from "./utils/APIRouter";
import type { CurrentUser } from "./types/user"; import type { CurrentUser, UserOrg } from "./types/user";
import type { AuthStorageEventData } from "./utils/AuthService"; import type { AuthStorageEventData } from "./utils/AuthService";
import type { OrgData } from "./utils/orgs"; import type { OrgData } from "./utils/orgs";
import theme from "./theme"; import theme from "./theme";
@ -39,7 +39,7 @@ type APIUser = {
name: string; name: string;
is_verified: boolean; is_verified: boolean;
is_superuser: boolean; is_superuser: boolean;
orgs: OrgData[]; orgs: UserOrg[];
}; };
type UserSettings = { type UserSettings = {
@ -151,6 +151,7 @@ export class App extends LiteElement {
name: userInfo.name, name: userInfo.name,
isVerified: userInfo.is_verified, isVerified: userInfo.is_verified,
isAdmin: userInfo.is_superuser, isAdmin: userInfo.is_superuser,
orgs: userInfo.orgs,
}; };
const settings = this.getPersistedUserSettings(userInfo.id); const settings = this.getPersistedUserSettings(userInfo.id);
if (settings) { if (settings) {
@ -158,7 +159,7 @@ export class App extends LiteElement {
} }
const orgs = userInfo.orgs; const orgs = userInfo.orgs;
this.orgs = orgs; this.orgs = orgs;
if (orgs.length && !this.userInfo.isAdmin && !this.selectedOrgId) { if (orgs.length && !this.userInfo!.isAdmin && !this.selectedOrgId) {
const firstOrg = orgs[0].id; const firstOrg = orgs[0].id;
if (orgs.length === 1) { if (orgs.length === 1) {
// Persist selected org ID since there's no // Persist selected org ID since there's no

View File

@ -6,7 +6,7 @@ import type { ViewState } from "../../utils/APIRouter";
import type { AuthState } from "../../utils/AuthService"; import type { AuthState } from "../../utils/AuthService";
import type { CurrentUser } from "../../types/user"; import type { CurrentUser } from "../../types/user";
import type { OrgData } from "../../utils/orgs"; import type { OrgData } from "../../utils/orgs";
import { isOwner } from "../../utils/orgs"; import { isOwner, isCrawler } from "../../utils/orgs";
import LiteElement, { html } from "../../utils/LiteElement"; import LiteElement, { html } from "../../utils/LiteElement";
import { needLogin } from "../../utils/auth"; import { needLogin } from "../../utils/auth";
import "./crawl-configs-detail"; import "./crawl-configs-detail";
@ -62,6 +62,23 @@ export class Org extends LiteElement {
@state() @state()
private org?: OrgData | null; private org?: OrgData | null;
get userOrg() {
if (!this.userInfo) return null;
return this.userInfo.orgs.find(({ id }) => id === this.orgId)!;
}
get isOwner() {
const userOrg = this.userOrg;
if (userOrg) return isOwner(userOrg.role);
return false;
}
get isCrawler() {
const userOrg = this.userOrg;
if (userOrg) return isCrawler(userOrg.role);
return false;
}
async willUpdate(changedProperties: Map<string, any>) { async willUpdate(changedProperties: Map<string, any>) {
if (changedProperties.has("orgId") && this.orgId) { if (changedProperties.has("orgId") && this.orgId) {
try { try {
@ -95,8 +112,6 @@ export class Org extends LiteElement {
`; `;
} }
const memberInfo = (this.org.users ?? {})[this.userInfo.id];
const isOrgOwner = memberInfo && isOwner(memberInfo.role);
let tabPanelContent = "" as any; let tabPanelContent = "" as any;
switch (this.orgTab) { switch (this.orgTab) {
@ -110,7 +125,7 @@ export class Org extends LiteElement {
tabPanelContent = this.renderBrowserProfiles(); tabPanelContent = this.renderBrowserProfiles();
break; break;
case "settings": { case "settings": {
if (isOrgOwner) { if (this.isOwner) {
tabPanelContent = this.renderOrgSettings(); tabPanelContent = this.renderOrgSettings();
break; break;
} }
@ -123,7 +138,7 @@ export class Org extends LiteElement {
} }
return html` return html`
${this.renderOrgNavBar(isOrgOwner)} ${this.renderOrgNavBar()}
<main> <main>
<div <div
class="w-full max-w-screen-lg mx-auto px-3 box-border py-5" class="w-full max-w-screen-lg mx-auto px-3 box-border py-5"
@ -135,20 +150,24 @@ export class Org extends LiteElement {
`; `;
} }
private renderOrgNavBar(isOrgOwner: boolean) { private renderOrgNavBar() {
return html` return html`
<div class="w-full max-w-screen-lg mx-auto px-3 box-border"> <div class="w-full max-w-screen-lg mx-auto px-3 box-border">
<nav class="-ml-3 flex items-end overflow-x-auto"> <nav class="-ml-3 flex items-end overflow-x-auto">
${this.renderNavTab({ tabName: "crawls", label: msg("Crawls") })} ${this.renderNavTab({ tabName: "crawls", label: msg("Crawls") })}
${this.renderNavTab({ ${when(this.isCrawler, () =>
tabName: "crawl-configs", this.renderNavTab({
label: msg("Crawl Configs"), tabName: "crawl-configs",
})} label: msg("Crawl Configs"),
${this.renderNavTab({ })
tabName: "browser-profiles", )}
label: msg("Browser Profiles"), ${when(this.isCrawler, () =>
})} this.renderNavTab({
${when(isOrgOwner, () => tabName: "browser-profiles",
label: msg("Browser Profiles"),
})
)}
${when(this.isOwner, () =>
this.renderNavTab({ this.renderNavTab({
tabName: "settings", tabName: "settings",
label: msg("Org Settings"), label: msg("Org Settings"),

View File

@ -1,116 +1 @@
export type CrawlState = export * from "../../types/crawler";
| "starting"
| "running"
| "complete"
| "failed"
| "partial_complete"
| "timed_out"
| "stopping"
| "canceled";
export type Crawl = {
id: string;
userid: string;
userName: string;
oid: string;
cid: string;
configName: string;
schedule: string;
manual: boolean;
started: string; // UTC ISO date
finished?: string; // UTC ISO date
state: CrawlState;
scale: number;
stats: { done: string; found: string } | null;
resources?: { name: string; path: string; hash: string; size: number }[];
fileCount?: number;
fileSize?: number;
completions?: number;
tags?: string[];
};
type ScopeType =
| "prefix"
| "host"
| "domain"
| "page"
| "page-spa"
| "any"
| "custom";
export type Seed = {
url: string;
scopeType: ScopeType;
include?: string[];
exclude?: string[];
limit?: number | null;
extraHops?: number | null;
};
export type SeedConfig = Pick<
Seed,
"scopeType" | "include" | "exclude" | "limit" | "extraHops"
> & {
seeds: (string | Seed)[];
lang?: string | null;
blockAds?: boolean;
behaviorTimeout?: number | null;
behaviors?: string | null;
};
export type JobType = "url-list" | "seed-crawl" | "custom";
export type CrawlConfigParams = {
jobType: JobType;
name: string;
schedule: string;
scale: number;
profileid: string | null;
config: SeedConfig;
crawlTimeout?: number | null;
tags?: string[];
};
export type InitialCrawlConfig = Pick<
CrawlConfigParams,
"name" | "profileid" | "schedule" | "tags" | "crawlTimeout"
> & {
jobType?: JobType;
config: Pick<
CrawlConfigParams["config"],
"seeds" | "scopeType" | "exclude" | "behaviorTimeout"
> & {
extraHops?: CrawlConfigParams["config"]["extraHops"];
};
};
export type CrawlConfig = CrawlConfigParams & {
id: string;
oid: string;
jobType: JobType;
userid: string;
userName: string | null;
created: string;
crawlCount: number;
crawlAttemptCount: number;
lastCrawlId: string;
lastCrawlTime: string;
lastCrawlState: CrawlState;
currCrawlId: string;
newId: string | null;
oldId: string | null;
inactive: boolean;
profileName: string | null;
};
export type Profile = {
id: string;
name: string;
description: string;
created: string;
origins: string[];
profileId: string;
baseProfileName: string;
oid: string;
crawlconfigs: { id: string; name: string }[];
};

View File

@ -0,0 +1,116 @@
export type CrawlState =
| "starting"
| "running"
| "complete"
| "failed"
| "partial_complete"
| "timed_out"
| "stopping"
| "canceled";
export type Crawl = {
id: string;
userid: string;
userName: string;
oid: string;
cid: string;
configName: string;
schedule: string;
manual: boolean;
started: string; // UTC ISO date
finished?: string; // UTC ISO date
state: CrawlState;
scale: number;
stats: { done: string; found: string } | null;
resources?: { name: string; path: string; hash: string; size: number }[];
fileCount?: number;
fileSize?: number;
completions?: number;
tags?: string[];
};
type ScopeType =
| "prefix"
| "host"
| "domain"
| "page"
| "page-spa"
| "any"
| "custom";
export type Seed = {
url: string;
scopeType: ScopeType;
include?: string[];
exclude?: string[];
limit?: number | null;
extraHops?: number | null;
};
export type SeedConfig = Pick<
Seed,
"scopeType" | "include" | "exclude" | "limit" | "extraHops"
> & {
seeds: (string | Seed)[];
lang?: string | null;
blockAds?: boolean;
behaviorTimeout?: number | null;
behaviors?: string | null;
};
export type JobType = "url-list" | "seed-crawl" | "custom";
export type CrawlConfigParams = {
jobType: JobType;
name: string;
schedule: string;
scale: number;
profileid: string | null;
config: SeedConfig;
crawlTimeout?: number | null;
tags?: string[];
};
export type InitialCrawlConfig = Pick<
CrawlConfigParams,
"name" | "profileid" | "schedule" | "tags" | "crawlTimeout"
> & {
jobType?: JobType;
config: Pick<
CrawlConfigParams["config"],
"seeds" | "scopeType" | "exclude" | "behaviorTimeout"
> & {
extraHops?: CrawlConfigParams["config"]["extraHops"];
};
};
export type CrawlConfig = CrawlConfigParams & {
id: string;
oid: string;
jobType: JobType;
userid: string;
userName: string | null;
created: string;
crawlCount: number;
crawlAttemptCount: number;
lastCrawlId: string;
lastCrawlTime: string;
lastCrawlState: CrawlState;
currCrawlId: string;
newId: string | null;
oldId: string | null;
inactive: boolean;
profileName: string | null;
};
export type Profile = {
id: string;
name: string;
description: string;
created: string;
origins: string[];
profileId: string;
baseProfileName: string;
oid: string;
crawlconfigs: { id: string; name: string }[];
};

22
frontend/src/types/org.ts Normal file
View File

@ -0,0 +1,22 @@
// From UserRole in backend
export type UserRole = "viewer" | "crawler" | "owner";
export const AccessCode: Record<UserRole, number> = {
viewer: 10,
crawler: 20,
owner: 40,
} as const;
export type OrgData = {
id: string;
name: string;
users?: {
[id: string]: {
role: typeof AccessCode[UserRole];
name: string;
email: string;
};
};
};
export type OrgConfig = any;

View File

@ -1,7 +1,15 @@
import type { AccessCode, UserRole, OrgData } from "./org";
export type UserOrg = OrgData & {
default?: boolean;
role: typeof AccessCode[UserRole];
};
export type CurrentUser = { export type CurrentUser = {
id: string; id: string;
email: string; email: string;
name: string; name: string;
isVerified: boolean; isVerified: boolean;
isAdmin: boolean; isAdmin: boolean;
orgs: UserOrg[];
}; };

View File

@ -1,34 +1,14 @@
// From UserRole in backend import { AccessCode, UserRole } from "../types/org";
type UserRole = "viewer" | "crawler" | "owner"; export * from "../types/org";
export const AccessCode: Record<UserRole, number> = {
viewer: 10,
crawler: 20,
owner: 40,
} as const;
export type OrgData = {
id: string;
name: string;
users?: {
[id: string]: {
role: typeof AccessCode[UserRole];
name: "string";
};
};
};
export type Org = {
oid: string;
name?: string;
id?: string;
users?: { [id: string]: OrgData };
};
export type OrgConfig = any;
export function isOwner(accessCode?: typeof AccessCode[UserRole]): boolean { export function isOwner(accessCode?: typeof AccessCode[UserRole]): boolean {
if (!accessCode) return false; if (!accessCode) return false;
return accessCode === AccessCode.owner; return accessCode === AccessCode.owner;
} }
export function isCrawler(accessCode?: typeof AccessCode[UserRole]): boolean {
if (!accessCode) return false;
return accessCode >= AccessCode.crawler;
}