From 0aa91fa99fbb43f1b73915a428f938a75fff17d7 Mon Sep 17 00:00:00 2001 From: Kevin Kandlbinder Date: Fri, 25 Mar 2022 21:18:45 +0100 Subject: [PATCH] webui: Add API authentication --- graph/generated/generated.go | 126 +++----- graph/schema.graphqls | 14 +- internal/web/api/api.go | 8 +- webui/.babelrc | 5 + webui/data/schema.graphql | 282 ++++++++++++++++++ webui/package.json | 15 +- webui/public/locales/de/translation.json | 4 +- webui/public/locales/en/translation.json | 4 +- webui/relay.config.js | 5 + webui/src/App.tsx | 37 ++- webui/src/RelayEnvironment.ts | 22 ++ .../src/__generated__/AppMainQuery.graphql.ts | 90 ++++++ .../AppQueryComponent_lists.graphql.ts | 97 ++++++ .../__generated__/srcListsQuery.graphql.ts | 217 ++++++++++++++ .../__generated__/srcUserDataQuery.graphql.ts | 98 ++++++ webui/src/app/fetchGraphQL.ts | 29 ++ webui/src/components/auth/LoginView.tsx | 17 +- webui/src/components/auth/RegisterView.tsx | 57 +++- webui/src/components/dashboard/Dashboard.tsx | 43 +++ .../__generated__/DashboardQuery.graphql.ts | 90 ++++++ .../DashboardRefreshQuery.graphql.ts | 248 +++++++++++++++ .../Dashboard_fragment.graphql.ts | 240 +++++++++++++++ webui/src/index.scss | 2 + webui/src/index.tsx | 17 +- webui/src/layouts/AuthLayout.module.scss | 2 +- webui/src/layouts/AuthLayout.tsx | 2 +- webui/src/layouts/PanelLayout.module.scss | 11 +- webui/src/layouts/PanelLayout.tsx | 11 +- webui/src/mutations/LoginMutation.ts | 40 +++ webui/src/mutations/RegisterMutation.ts | 41 +++ .../__generated__/LoginMutation.graphql.ts | 80 +++++ .../__generated__/RegisterMutation.graphql.ts | 81 +++++ webui/src/react-app-env.d.ts | 3 + webui/yarn.lock | 185 +++++++++++- 34 files changed, 2088 insertions(+), 135 deletions(-) create mode 100644 webui/.babelrc create mode 100644 webui/data/schema.graphql create mode 100644 webui/relay.config.js create mode 100644 webui/src/RelayEnvironment.ts create mode 100644 webui/src/__generated__/AppMainQuery.graphql.ts create mode 100644 webui/src/__generated__/AppQueryComponent_lists.graphql.ts create mode 100644 webui/src/__generated__/srcListsQuery.graphql.ts create mode 100644 webui/src/__generated__/srcUserDataQuery.graphql.ts create mode 100644 webui/src/app/fetchGraphQL.ts create mode 100644 webui/src/components/dashboard/Dashboard.tsx create mode 100644 webui/src/components/dashboard/__generated__/DashboardQuery.graphql.ts create mode 100644 webui/src/components/dashboard/__generated__/DashboardRefreshQuery.graphql.ts create mode 100644 webui/src/components/dashboard/__generated__/Dashboard_fragment.graphql.ts create mode 100644 webui/src/mutations/LoginMutation.ts create mode 100644 webui/src/mutations/RegisterMutation.ts create mode 100644 webui/src/mutations/__generated__/LoginMutation.graphql.ts create mode 100644 webui/src/mutations/__generated__/RegisterMutation.graphql.ts diff --git a/graph/generated/generated.go b/graph/generated/generated.go index 4422b01..8d66aa1 100644 --- a/graph/generated/generated.go +++ b/graph/generated/generated.go @@ -1006,15 +1006,15 @@ input EntrySort { } type Query { - users(first: Int, after: String, filter: UserFilter, sort: UserSort): UserConnection! @loggedIn - lists(first: Int, after: String, filter: ListFilter, sort: ListSort): ListConnection! @loggedIn - entries(first: Int, after: String, filter: EntryFilter, sort: EntrySort): EntryConnection! @loggedIn + users(first: Int, after: String, filter: UserFilter, sort: UserSort): UserConnection @loggedIn + lists(first: Int, after: String, filter: ListFilter, sort: ListSort): ListConnection @loggedIn + entries(first: Int, after: String, filter: EntryFilter, sort: EntrySort): EntryConnection @loggedIn - user(id: ID, username: String): User! @loggedIn - entry(id: ID, hashValue: String): Entry! @loggedIn - list(id: ID, name: String): List! @loggedIn + user(id: ID, username: String): User @loggedIn + entry(id: ID, hashValue: String): Entry @loggedIn + list(id: ID, name: String): List @loggedIn - self: User! @loggedIn + self: User @loggedIn } input Login { @@ -3542,14 +3542,11 @@ func (ec *executionContext) _Query_users(ctx context.Context, field graphql.Coll return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } res := resTmp.(*model.UserConnection) fc.Result = res - return ec.marshalNUserConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUserConnection(ctx, field.Selections, res) + return ec.marshalOUserConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUserConnection(ctx, field.Selections, res) } func (ec *executionContext) _Query_lists(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -3604,14 +3601,11 @@ func (ec *executionContext) _Query_lists(ctx context.Context, field graphql.Coll return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } res := resTmp.(*model.ListConnection) fc.Result = res - return ec.marshalNListConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx, field.Selections, res) + return ec.marshalOListConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx, field.Selections, res) } func (ec *executionContext) _Query_entries(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -3666,14 +3660,11 @@ func (ec *executionContext) _Query_entries(ctx context.Context, field graphql.Co return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } res := resTmp.(*model.EntryConnection) fc.Result = res - return ec.marshalNEntryConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryConnection(ctx, field.Selections, res) + return ec.marshalOEntryConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryConnection(ctx, field.Selections, res) } func (ec *executionContext) _Query_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -3728,14 +3719,11 @@ func (ec *executionContext) _Query_user(ctx context.Context, field graphql.Colle return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } res := resTmp.(*model.User) fc.Result = res - return ec.marshalNUser2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) + return ec.marshalOUser2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) } func (ec *executionContext) _Query_entry(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -3790,14 +3778,11 @@ func (ec *executionContext) _Query_entry(ctx context.Context, field graphql.Coll return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } res := resTmp.(*model.Entry) fc.Result = res - return ec.marshalNEntry2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntry(ctx, field.Selections, res) + return ec.marshalOEntry2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntry(ctx, field.Selections, res) } func (ec *executionContext) _Query_list(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -3852,14 +3837,11 @@ func (ec *executionContext) _Query_list(ctx context.Context, field graphql.Colle return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } res := resTmp.(*model.List) fc.Result = res - return ec.marshalNList2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐList(ctx, field.Selections, res) + return ec.marshalOList2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐList(ctx, field.Selections, res) } func (ec *executionContext) _Query_self(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -3907,14 +3889,11 @@ func (ec *executionContext) _Query_self(ctx context.Context, field graphql.Colle return graphql.Null } if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } return graphql.Null } res := resTmp.(*model.User) fc.Result = res - return ec.marshalNUser2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) + return ec.marshalOUser2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUser(ctx, field.Selections, res) } func (ec *executionContext) _Query___type(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { @@ -6919,9 +6898,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_users(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } return res }) case "lists": @@ -6933,9 +6909,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_lists(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } return res }) case "entries": @@ -6947,9 +6920,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_entries(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } return res }) case "user": @@ -6961,9 +6931,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_user(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } return res }) case "entry": @@ -6975,9 +6942,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_entry(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } return res }) case "list": @@ -6989,9 +6953,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_list(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } return res }) case "self": @@ -7003,9 +6964,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } }() res = ec._Query_self(ctx, field) - if res == graphql.Null { - atomic.AddUint32(&invalids, 1) - } return res }) case "__type": @@ -7498,20 +7456,6 @@ func (ec *executionContext) marshalNEntry2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrix return ec._Entry(ctx, sel, v) } -func (ec *executionContext) marshalNEntryConnection2githubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryConnection(ctx context.Context, sel ast.SelectionSet, v model.EntryConnection) graphql.Marshaler { - return ec._EntryConnection(ctx, sel, &v) -} - -func (ec *executionContext) marshalNEntryConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryConnection(ctx context.Context, sel ast.SelectionSet, v *model.EntryConnection) graphql.Marshaler { - if v == nil { - if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - return ec._EntryConnection(ctx, sel, v) -} - func (ec *executionContext) marshalNEntryEdge2ᚕᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.EntryEdge) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup @@ -7631,20 +7575,6 @@ func (ec *executionContext) marshalNList2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrix return ec._List(ctx, sel, v) } -func (ec *executionContext) marshalNListConnection2githubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx context.Context, sel ast.SelectionSet, v model.ListConnection) graphql.Marshaler { - return ec._ListConnection(ctx, sel, &v) -} - -func (ec *executionContext) marshalNListConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx context.Context, sel ast.SelectionSet, v *model.ListConnection) graphql.Marshaler { - if v == nil { - if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - return ec._ListConnection(ctx, sel, v) -} - func (ec *executionContext) marshalNListEdge2ᚕᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.ListEdge) graphql.Marshaler { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup @@ -8170,6 +8100,13 @@ func (ec *executionContext) marshalOCommentConnection2ᚖgithubᚗcomᚋUnkn0wnC return ec._CommentConnection(ctx, sel, v) } +func (ec *executionContext) marshalOEntry2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntry(ctx context.Context, sel ast.SelectionSet, v *model.Entry) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._Entry(ctx, sel, v) +} + func (ec *executionContext) marshalOEntryConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐEntryConnection(ctx context.Context, sel ast.SelectionSet, v *model.EntryConnection) graphql.Marshaler { if v == nil { return graphql.Null @@ -8333,6 +8270,13 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele return graphql.MarshalInt(*v) } +func (ec *executionContext) marshalOList2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐList(ctx context.Context, sel ast.SelectionSet, v *model.List) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._List(ctx, sel, v) +} + func (ec *executionContext) marshalOListConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx context.Context, sel ast.SelectionSet, v *model.ListConnection) graphql.Marshaler { if v == nil { return graphql.Null @@ -8562,6 +8506,20 @@ func (ec *executionContext) unmarshalOTimestampFilter2ᚖgithubᚗcomᚋUnkn0wnC return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) marshalOUser2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUser(ctx context.Context, sel ast.SelectionSet, v *model.User) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._User(ctx, sel, v) +} + +func (ec *executionContext) marshalOUserConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUserConnection(ctx context.Context, sel ast.SelectionSet, v *model.UserConnection) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return ec._UserConnection(ctx, sel, v) +} + func (ec *executionContext) unmarshalOUserFilter2ᚕᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐUserFilter(ctx context.Context, v interface{}) ([]*model.UserFilter, error) { if v == nil { return nil, nil diff --git a/graph/schema.graphqls b/graph/schema.graphqls index 7b1b588..0e08e77 100644 --- a/graph/schema.graphqls +++ b/graph/schema.graphqls @@ -199,15 +199,15 @@ input EntrySort { } type Query { - users(first: Int, after: String, filter: UserFilter, sort: UserSort): UserConnection! @loggedIn - lists(first: Int, after: String, filter: ListFilter, sort: ListSort): ListConnection! @loggedIn - entries(first: Int, after: String, filter: EntryFilter, sort: EntrySort): EntryConnection! @loggedIn + users(first: Int, after: String, filter: UserFilter, sort: UserSort): UserConnection @loggedIn + lists(first: Int, after: String, filter: ListFilter, sort: ListSort): ListConnection @loggedIn + entries(first: Int, after: String, filter: EntryFilter, sort: EntrySort): EntryConnection @loggedIn - user(id: ID, username: String): User! @loggedIn - entry(id: ID, hashValue: String): Entry! @loggedIn - list(id: ID, name: String): List! @loggedIn + user(id: ID, username: String): User @loggedIn + entry(id: ID, hashValue: String): Entry @loggedIn + list(id: ID, name: String): List @loggedIn - self: User! @loggedIn + self: User @loggedIn } input Login { diff --git a/internal/web/api/api.go b/internal/web/api/api.go index a880d8e..38e96b4 100644 --- a/internal/web/api/api.go +++ b/internal/web/api/api.go @@ -50,7 +50,7 @@ func SetupAPI() chi.Router { } } - return nil, errors.New("authorization required") + return nil, nil } c.Directives.HasRole = func(ctx context.Context, obj interface{}, next graphql.Resolver, role model2.UserRole) (res interface{}, err error) { @@ -59,7 +59,7 @@ func SetupAPI() chi.Router { if role == model2.UserRoleUnauthenticated { return next(ctx) } - return nil, errors.New("authorization required") + return nil, nil } switch role { @@ -75,13 +75,13 @@ func SetupAPI() chi.Router { return nil, errors.New("server error") } - return nil, errors.New("unauthorized") + return nil, nil } c.Directives.Owner = func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) { user, err := graph.GetUserFromContext(ctx) if err != nil { - return nil, errors.New("authorization required") + return nil, nil } ctx2 := context.WithValue(ctx, "ownerConstraint", user.ID.Hex()) diff --git a/webui/.babelrc b/webui/.babelrc new file mode 100644 index 0000000..15adeb0 --- /dev/null +++ b/webui/.babelrc @@ -0,0 +1,5 @@ +{ + "plugins": [ + "relay" + ] +} \ No newline at end of file diff --git a/webui/data/schema.graphql b/webui/data/schema.graphql new file mode 100644 index 0000000..0e08e77 --- /dev/null +++ b/webui/data/schema.graphql @@ -0,0 +1,282 @@ +# GraphQL schema example +# +# https://gqlgen.com/getting-started/ + +scalar Time + +directive @loggedIn on FIELD_DEFINITION +directive @hasRole(role: UserRole!) on FIELD_DEFINITION +directive @owner on FIELD_DEFINITION +directive @maintainer on FIELD_DEFINITION + +enum UserRole { + ADMIN + USER + UNAUTHENTICATED +} + +enum SortDirection { + ASC + DESC +} + +type PageInfo { + hasPreviousPage: Boolean! + hasNextPage: Boolean! + startCursor: String! + endCursor: String! +} + +input SortRule { + direction: SortDirection! +} + +type User { + id: ID! + + username: String! + + admin: Boolean + + matrixLinks: [String!] + pendingMatrixLinks: [String!] +} + +type UserConnection { + pageInfo: PageInfo! + edges: [UserEdge!]! +} + +type UserEdge { + node: User! + cursor: String! +} + +type Entry { + id: ID! + tags: [String!] + partOf(first: Int, after: String): ListConnection + hashValue: String! + timestamp: Time! + addedBy: User! + comments(first: Int, after: String): CommentConnection +} + +type EntryConnection { + pageInfo: PageInfo! + edges: [EntryEdge!]! +} + +type EntryEdge { + node: Entry! + cursor: String! +} + +type List { + id: ID! + name: String! + tags: [String!] + creator: User! + comments(first: Int, after: String): CommentConnection + maintainers(first: Int, after: String): UserConnection! + entries(first: Int, after: String): EntryConnection +} + +type ListConnection { + pageInfo: PageInfo! + edges: [ListEdge!]! +} + +type ListEdge { + node: List! + cursor: String! +} + +type Comment { + timestamp: Time! + author: User! + content: String! +} + +type CommentConnection { + pageInfo: PageInfo! + edges: [CommentEdge!]! +} + +type CommentEdge { + node: Comment! + cursor: String! +} + +input IntFilter { + gt: Int + lt: Int + eq: Int + neq: Int +} + +input TimestampFilter { + after: Time + before: Time +} + +input StringFilter { + eq: String # Equal + neq: String # Not Equal + regex: String # Regex Check +} + +input StringArrayFilter { + containsAll: [String] + elemMatch: StringFilter + length: Int +} + +input UserFilter { + id: ID + username: StringFilter + matrixLinks: StringArrayFilter + pendingMatrixLinks: StringArrayFilter + admin: Boolean +} + +input UserArrayFilter { + containsAll: [UserFilter] + containsOne: [UserFilter] + length: Int +} + +input UserSort { + id: SortRule + username: SortRule + admin: SortRule +} + +input ListFilter { + id: ID + name: StringFilter + tags: StringArrayFilter + maintainers: IDArrayFilter + # entries: EntryArrayFilter +} + +input IDArrayFilter { + containsAll: [ID] + length: Int +} + +input ListArrayFilter { + containsAll: [ListFilter] + containsOne: [ListFilter] + length: Int +} + +input ListSort { + id: SortRule + name: SortRule +} + +input EntryFilter { + id: ID + hashValue: StringFilter + tags: StringArrayFilter + addedBy: ID + timestamp: TimestampFilter + partOf: IDArrayFilter +} + +input EntryArrayFilter { + containsAll: [EntryFilter] + containsOne: [EntryFilter] + length: Int +} + +input EntrySort { + id: SortRule + hashValue: SortRule + timestamp: SortRule + addedBy: SortRule +} + +type Query { + users(first: Int, after: String, filter: UserFilter, sort: UserSort): UserConnection @loggedIn + lists(first: Int, after: String, filter: ListFilter, sort: ListSort): ListConnection @loggedIn + entries(first: Int, after: String, filter: EntryFilter, sort: EntrySort): EntryConnection @loggedIn + + user(id: ID, username: String): User @loggedIn + entry(id: ID, hashValue: String): Entry @loggedIn + list(id: ID, name: String): List @loggedIn + + self: User @loggedIn +} + +input Login { + username: String! + password: String! +} + +input Register { + username: String! + password: String! + mxID: String! +} + +input CreateEntry { + tags: [String!] + partOf: [ID!] + hashValue: String! + comment: String +} + +input CommentEntry { + entry: ID! + comment: String! +} + +input CreateList { + name: String! + tags: [String!] + comment: String + maintainers: [ID!] + entries: [ID!] +} + +input CommentList { + list: ID! + comment: String! +} + +input AddToLists { + lists: [ID!]! + entry: ID! +} + +input RemoveFromLists { + lists: [ID!]! + entry: ID! +} + +input AddMXID { + mxid: String! +} + +input RemoveMXID { + mxid: String! +} + +type Mutation { + login(input: Login!): String! + register(input: Register!): String! @hasRole(role: UNAUTHENTICATED) + addMXID(input: AddMXID!): User! @loggedIn + removeMXID(input: RemoveMXID!): User! @loggedIn + + createEntry(input: CreateEntry!): Entry! @loggedIn + commentEntry(input: CommentEntry!): Entry! @loggedIn + addToLists(input: AddToLists!): Entry! @loggedIn + removeFromLists(input: RemoveFromLists!): Entry! @loggedIn + + createList(input: CreateList!): List! @loggedIn + commentList(input: CommentList!): List! @loggedIn + deleteList(input: ID!): Boolean! @loggedIn @owner + +} diff --git a/webui/package.json b/webui/package.json index 90b35f5..dff5dd1 100644 --- a/webui/package.json +++ b/webui/package.json @@ -14,6 +14,8 @@ "@types/react-dom": "^16.9.14", "@types/react-helmet": "^6.1.5", "@types/react-redux": "^7.1.22", + "@types/react-relay": "^13.0.1", + "@types/relay-runtime": "^13.0.2", "axios": "^0.26.0", "i18next": "^21.6.13", "i18next-browser-languagedetector": "^6.1.3", @@ -25,17 +27,19 @@ "react-helmet": "^6.1.0", "react-i18next": "^11.15.5", "react-redux": "^7.2.6", + "react-relay": "^13.2.0", "react-router-dom": "6", "react-scripts": "5.0.0", "sass": "^1.49.9", "typescript": "~4.1.5" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", + "start": "yarn run relay && react-scripts start", + "build": "yarn run relay && react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "extract-translations": "i18next --fail-on-warnings" + "extract-translations": "i18next --fail-on-warnings", + "relay": "yarn run relay-compiler $@" }, "eslintConfig": { "extends": "react-app" @@ -51,5 +55,10 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "babel-plugin-relay": "^13.2.0", + "graphql": "^16.3.0", + "relay-compiler": "^13.2.0" } } diff --git a/webui/public/locales/de/translation.json b/webui/public/locales/de/translation.json index 83137f4..b3d3893 100644 --- a/webui/public/locales/de/translation.json +++ b/webui/public/locales/de/translation.json @@ -1,3 +1,5 @@ { - "test": "Tst2" + "dashboard": { + "helloText": "Ayo {{name}}!" + } } diff --git a/webui/public/locales/en/translation.json b/webui/public/locales/en/translation.json index 7fb631f..b3d3893 100644 --- a/webui/public/locales/en/translation.json +++ b/webui/public/locales/en/translation.json @@ -1,3 +1,5 @@ { - "test": "Test" + "dashboard": { + "helloText": "Ayo {{name}}!" + } } diff --git a/webui/relay.config.js b/webui/relay.config.js new file mode 100644 index 0000000..031635d --- /dev/null +++ b/webui/relay.config.js @@ -0,0 +1,5 @@ +module.exports = { + src: "./src", + schema: "./data/schema.graphql", + language: "typescript" +} \ No newline at end of file diff --git a/webui/src/App.tsx b/webui/src/App.tsx index ac1eb76..964c31f 100644 --- a/webui/src/App.tsx +++ b/webui/src/App.tsx @@ -1,17 +1,29 @@ -import React from 'react'; +import React, {useEffect} from 'react'; import { Routes, Route } from "react-router-dom"; import AuthLayout from "./layouts/AuthLayout"; import LoginView from "./components/auth/LoginView"; import RegisterView from "./components/auth/RegisterView"; import RequireAuth from "./features/auth/RequireAuth"; -import {useAppDispatch} from "./app/hooks"; +import {useAppDispatch, useAppSelector} from "./app/hooks"; import broadcastChannel from "./app/broadcastChannel"; -import {logOut, receiveAuthUpdate} from "./features/auth/authSlice"; +import {logOut, receiveAuthUpdate, selectAuth} from "./features/auth/authSlice"; import PanelLayout from "./layouts/PanelLayout"; -import {Trans, useTranslation} from "react-i18next"; +import {useTranslation} from "react-i18next"; +import { + useQueryLoader, useRelayEnvironment, +} from 'react-relay/hooks'; +import Dashboard from "./components/dashboard/Dashboard"; +import DashboardQueryGraphql, {DashboardQuery} from "./components/dashboard/__generated__/DashboardQuery.graphql"; function App() { const dispatch = useAppDispatch() + const auth = useAppSelector(selectAuth) + const environment = useRelayEnvironment(); + + + const [dashboardInitialState, loadQuery, disposeQuery] = useQueryLoader( + DashboardQueryGraphql + ) // This needs to be here to prevent a weird bug useTranslation() @@ -22,6 +34,17 @@ function App() { } }) + useEffect(() => { + if(auth.jwt !== null) { + loadQuery({}) + return + } + + disposeQuery() + environment.getStore().notify(undefined, true) + }, [auth]) + + return ( }> @@ -29,10 +52,12 @@ function App() { } /> }> -

Test

} /> + }>Log out

{ + JSON.stringify(data.self) + }

*/}{dashboardInitialState && }} />

rooms

} />

lists

} />

entries

} /> diff --git a/webui/src/RelayEnvironment.ts b/webui/src/RelayEnvironment.ts new file mode 100644 index 0000000..c9ed40f --- /dev/null +++ b/webui/src/RelayEnvironment.ts @@ -0,0 +1,22 @@ +import {Environment, Network, RecordSource, Store} from 'relay-runtime'; +import fetchGraphQL from "./app/fetchGraphQL"; +import {RequestParameters} from "relay-runtime/lib/util/RelayConcreteNode"; +import {Variables} from "relay-runtime/lib/util/RelayRuntimeTypes"; +import {AnyAction, EnhancedStore} from "@reduxjs/toolkit"; +import {ThunkMiddlewareFor} from "@reduxjs/toolkit/dist/getDefaultMiddleware"; + +const fetchRelay = (auth: GQLAuthObj) => { + return async (params: RequestParameters, variables: Variables) => { + return fetchGraphQL(auth, params.text, variables) + } +} + +export type GQLAuthObj = { + store?: EnhancedStore]> + auth?: string +} + +export default (auth: GQLAuthObj) => new Environment({ + network: Network.create(fetchRelay(auth)), + store: new Store(new RecordSource()), +}); \ No newline at end of file diff --git a/webui/src/__generated__/AppMainQuery.graphql.ts b/webui/src/__generated__/AppMainQuery.graphql.ts new file mode 100644 index 0000000..421c310 --- /dev/null +++ b/webui/src/__generated__/AppMainQuery.graphql.ts @@ -0,0 +1,90 @@ +/** + * @generated SignedSource<<92d3c9bc5ecbad19fc01cc3038c80043>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime'; +export type AppMainQuery$variables = {}; +export type AppMainQuery$data = { + readonly self: { + readonly admin: boolean | null; + readonly id: string; + readonly username: string; + } | null; +}; +export type AppMainQuery = { + variables: AppMainQuery$variables; + response: AppMainQuery$data; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "self", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "admin", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "username", + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "AppMainQuery", + "selections": (v0/*: any*/), + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "AppMainQuery", + "selections": (v0/*: any*/) + }, + "params": { + "cacheID": "b0e68d6eaa2fc3081f412c76778294fe", + "id": null, + "metadata": {}, + "name": "AppMainQuery", + "operationKind": "query", + "text": "query AppMainQuery {\n self {\n admin\n id\n username\n }\n}\n" + } +}; +})(); + +(node as any).hash = "4b6ec346f1ba3fa1a215bfd2f2b5bc9e"; + +export default node; diff --git a/webui/src/__generated__/AppQueryComponent_lists.graphql.ts b/webui/src/__generated__/AppQueryComponent_lists.graphql.ts new file mode 100644 index 0000000..85cf4f4 --- /dev/null +++ b/webui/src/__generated__/AppQueryComponent_lists.graphql.ts @@ -0,0 +1,97 @@ +/** + * @generated SignedSource<<1ad106fe6b3ac770c83852304ee79afe>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { Fragment, ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type AppQueryComponent_lists$data = { + readonly lists: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly name: string; + readonly id: string; + readonly tags: ReadonlyArray | null; + }; + }>; + }; + readonly " $fragmentType": "AppQueryComponent_lists"; +}; +export type AppQueryComponent_lists$key = { + readonly " $data"?: AppQueryComponent_lists$data; + readonly " $fragmentSpreads": FragmentRefs<"AppQueryComponent_lists">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "AppQueryComponent_lists", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ListConnection", + "kind": "LinkedField", + "name": "lists", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ListEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "List", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "tags", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null +}; + +(node as any).hash = "f7682e7e8dcd1a0ad8275bc5c03dbde8"; + +export default node; diff --git a/webui/src/__generated__/srcListsQuery.graphql.ts b/webui/src/__generated__/srcListsQuery.graphql.ts new file mode 100644 index 0000000..f71347d --- /dev/null +++ b/webui/src/__generated__/srcListsQuery.graphql.ts @@ -0,0 +1,217 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime'; +export type srcListsQuery$variables = {}; +export type srcListsQuery$data = { + readonly lists: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly name: string; + readonly tags: ReadonlyArray | null; + readonly entries: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly hashValue: string; + }; + }>; + } | null; + }; + }>; + }; +}; +export type srcListsQuery = { + variables: srcListsQuery$variables; + response: srcListsQuery$data; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "kind": "Literal", + "name": "first", + "value": 20 + } +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "tags", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v4 = { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 5 + } + ], + "concreteType": "EntryConnection", + "kind": "LinkedField", + "name": "entries", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "EntryEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Entry", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hashValue", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "entries(first:5)" +}; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "srcListsQuery", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": "ListConnection", + "kind": "LinkedField", + "name": "lists", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ListEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "List", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + (v2/*: any*/), + (v4/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "lists(first:20)" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "srcListsQuery", + "selections": [ + { + "alias": null, + "args": (v0/*: any*/), + "concreteType": "ListConnection", + "kind": "LinkedField", + "name": "lists", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "ListEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "List", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + (v2/*: any*/), + (v4/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "lists(first:20)" + } + ] + }, + "params": { + "cacheID": "27095434122932f164ae88c05f4cc6b9", + "id": null, + "metadata": {}, + "name": "srcListsQuery", + "operationKind": "query", + "text": "query srcListsQuery {\n lists(first: 20) {\n edges {\n node {\n name\n tags\n entries(first: 5) {\n edges {\n node {\n id\n hashValue\n }\n }\n }\n id\n }\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "ac81a444112c67683994c05979dedb6f"; + +export default node; diff --git a/webui/src/__generated__/srcUserDataQuery.graphql.ts b/webui/src/__generated__/srcUserDataQuery.graphql.ts new file mode 100644 index 0000000..6362fb2 --- /dev/null +++ b/webui/src/__generated__/srcUserDataQuery.graphql.ts @@ -0,0 +1,98 @@ +/** + * @generated SignedSource<<557f6510fc295600d6e0d8aa1cdad3af>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime'; +export type srcUserDataQuery$variables = {}; +export type srcUserDataQuery$data = { + readonly self: { + readonly id: string; + readonly username: string; + readonly matrixLinks: ReadonlyArray | null; + readonly admin: boolean | null; + }; +}; +export type srcUserDataQuery = { + variables: srcUserDataQuery$variables; + response: srcUserDataQuery$data; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "self", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "username", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "matrixLinks", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "admin", + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "srcUserDataQuery", + "selections": (v0/*: any*/), + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "srcUserDataQuery", + "selections": (v0/*: any*/) + }, + "params": { + "cacheID": "5a22c874dfb788e95810ee63ec866c39", + "id": null, + "metadata": {}, + "name": "srcUserDataQuery", + "operationKind": "query", + "text": "query srcUserDataQuery {\n self {\n id\n username\n matrixLinks\n admin\n }\n}\n" + } +}; +})(); + +(node as any).hash = "22115af4048ca4bee32c21e03d4cd3da"; + +export default node; diff --git a/webui/src/app/fetchGraphQL.ts b/webui/src/app/fetchGraphQL.ts new file mode 100644 index 0000000..2a7a81a --- /dev/null +++ b/webui/src/app/fetchGraphQL.ts @@ -0,0 +1,29 @@ +import {GQLAuthObj} from "../RelayEnvironment"; + +async function fetchGraphQL(auth: GQLAuthObj, text: any, variables: any) { + let headers: Record = { + 'Content-Type': 'application/json', + } + + if(auth.store && auth.store.getState().auth?.jwt) { + headers["Authorization"] = `Bearer ${auth.store.getState().auth.jwt}` + } + + if(auth.auth) { + headers["Authorization"] = `Bearer ${auth.auth}` + } + + const response = await fetch('http://127.0.0.1:8123/api/query', { + method: 'POST', + headers: headers, + body: JSON.stringify({ + query: text, + variables, + }), + }); + + // Get the response as JSON + return await response.json(); +} + +export default fetchGraphQL; \ No newline at end of file diff --git a/webui/src/components/auth/LoginView.tsx b/webui/src/components/auth/LoginView.tsx index ba5d2dc..dbb7c6d 100644 --- a/webui/src/components/auth/LoginView.tsx +++ b/webui/src/components/auth/LoginView.tsx @@ -4,8 +4,6 @@ import styles from "./AuthViews.module.scss"; import {ReactComponent as Logo} from "../../logo.svg"; import {Link, useLocation} from "react-router-dom"; -import {axiosDefault} from "../../context/axios"; -import {AxiosError} from "axios"; import {useAppDispatch} from "../../app/hooks"; import {logIn} from "../../features/auth/authSlice"; @@ -13,6 +11,7 @@ import {Key} from "lucide-react"; import {AuthLocationState} from "../../layouts/AuthLayout"; import {Helmet} from "react-helmet"; import {Trans, useTranslation} from "react-i18next"; +import LoginMutation from "../../mutations/LoginMutation"; const LoginView = () => { const [username, setUsername] = useState(""); @@ -34,6 +33,18 @@ const LoginView = () => { setError("") try { + const res = await LoginMutation(username, password) + + const jwt = res.login; + + dispatch(logIn(jwt)) + } catch (e: any) { + setError("An error occurred: "+e.source?.errors[0]?.message) + } finally { + setLoading(false) + } + + /*try { const res = await axiosDefault.post("/auth/login", { username, password @@ -52,7 +63,7 @@ const LoginView = () => { } } finally { setLoading(false) - } + }*/ } return <> diff --git a/webui/src/components/auth/RegisterView.tsx b/webui/src/components/auth/RegisterView.tsx index 7cfb5a8..fd816f8 100644 --- a/webui/src/components/auth/RegisterView.tsx +++ b/webui/src/components/auth/RegisterView.tsx @@ -8,12 +8,18 @@ import {AuthLocationState} from "../../layouts/AuthLayout"; import {useAppDispatch} from "../../app/hooks"; import {Helmet} from "react-helmet"; import {Trans, useTranslation} from "react-i18next"; +import {logIn} from "../../features/auth/authSlice"; +import RegisterMutation from "../../mutations/RegisterMutation"; +import {Key} from "lucide-react"; const RegisterView = () => { const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [matrix, setMatrix] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const location = useLocation() const locationState = location.state as AuthLocationState @@ -22,26 +28,57 @@ const RegisterView = () => { const {t} = useTranslation() - const onSubmit = () => { - console.log(username, password, matrix) + const onSubmit = async () => { + setLoading(true) + setError("") + + try { + const res = await RegisterMutation(username, password, matrix) + + const jwt = res.register; + + dispatch(logIn(jwt)) + } catch (e: any) { + setError("An error occurred: " + e.source?.errors[0]?.message) + } finally { + setLoading(false) + } } return <> - + {t("auth:register.htmlTitle", "Register with Veles")}

Register

-
{e.preventDefault(); onSubmit()}} className={styles.authForm}> - setUsername(ev.target.value)} value={username} placeholder={t("auth:username", "Username")} autoCapitalize={"no"} autoCorrect={"no"} /> - setPassword(ev.target.value)} value={password} placeholder={t("auth:password", "Password")} type={"password"} autoCapitalize={"no"} autoCorrect={"no"} /> - setMatrix(ev.target.value)} value={matrix} placeholder={t("auth:matrix_handle", "Matrix-Handle")+" (@user:matrix.org)"} autoCapitalize={"no"} autoCorrect={"no"} /> - -
+ {loading &&
+ + Logging in... +
} - I already have an account + {!loading && <> + {error !== "" && {error}} + +
{ + e.preventDefault(); + onSubmit() + }} className={styles.authForm}> + setUsername(ev.target.value)} value={username} + placeholder={t("auth:username", "Username")} autoCapitalize={"no"} autoCorrect={"no"}/> + setPassword(ev.target.value)} value={password} + placeholder={t("auth:password", "Password")} type={"password"} autoCapitalize={"no"} + autoCorrect={"no"}/> + setMatrix(ev.target.value)} value={matrix} + placeholder={t("auth:matrix_handle", "Matrix-Handle") + " (@user:matrix.org)"} + autoCapitalize={"no"} autoCorrect={"no"}/> + +
+ + I already have an account + } } diff --git a/webui/src/components/dashboard/Dashboard.tsx b/webui/src/components/dashboard/Dashboard.tsx new file mode 100644 index 0000000..c894404 --- /dev/null +++ b/webui/src/components/dashboard/Dashboard.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import {graphql} from "babel-plugin-relay/macro"; + + +import {PreloadedQuery, usePreloadedQuery} from "react-relay/hooks"; +import {DashboardQuery} from "./__generated__/DashboardQuery.graphql"; +import {Trans} from "react-i18next"; + +type Props = { + initialQueryRef: PreloadedQuery, +} + +const Dashboard = (props: Props) => { + const data = usePreloadedQuery( + graphql` + query DashboardQuery { + self { + username + id + admin + } + } + `, + props.initialQueryRef + ) + + const name = data.self?.username + + return <> +

Ayo {{name}}!

+ + {/**/} + + {/*hasNext && */} + +} + +export default Dashboard \ No newline at end of file diff --git a/webui/src/components/dashboard/__generated__/DashboardQuery.graphql.ts b/webui/src/components/dashboard/__generated__/DashboardQuery.graphql.ts new file mode 100644 index 0000000..7eb6c3b --- /dev/null +++ b/webui/src/components/dashboard/__generated__/DashboardQuery.graphql.ts @@ -0,0 +1,90 @@ +/** + * @generated SignedSource<<3feb62450a4128509da1c04742d02894>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime'; +export type DashboardQuery$variables = {}; +export type DashboardQuery$data = { + readonly self: { + readonly username: string; + readonly id: string; + readonly admin: boolean | null; + } | null; +}; +export type DashboardQuery = { + variables: DashboardQuery$variables; + response: DashboardQuery$data; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "self", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "username", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "admin", + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "DashboardQuery", + "selections": (v0/*: any*/), + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "DashboardQuery", + "selections": (v0/*: any*/) + }, + "params": { + "cacheID": "30b35581f559634650cb5670a9d33fef", + "id": null, + "metadata": {}, + "name": "DashboardQuery", + "operationKind": "query", + "text": "query DashboardQuery {\n self {\n username\n id\n admin\n }\n}\n" + } +}; +})(); + +(node as any).hash = "e57a6cbc8db2ebac74f44dc969f1f0dc"; + +export default node; diff --git a/webui/src/components/dashboard/__generated__/DashboardRefreshQuery.graphql.ts b/webui/src/components/dashboard/__generated__/DashboardRefreshQuery.graphql.ts new file mode 100644 index 0000000..9841ae2 --- /dev/null +++ b/webui/src/components/dashboard/__generated__/DashboardRefreshQuery.graphql.ts @@ -0,0 +1,248 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Query } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type DashboardRefreshQuery$variables = { + count?: number | null; + cursor?: string | null; +}; +export type DashboardRefreshQuery$data = { + readonly " $fragmentSpreads": FragmentRefs<"Dashboard_fragment">; +}; +export type DashboardRefreshQuery = { + variables: DashboardRefreshQuery$variables; + response: DashboardRefreshQuery$data; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "count" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "cursor" + } +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "username", + "storageKey": null +}, +v3 = [ + { + "kind": "Variable", + "name": "after", + "variableName": "cursor" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "count" + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "DashboardRefreshQuery", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "Dashboard_fragment" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "DashboardRefreshQuery", + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "name", + "value": "hello-world" + } + ], + "concreteType": "List", + "kind": "LinkedField", + "name": "list", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "tags", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "creator", + "plural": false, + "selections": [ + (v1/*: any*/), + (v2/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": "list(name:\"hello-world\")" + }, + { + "alias": null, + "args": (v3/*: any*/), + "concreteType": "EntryConnection", + "kind": "LinkedField", + "name": "entries", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "EntryEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Entry", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hashValue", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "addedBy", + "plural": false, + "selections": [ + (v2/*: any*/), + (v1/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": (v3/*: any*/), + "filters": null, + "handle": "connection", + "key": "Dashboard_entries", + "kind": "LinkedHandle", + "name": "entries" + } + ] + }, + "params": { + "cacheID": "6bcca75650c7302358635c549b4d0aeb", + "id": null, + "metadata": {}, + "name": "DashboardRefreshQuery", + "operationKind": "query", + "text": "query DashboardRefreshQuery(\n $count: Int\n $cursor: String\n) {\n ...Dashboard_fragment\n}\n\nfragment Dashboard_fragment on Query {\n list(name: \"hello-world\") {\n name\n id\n tags\n creator {\n id\n username\n }\n }\n entries(after: $cursor, first: $count) {\n edges {\n node {\n id\n hashValue\n addedBy {\n username\n id\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "2c84f3121efd16285a3877f4d33f3733"; + +export default node; diff --git a/webui/src/components/dashboard/__generated__/Dashboard_fragment.graphql.ts b/webui/src/components/dashboard/__generated__/Dashboard_fragment.graphql.ts new file mode 100644 index 0000000..0add292 --- /dev/null +++ b/webui/src/components/dashboard/__generated__/Dashboard_fragment.graphql.ts @@ -0,0 +1,240 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment, RefetchableFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type Dashboard_fragment$data = { + readonly list: { + readonly name: string; + readonly id: string; + readonly tags: ReadonlyArray | null; + readonly creator: { + readonly id: string; + readonly username: string; + }; + } | null; + readonly entries: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly hashValue: string; + readonly addedBy: { + readonly username: string; + }; + }; + }>; + } | null; + readonly " $fragmentType": "Dashboard_fragment"; +}; +export type Dashboard_fragment$key = { + readonly " $data"?: Dashboard_fragment$data; + readonly " $fragmentSpreads": FragmentRefs<"Dashboard_fragment">; +}; + +const node: ReaderFragment = (function(){ +var v0 = [ + "entries" +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "username", + "storageKey": null +}; +return { + "argumentDefinitions": [ + { + "kind": "RootArgument", + "name": "count" + }, + { + "kind": "RootArgument", + "name": "cursor" + } + ], + "kind": "Fragment", + "metadata": { + "connection": [ + { + "count": "count", + "cursor": "cursor", + "direction": "forward", + "path": (v0/*: any*/) + } + ], + "refetch": { + "connection": { + "forward": { + "count": "count", + "cursor": "cursor" + }, + "backward": null, + "path": (v0/*: any*/) + }, + "fragmentPathInResult": [], + "operation": require('./DashboardRefreshQuery.graphql') + } + }, + "name": "Dashboard_fragment", + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "name", + "value": "hello-world" + } + ], + "concreteType": "List", + "kind": "LinkedField", + "name": "list", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "tags", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "creator", + "plural": false, + "selections": [ + (v1/*: any*/), + (v2/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": "list(name:\"hello-world\")" + }, + { + "alias": "entries", + "args": null, + "concreteType": "EntryConnection", + "kind": "LinkedField", + "name": "__Dashboard_entries_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "EntryEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Entry", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hashValue", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "addedBy", + "plural": false, + "selections": [ + (v2/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null +}; +})(); + +(node as any).hash = "2c84f3121efd16285a3877f4d33f3733"; + +export default node; diff --git a/webui/src/index.scss b/webui/src/index.scss index 52adf9a..d515353 100644 --- a/webui/src/index.scss +++ b/webui/src/index.scss @@ -14,6 +14,8 @@ html, body, #root { margin: 0; padding: 0; font-family: $fontMain; + background-color: var(--veles-color-background); + color: var(--veles-color-foreground); } :root { diff --git a/webui/src/index.tsx b/webui/src/index.tsx index 34fe774..1a58dfc 100644 --- a/webui/src/index.tsx +++ b/webui/src/index.tsx @@ -6,17 +6,24 @@ import {store} from './app/store'; import {Provider} from 'react-redux'; import * as serviceWorker from './serviceWorker'; import { BrowserRouter } from "react-router-dom"; +import { + RelayEnvironmentProvider, +} from 'react-relay/hooks'; import "./i18n"; +import RelayEnvironment from "./RelayEnvironment"; + ReactDOM.render( - Loading...}> - - - - + + Loading...}> + + + + + , document.getElementById('root') diff --git a/webui/src/layouts/AuthLayout.module.scss b/webui/src/layouts/AuthLayout.module.scss index 51efbfe..69b7727 100644 --- a/webui/src/layouts/AuthLayout.module.scss +++ b/webui/src/layouts/AuthLayout.module.scss @@ -12,7 +12,7 @@ left: 0; width: 100%; height: 100%; - z-index: -1; + z-index: 0; background: linear-gradient(129.96deg,#ff2f2f 10.43%,#000460 92.78%),radial-gradient(100% 246.94% at 100% 0,#fff 0,#020063 100%),linear-gradient(58.72deg,#2200f2,#530000),linear-gradient(154.03deg,#b70000,#ff003d 74.04%),linear-gradient(341.1deg,red 7.52%,#0038ff 77.98%),linear-gradient(136.23deg,#00c2ff 11.12%,red 86.47%),radial-gradient(57.37% 100% at 50% 0,#b50000 0,#0034bb 100%); background-blend-mode: overlay,color-burn,screen,overlay,difference,difference,normal; } diff --git a/webui/src/layouts/AuthLayout.tsx b/webui/src/layouts/AuthLayout.tsx index 8c2d09f..b182a98 100644 --- a/webui/src/layouts/AuthLayout.tsx +++ b/webui/src/layouts/AuthLayout.tsx @@ -5,7 +5,7 @@ import {Link, useLocation, useNavigate, useOutlet} from "react-router-dom"; import {UserPlus, User} from "lucide-react"; import {ReactComponent as Logo} from "../logo.svg"; -import {useAppSelector} from "../app/hooks"; +import {useAppDispatch, useAppSelector} from "../app/hooks"; import {selectAuth} from "../features/auth/authSlice"; import {Trans} from "react-i18next"; diff --git a/webui/src/layouts/PanelLayout.module.scss b/webui/src/layouts/PanelLayout.module.scss index f1b0bfd..e0c1a9b 100644 --- a/webui/src/layouts/PanelLayout.module.scss +++ b/webui/src/layouts/PanelLayout.module.scss @@ -62,7 +62,7 @@ border-bottom: thin solid var(--veles-color-border); height: 66px; - a { + a, button { @include panelTopBarLink; } @@ -81,6 +81,10 @@ align-items: stretch; flex-grow: 1; + >* { + flex-shrink: 0; + } + > nav { display: flex; flex-direction: column; @@ -137,7 +141,10 @@ } > main { - padding: var(--veles-layout-padding) + padding: var(--veles-layout-padding); + height: calc(100vh - 66px); + overflow: auto; + flex-grow: 1; } } } \ No newline at end of file diff --git a/webui/src/layouts/PanelLayout.tsx b/webui/src/layouts/PanelLayout.tsx index 49b9c41..33b42aa 100644 --- a/webui/src/layouts/PanelLayout.tsx +++ b/webui/src/layouts/PanelLayout.tsx @@ -1,23 +1,28 @@ import React, {useState} from "react"; import {Link, NavLink, useOutlet} from "react-router-dom"; -import {Home, List, ClipboardList, ExternalLink, ChevronRight, MessageSquare} from "lucide-react"; +import {Home, List, ClipboardList, ExternalLink, ChevronRight, MessageSquare, LogOut} from "lucide-react"; import {ReactComponent as Logo} from "../logo.svg"; import styles from "./PanelLayout.module.scss"; import {Trans} from "react-i18next"; +import {useAppDispatch} from "../app/hooks"; +import {logOut} from "../features/auth/authSlice"; const PanelLayout = () => { const outlet = useOutlet(); const [hashingExpanded, setHashingExpanded] = useState(false) + const dispatch = useAppDispatch() + return
Jump to Content Jump to Navigation
Matrix-Veles Documentation +
- {outlet} + Fetching data...}> + {outlet} +
diff --git a/webui/src/mutations/LoginMutation.ts b/webui/src/mutations/LoginMutation.ts new file mode 100644 index 0000000..77417fa --- /dev/null +++ b/webui/src/mutations/LoginMutation.ts @@ -0,0 +1,40 @@ +import { + commitMutation +} from "relay-runtime"; +import {graphql} from "babel-plugin-relay/macro"; +import RelayEnvironment from "../RelayEnvironment"; +import { + LoginMutation as LoginMutationType, + LoginMutation$data, + LoginMutation$variables +} from "./__generated__/LoginMutation.graphql"; + +const mutation = graphql` + mutation LoginMutation($loginInput: Login!) { + login(input: $loginInput) + } +` + +const LoginMutation = (username: string, password: string): Promise => { + return new Promise((resolve, reject) => { + const variables: LoginMutation$variables = { + loginInput: { + username, + password + } + } + + commitMutation( + RelayEnvironment({}), + { + mutation: mutation, + variables, + onCompleted: resolve, + onError: reject + } + ) + }) + +} + +export default LoginMutation \ No newline at end of file diff --git a/webui/src/mutations/RegisterMutation.ts b/webui/src/mutations/RegisterMutation.ts new file mode 100644 index 0000000..4a21a29 --- /dev/null +++ b/webui/src/mutations/RegisterMutation.ts @@ -0,0 +1,41 @@ +import { + commitMutation +} from "relay-runtime"; +import {graphql} from "babel-plugin-relay/macro"; +import RelayEnvironment from "../RelayEnvironment"; +import { + RegisterMutation as RegisterMutationType, + RegisterMutation$data, + RegisterMutation$variables +} from "./__generated__/RegisterMutation.graphql"; + +const mutation = graphql` + mutation RegisterMutation($registerInput: Register!) { + register(input: $registerInput) + } +` + +const RegisterMutation = (username: string, password: string, mxID: string): Promise => { + return new Promise((resolve, reject) => { + const variables: RegisterMutation$variables = { + registerInput: { + username, + password, + mxID + } + } + + commitMutation( + RelayEnvironment({}), + { + mutation: mutation, + variables, + onCompleted: resolve, + onError: reject + } + ) + }) + +} + +export default RegisterMutation \ No newline at end of file diff --git a/webui/src/mutations/__generated__/LoginMutation.graphql.ts b/webui/src/mutations/__generated__/LoginMutation.graphql.ts new file mode 100644 index 0000000..25a4206 --- /dev/null +++ b/webui/src/mutations/__generated__/LoginMutation.graphql.ts @@ -0,0 +1,80 @@ +/** + * @generated SignedSource<<4f02a0f62b7e8664299d742dc566496a>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Mutation } from 'relay-runtime'; +export type Login = { + username: string; + password: string; +}; +export type LoginMutation$variables = { + loginInput: Login; +}; +export type LoginMutation$data = { + readonly login: string; +}; +export type LoginMutation = { + variables: LoginMutation$variables; + response: LoginMutation$data; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "loginInput" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "loginInput" + } + ], + "kind": "ScalarField", + "name": "login", + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "LoginMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "LoginMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "f93ebcc6267e266343c48ca5696aa0f2", + "id": null, + "metadata": {}, + "name": "LoginMutation", + "operationKind": "mutation", + "text": "mutation LoginMutation(\n $loginInput: Login!\n) {\n login(input: $loginInput)\n}\n" + } +}; +})(); + +(node as any).hash = "f4c8734042b7e93c4d1e63db9879561a"; + +export default node; diff --git a/webui/src/mutations/__generated__/RegisterMutation.graphql.ts b/webui/src/mutations/__generated__/RegisterMutation.graphql.ts new file mode 100644 index 0000000..f3ec2ee --- /dev/null +++ b/webui/src/mutations/__generated__/RegisterMutation.graphql.ts @@ -0,0 +1,81 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest, Mutation } from 'relay-runtime'; +export type Register = { + username: string; + password: string; + mxID: string; +}; +export type RegisterMutation$variables = { + registerInput: Register; +}; +export type RegisterMutation$data = { + readonly register: string; +}; +export type RegisterMutation = { + variables: RegisterMutation$variables; + response: RegisterMutation$data; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "registerInput" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "registerInput" + } + ], + "kind": "ScalarField", + "name": "register", + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "RegisterMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "RegisterMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "d49e8f643837ce5419c3a7a0a67d3bf0", + "id": null, + "metadata": {}, + "name": "RegisterMutation", + "operationKind": "mutation", + "text": "mutation RegisterMutation(\n $registerInput: Register!\n) {\n register(input: $registerInput)\n}\n" + } +}; +})(); + +(node as any).hash = "152daf2d7a917ffaafddee2b18453aa8"; + +export default node; diff --git a/webui/src/react-app-env.d.ts b/webui/src/react-app-env.d.ts index 6431bc5..94bf0bd 100644 --- a/webui/src/react-app-env.d.ts +++ b/webui/src/react-app-env.d.ts @@ -1 +1,4 @@ /// +declare module 'babel-plugin-relay/macro' { + export { graphql } from 'react-relay' +} \ No newline at end of file diff --git a/webui/yarn.lock b/webui/yarn.lock index 9b719d5..aa66c5b 100644 --- a/webui/yarn.lock +++ b/webui/yarn.lock @@ -1024,6 +1024,13 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" +"@babel/runtime@^7.0.0", "@babel/runtime@^7.7.2": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" + integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.5.1", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.17.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" @@ -1948,6 +1955,14 @@ hoist-non-react-statics "^3.3.0" redux "^4.0.0" +"@types/react-relay@^13.0.1": + version "13.0.1" + resolved "https://registry.yarnpkg.com/@types/react-relay/-/react-relay-13.0.1.tgz#acc25817b29febdc6b251fa076ccbe6ee9b97b1f" + integrity sha512-nEjkbipJ84oOvjDz8X5bqkpaPjq3tpQekaS9P/3rlePIigttN3m3S6HMthKCeRWwXeKNdVqrMcmdPXI0TQly3g== + dependencies: + "@types/react" "*" + "@types/relay-runtime" "*" + "@types/react@*": version "17.0.39" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.39.tgz#d0f4cde092502a6db00a1cded6e6bf2abb7633ce" @@ -1966,6 +1981,11 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/relay-runtime@*", "@types/relay-runtime@^13.0.2": + version "13.0.2" + resolved "https://registry.yarnpkg.com/@types/relay-runtime/-/relay-runtime-13.0.2.tgz#2b672d60ccf7cdb065fbf79724f6b34dfa79d016" + integrity sha512-3K0ONsoDb4xfGhOUfXpFQKM4xziP/YZzpbWEb0LfEC4S7n5zAn/IfQq+PISfVaU6wvSalz56lv/q/JBELzJ0RQ== + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -2551,7 +2571,7 @@ array.prototype.flatmap@^1.2.5: define-properties "^1.1.3" es-abstract "^1.19.0" -asap@~2.0.6: +asap@~2.0.3, asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -2669,6 +2689,15 @@ babel-plugin-jest-hoist@^27.5.1: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" +babel-plugin-macros@^2.0.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" + integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== + dependencies: + "@babel/runtime" "^7.7.2" + cosmiconfig "^6.0.0" + resolve "^1.12.0" + babel-plugin-macros@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" @@ -2707,6 +2736,15 @@ babel-plugin-polyfill-regenerator@^0.3.0: dependencies: "@babel/helper-define-polyfill-provider" "^0.3.1" +babel-plugin-relay@^13.2.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-relay/-/babel-plugin-relay-13.2.0.tgz#2145f05373ef8e452c6aff14c9055994a14d47a7" + integrity sha512-1jGcpu0aTHkbBNtMDq7F3ZQWtVPSJfIx83lW1Qce/AMElD27JEjXd9eRPwU0/9K6JQgjLESQMjurCwJAEF8Xxw== + dependencies: + babel-plugin-macros "^2.0.0" + cosmiconfig "^5.0.5" + graphql "15.3.0" + babel-plugin-transform-react-remove-prop-types@^0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a" @@ -2936,6 +2974,25 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" +caller-callsite@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134" + integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ= + dependencies: + callsites "^2.0.0" + +caller-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4" + integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ= + dependencies: + caller-callsite "^2.0.0" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -3320,6 +3377,16 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cosmiconfig@^5.0.5: + version "5.2.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" + integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA== + dependencies: + import-fresh "^2.0.0" + is-directory "^0.3.1" + js-yaml "^3.13.1" + parse-json "^4.0.0" + cosmiconfig@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" @@ -3342,7 +3409,7 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" -cross-fetch@3.1.5: +cross-fetch@3.1.5, cross-fetch@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.5.tgz#e1389f44d9e7ba767907f7af8454787952ab534f" integrity sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw== @@ -4445,6 +4512,24 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ== + +fbjs@^3.0.2: + version "3.0.4" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-3.0.4.tgz#e1871c6bd3083bac71ff2da868ad5067d37716c6" + integrity sha512-ucV0tDODnGV3JCnnkmoszb5lf4bNpzjv80K41wd4k798Etq+UYD0y0TIfalLjZoKgjive6/adkRnszwapiDgBQ== + dependencies: + cross-fetch "^3.1.5" + fbjs-css-vars "^1.0.0" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.30" + file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -4825,6 +4910,16 @@ graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +graphql@15.3.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" + integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== + +graphql@^16.3.0: + version "16.3.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.3.0.tgz#a91e24d10babf9e60c706919bb182b53ccdffc05" + integrity sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A== + gulp-sort@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/gulp-sort/-/gulp-sort-2.0.0.tgz#c6762a2f1f0de0a3fc595a21599d3fac8dba1aca" @@ -5159,6 +5254,14 @@ immutable@^4.0.0: resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23" integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw== +import-fresh@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" + integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY= + dependencies: + caller-path "^2.0.0" + resolve-from "^3.0.0" + import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -5217,6 +5320,13 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ip@^1.1.0: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -5299,6 +5409,11 @@ is-date-object@^1.0.1: dependencies: has-tostringtag "^1.0.0" +is-directory@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" + integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= + is-docker@^2.0.0, is-docker@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" @@ -6057,7 +6172,7 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= -json-parse-better-errors@^1.0.2: +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== @@ -6290,7 +6405,7 @@ lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7. resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -6603,6 +6718,11 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" +nullthrows@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" + integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== + nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" @@ -6850,6 +6970,14 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" @@ -7570,6 +7698,13 @@ promise-map-series@^0.3.0: resolved "https://registry.yarnpkg.com/promise-map-series/-/promise-map-series-0.3.0.tgz#41873ca3652bb7a042b387d538552da9b576f8a1" integrity sha512-3npG2NGhTc8BWBolLLf8l/92OxMGaRLbqvIh9wjCHhDXNvk4zsxaTaCpiCunW09qWPrN2zeNSNwRLVBrQQtutA== +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + promise@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/promise/-/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" @@ -7794,6 +7929,17 @@ react-refresh@^0.11.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== +react-relay@^13.2.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/react-relay/-/react-relay-13.2.0.tgz#3f8cf7bb4058d293b079d107b204fb5c27d7a4e7" + integrity sha512-ldUMWZ5nIMhz1VpRylFhMGc8lEaufR0ZwwfW6YqTMU/Rsy5c5nHGN7hvktPvdUV+biy6pzdRzfFiny469gok4A== + dependencies: + "@babel/runtime" "^7.0.0" + fbjs "^3.0.2" + invariant "^2.2.4" + nullthrows "^1.1.1" + relay-runtime "13.2.0" + react-router-dom@6: version "6.2.2" resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.2.2.tgz#f1a2c88365593c76b9612ae80154a13fcb72e442" @@ -8004,6 +8150,20 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= +relay-compiler@^13.2.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-13.2.0.tgz#06dd416e19e1cd22008b89499726ea5eb342c36e" + integrity sha512-GGLWTJYqQ5jQLMXlq++Fl17f4gMuvmIi3+xU/TrD4UqWZLI3qhxE0NFAmOvg6xgBQ8xtCMhP066kv+3bhL+7Aw== + +relay-runtime@13.2.0: + version "13.2.0" + resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-13.2.0.tgz#e587e8c5cc9decdacb11227425bfe9cd5d9fa36f" + integrity sha512-iDS/fy5iWg9EhySaPpeo4Fwy1cYwNJqLHdhdGncpJBtSogFFd3fI+K0SafUoxX7+Moj6a+aMQt/JKN78HdeLhA== + dependencies: + "@babel/runtime" "^7.0.0" + fbjs "^3.0.2" + invariant "^2.2.4" + remove-bom-buffer@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53" @@ -8069,6 +8229,11 @@ resolve-cwd@^3.0.0: dependencies: resolve-from "^5.0.0" +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + integrity sha1-six699nWiBvItuZTM17rywoYh0g= + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -8107,7 +8272,7 @@ resolve.exports@^1.1.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== -resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0: +resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== @@ -8361,6 +8526,11 @@ serve-static@1.14.2: parseurl "~1.3.3" send "0.17.2" +setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= + setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" @@ -9080,6 +9250,11 @@ typescript@~4.1.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.6.tgz#1becd85d77567c3c741172339e93ce2e69932138" integrity sha512-pxnwLxeb/Z5SP80JDRzVjh58KsM6jZHRAOtTpS7sXLS4ogXNKC9ANxHHZqLLeVHZN35jCtI4JdmLLbLiC1kBow== +ua-parser-js@^0.7.30: + version "0.7.31" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6" + integrity sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ== + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"