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",
is_verified: 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 type { LoggedInEvent } from "./utils/AuthService";
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 { OrgData } from "./utils/orgs";
import theme from "./theme";
@ -39,7 +39,7 @@ type APIUser = {
name: string;
is_verified: boolean;
is_superuser: boolean;
orgs: OrgData[];
orgs: UserOrg[];
};
type UserSettings = {
@ -151,6 +151,7 @@ export class App extends LiteElement {
name: userInfo.name,
isVerified: userInfo.is_verified,
isAdmin: userInfo.is_superuser,
orgs: userInfo.orgs,
};
const settings = this.getPersistedUserSettings(userInfo.id);
if (settings) {
@ -158,7 +159,7 @@ export class App extends LiteElement {
}
const orgs = userInfo.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;
if (orgs.length === 1) {
// 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 { CurrentUser } from "../../types/user";
import type { OrgData } from "../../utils/orgs";
import { isOwner } from "../../utils/orgs";
import { isOwner, isCrawler } from "../../utils/orgs";
import LiteElement, { html } from "../../utils/LiteElement";
import { needLogin } from "../../utils/auth";
import "./crawl-configs-detail";
@ -62,6 +62,23 @@ export class Org extends LiteElement {
@state()
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>) {
if (changedProperties.has("orgId") && this.orgId) {
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;
switch (this.orgTab) {
@ -110,7 +125,7 @@ export class Org extends LiteElement {
tabPanelContent = this.renderBrowserProfiles();
break;
case "settings": {
if (isOrgOwner) {
if (this.isOwner) {
tabPanelContent = this.renderOrgSettings();
break;
}
@ -123,7 +138,7 @@ export class Org extends LiteElement {
}
return html`
${this.renderOrgNavBar(isOrgOwner)}
${this.renderOrgNavBar()}
<main>
<div
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`
<div class="w-full max-w-screen-lg mx-auto px-3 box-border">
<nav class="-ml-3 flex items-end overflow-x-auto">
${this.renderNavTab({ tabName: "crawls", label: msg("Crawls") })}
${this.renderNavTab({
tabName: "crawl-configs",
label: msg("Crawl Configs"),
})}
${this.renderNavTab({
tabName: "browser-profiles",
label: msg("Browser Profiles"),
})}
${when(isOrgOwner, () =>
${when(this.isCrawler, () =>
this.renderNavTab({
tabName: "crawl-configs",
label: msg("Crawl Configs"),
})
)}
${when(this.isCrawler, () =>
this.renderNavTab({
tabName: "browser-profiles",
label: msg("Browser Profiles"),
})
)}
${when(this.isOwner, () =>
this.renderNavTab({
tabName: "settings",
label: msg("Org Settings"),

View File

@ -1,116 +1 @@
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 }[];
};
export * from "../../types/crawler";

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 = {
id: string;
email: string;
name: string;
isVerified: boolean;
isAdmin: boolean;
orgs: UserOrg[];
};

View File

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