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",
|
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",
|
||||||
|
},
|
||||||
|
],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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
|
||||||
|
@ -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"),
|
||||||
|
@ -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 }[];
|
|
||||||
};
|
|
||||||
|
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 = {
|
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[];
|
||||||
};
|
};
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user