Update tab access by user role (#549)
* update types * update user org type * update tabs
This commit is contained in:
parent
16ca8ecefd
commit
10c96ed2ae
@ -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",
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -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
|
||||
|
@ -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"),
|
||||
|
@ -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";
|
||||
|
116
frontend/src/types/crawler.ts
Normal file
116
frontend/src/types/crawler.ts
Normal 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
22
frontend/src/types/org.ts
Normal 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;
|
@ -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[];
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user