diff --git a/admin/src/components/reqlog/ConfirmationDialog.tsx b/admin/src/components/reqlog/ConfirmationDialog.tsx
new file mode 100644
index 0000000..0c65398
--- /dev/null
+++ b/admin/src/components/reqlog/ConfirmationDialog.tsx
@@ -0,0 +1,53 @@
+import React, { useState } from "react";
+import Button from "@material-ui/core/Button";
+import Dialog from "@material-ui/core/Dialog";
+import DialogActions from "@material-ui/core/DialogActions";
+import DialogContent from "@material-ui/core/DialogContent";
+import DialogContentText from "@material-ui/core/DialogContentText";
+import DialogTitle from "@material-ui/core/DialogTitle";
+
+export function useConfirmationDialog() {
+ const [isOpen, setIsOpen] = useState(false);
+ const close = () => setIsOpen(false);
+ const open = () => setIsOpen(true);
+
+ return { open, close, isOpen };
+}
+
+interface ConfirmationDialog {
+ isOpen: boolean;
+ onClose: () => void;
+ onConfirm: () => void;
+ children: React.ReactNode;
+}
+
+export function ConfirmationDialog(props: ConfirmationDialog) {
+ const { onClose, onConfirm, isOpen, children } = props;
+
+ function confirm() {
+ onConfirm();
+ onClose();
+ }
+
+ return (
+
+ );
+}
diff --git a/admin/src/components/reqlog/LogsOverview.tsx b/admin/src/components/reqlog/LogsOverview.tsx
index f8341e3..7216ee0 100644
--- a/admin/src/components/reqlog/LogsOverview.tsx
+++ b/admin/src/components/reqlog/LogsOverview.tsx
@@ -1,41 +1,24 @@
import { useRouter } from "next/router";
-import { gql, useQuery } from "@apollo/client";
import Link from "next/link";
import {
Box,
- Typography,
CircularProgress,
Link as MaterialLink,
+ Typography,
} from "@material-ui/core";
import Alert from "@material-ui/lab/Alert";
import RequestList from "./RequestList";
import LogDetail from "./LogDetail";
import CenteredPaper from "../CenteredPaper";
-
-const HTTP_REQUEST_LOGS = gql`
- query HttpRequestLogs {
- httpRequestLogs {
- id
- method
- url
- timestamp
- response {
- statusCode
- statusReason
- }
- }
- }
-`;
+import { useHttpRequestLogs } from "./hooks/useHttpRequestLogs";
function LogsOverview(): JSX.Element {
const router = useRouter();
const detailReqLogId =
router.query.id && parseInt(router.query.id as string, 10);
- const { loading, error, data } = useQuery(HTTP_REQUEST_LOGS, {
- pollInterval: 1000,
- });
+ const { loading, error, data } = useHttpRequestLogs();
const handleLogClick = (reqId: number) => {
router.push("/proxy/logs?id=" + reqId, undefined, {
diff --git a/admin/src/components/reqlog/Search.tsx b/admin/src/components/reqlog/Search.tsx
index 7f6ae7b..76a2d72 100644
--- a/admin/src/components/reqlog/Search.tsx
+++ b/admin/src/components/reqlog/Search.tsx
@@ -16,10 +16,16 @@ import {
import IconButton from "@material-ui/core/IconButton";
import SearchIcon from "@material-ui/icons/Search";
import FilterListIcon from "@material-ui/icons/FilterList";
+import DeleteIcon from "@material-ui/icons/Delete";
import React, { useRef, useState } from "react";
-import { gql, useApolloClient, useMutation, useQuery } from "@apollo/client";
+import { gql, useMutation, useQuery } from "@apollo/client";
import { withoutTypename } from "../../lib/omitTypename";
import { Alert } from "@material-ui/lab";
+import { useClearHTTPRequestLog } from "./hooks/useClearHTTPRequestLog";
+import {
+ ConfirmationDialog,
+ useConfirmationDialog,
+} from "./ConfirmationDialog";
const FILTER = gql`
query HttpRequestLogFilter {
@@ -79,15 +85,14 @@ function Search(): JSX.Element {
FILTER
);
- const client = useApolloClient();
const [
setFilterMutate,
{ error: setFilterErr, loading: setFilterLoading },
] = useMutation<{
setHttpRequestLogFilter: SearchFilter | null;
}>(SET_FILTER, {
- update(_, { data: { setHttpRequestLogFilter } }) {
- client.writeQuery({
+ update(cache, { data: { setHttpRequestLogFilter } }) {
+ cache.writeQuery({
query: FILTER,
data: {
httpRequestLogFilter: setHttpRequestLogFilter,
@@ -96,6 +101,12 @@ function Search(): JSX.Element {
},
});
+ const [
+ clearHTTPRequestLog,
+ clearHTTPRequestLogResult,
+ ] = useClearHTTPRequestLog();
+ const clearHTTPConfirmationDialog = useConfirmationDialog();
+
const filterRef = useRef();
const [filterOpen, setFilterOpen] = useState(false);
@@ -111,90 +122,112 @@ function Search(): JSX.Element {
};
return (
-
-
- {filterErr && (
-
-
- Error fetching filter: {filterErr.message}
-
-
- )}
- {setFilterErr && (
-
-
- Error setting filter: {setFilterErr.message}
-
-
- )}
-
-
- setFilterOpen(!filterOpen)}
- style={{
- color:
- filter?.httpRequestLogFilter !== null
- ? theme.palette.secondary.main
- : "inherit",
- }}
+
+
+
+
+
+
+
+
+ setFilterOpen(!filterOpen)}
+ style={{
+ color:
+ filter?.httpRequestLogFilter !== null
+ ? theme.palette.secondary.main
+ : "inherit",
+ }}
+ >
+ {filterLoading || setFilterLoading ? (
+
+ ) : (
+
+ )}
+
+
+ setFilterOpen(true)}
+ />
+
+
+
+
+
+
- {filterLoading || setFilterLoading ? (
-
- ) : (
-
- )}
-
-
- setFilterOpen(true)}
- />
-
-
-
-
-
-
-
-
-
-
- setFilterMutate({
- variables: {
- filter: {
- ...withoutTypename(filter?.httpRequestLogFilter),
- onlyInScope: e.target.checked,
- },
- },
- })
+
+
+ setFilterMutate({
+ variables: {
+ filter: {
+ ...withoutTypename(filter?.httpRequestLogFilter),
+ onlyInScope: e.target.checked,
+ },
+ },
+ })
+ }
+ />
}
+ label="Only show in-scope requests"
/>
- }
- label="Only show in-scope requests"
- />
+
+
-
+
+
+
+
+
+
+
+
-
+
+ All proxy logs are going to be removed. This action cannot be undone.
+
+
+ );
+}
+
+function Error(props: { prefix: string; error?: Error }) {
+ if (!props.error) return null;
+
+ return (
+
+
+ {props.prefix}: {props.error.message}
+
+
);
}
diff --git a/admin/src/components/reqlog/hooks/useClearHTTPRequestLog.ts b/admin/src/components/reqlog/hooks/useClearHTTPRequestLog.ts
new file mode 100644
index 0000000..2870712
--- /dev/null
+++ b/admin/src/components/reqlog/hooks/useClearHTTPRequestLog.ts
@@ -0,0 +1,16 @@
+import { gql, useMutation } from "@apollo/client";
+import { HTTP_REQUEST_LOGS } from "./useHttpRequestLogs";
+
+const CLEAR_HTTP_REQUEST_LOG = gql`
+ mutation ClearHTTPRequestLog {
+ clearHTTPRequestLog {
+ success
+ }
+ }
+`;
+
+export function useClearHTTPRequestLog() {
+ return useMutation(CLEAR_HTTP_REQUEST_LOG, {
+ refetchQueries: [{ query: HTTP_REQUEST_LOGS }],
+ });
+}
diff --git a/admin/src/components/reqlog/hooks/useHttpRequestLogs.ts b/admin/src/components/reqlog/hooks/useHttpRequestLogs.ts
new file mode 100644
index 0000000..f426702
--- /dev/null
+++ b/admin/src/components/reqlog/hooks/useHttpRequestLogs.ts
@@ -0,0 +1,22 @@
+import { gql, useQuery } from "@apollo/client";
+
+export const HTTP_REQUEST_LOGS = gql`
+ query HttpRequestLogs {
+ httpRequestLogs {
+ id
+ method
+ url
+ timestamp
+ response {
+ statusCode
+ statusReason
+ }
+ }
+ }
+`;
+
+export function useHttpRequestLogs() {
+ return useQuery(HTTP_REQUEST_LOGS, {
+ pollInterval: 1000,
+ });
+}
diff --git a/pkg/api/generated.go b/pkg/api/generated.go
index f0abe64..6da8e9d 100644
--- a/pkg/api/generated.go
+++ b/pkg/api/generated.go
@@ -43,6 +43,10 @@ type DirectiveRoot struct {
}
type ComplexityRoot struct {
+ ClearHTTPRequestLogResult struct {
+ Success func(childComplexity int) int
+ }
+
CloseProjectResult struct {
Success func(childComplexity int) int
}
@@ -81,6 +85,7 @@ type ComplexityRoot struct {
}
Mutation struct {
+ ClearHTTPRequestLog func(childComplexity int) int
CloseProject func(childComplexity int) int
DeleteProject func(childComplexity int, name string) int
OpenProject func(childComplexity int, name string) int
@@ -118,6 +123,7 @@ type MutationResolver interface {
OpenProject(ctx context.Context, name string) (*Project, error)
CloseProject(ctx context.Context) (*CloseProjectResult, error)
DeleteProject(ctx context.Context, name string) (*DeleteProjectResult, error)
+ ClearHTTPRequestLog(ctx context.Context) (*ClearHTTPRequestLogResult, error)
SetScope(ctx context.Context, scope []ScopeRuleInput) ([]ScopeRule, error)
SetHTTPRequestLogFilter(ctx context.Context, filter *HTTPRequestLogFilterInput) (*HTTPRequestLogFilter, error)
}
@@ -145,6 +151,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
_ = ec
switch typeName + "." + field {
+ case "ClearHTTPRequestLogResult.success":
+ if e.complexity.ClearHTTPRequestLogResult.Success == nil {
+ break
+ }
+
+ return e.complexity.ClearHTTPRequestLogResult.Success(childComplexity), true
+
case "CloseProjectResult.success":
if e.complexity.CloseProjectResult.Success == nil {
break
@@ -278,6 +291,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.HTTPResponseLog.StatusReason(childComplexity), true
+ case "Mutation.clearHTTPRequestLog":
+ if e.complexity.Mutation.ClearHTTPRequestLog == nil {
+ break
+ }
+
+ return e.complexity.Mutation.ClearHTTPRequestLog(childComplexity), true
+
case "Mutation.closeProject":
if e.complexity.Mutation.CloseProject == nil {
break
@@ -553,6 +573,10 @@ type DeleteProjectResult {
success: Boolean!
}
+type ClearHTTPRequestLogResult {
+ success: Boolean!
+}
+
input HttpRequestLogFilterInput {
onlyInScope: Boolean
}
@@ -574,6 +598,7 @@ type Mutation {
openProject(name: String!): Project
closeProject: CloseProjectResult!
deleteProject(name: String!): DeleteProjectResult!
+ clearHTTPRequestLog: ClearHTTPRequestLogResult!
setScope(scope: [ScopeRuleInput!]!): [ScopeRule!]!
setHttpRequestLogFilter(
filter: HttpRequestLogFilterInput
@@ -730,6 +755,41 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg
// region **************************** field.gotpl *****************************
+func (ec *executionContext) _ClearHTTPRequestLogResult_success(ctx context.Context, field graphql.CollectedField, obj *ClearHTTPRequestLogResult) (ret graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
+ fc := &graphql.FieldContext{
+ Object: "ClearHTTPRequestLogResult",
+ Field: field,
+ Args: nil,
+ IsMethod: false,
+ IsResolver: false,
+ }
+
+ ctx = graphql.WithFieldContext(ctx, fc)
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.Success, nil
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ if !graphql.HasFieldError(ctx, fc) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.(bool)
+ fc.Result = res
+ return ec.marshalNBoolean2bool(ctx, field.Selections, res)
+}
+
func (ec *executionContext) _CloseProjectResult_success(ctx context.Context, field graphql.CollectedField, obj *CloseProjectResult) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -1502,6 +1562,41 @@ func (ec *executionContext) _Mutation_deleteProject(ctx context.Context, field g
return ec.marshalNDeleteProjectResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐDeleteProjectResult(ctx, field.Selections, res)
}
+func (ec *executionContext) _Mutation_clearHTTPRequestLog(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
+ fc := &graphql.FieldContext{
+ Object: "Mutation",
+ Field: field,
+ Args: nil,
+ IsMethod: true,
+ IsResolver: true,
+ }
+
+ ctx = graphql.WithFieldContext(ctx, fc)
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
+ ctx = rctx // use context from middleware stack in children
+ return ec.resolvers.Mutation().ClearHTTPRequestLog(rctx)
+ })
+ if err != nil {
+ ec.Error(ctx, err)
+ return graphql.Null
+ }
+ if resTmp == nil {
+ if !graphql.HasFieldError(ctx, fc) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ res := resTmp.(*ClearHTTPRequestLogResult)
+ fc.Result = res
+ return ec.marshalNClearHTTPRequestLogResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐClearHTTPRequestLogResult(ctx, field.Selections, res)
+}
+
func (ec *executionContext) _Mutation_setScope(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -3271,6 +3366,33 @@ func (ec *executionContext) unmarshalInputScopeRuleInput(ctx context.Context, ob
// region **************************** object.gotpl ****************************
+var clearHTTPRequestLogResultImplementors = []string{"ClearHTTPRequestLogResult"}
+
+func (ec *executionContext) _ClearHTTPRequestLogResult(ctx context.Context, sel ast.SelectionSet, obj *ClearHTTPRequestLogResult) graphql.Marshaler {
+ fields := graphql.CollectFields(ec.OperationContext, sel, clearHTTPRequestLogResultImplementors)
+
+ out := graphql.NewFieldSet(fields)
+ var invalids uint32
+ for i, field := range fields {
+ switch field.Name {
+ case "__typename":
+ out.Values[i] = graphql.MarshalString("ClearHTTPRequestLogResult")
+ case "success":
+ out.Values[i] = ec._ClearHTTPRequestLogResult_success(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ invalids++
+ }
+ default:
+ panic("unknown field " + strconv.Quote(field.Name))
+ }
+ }
+ out.Dispatch()
+ if invalids > 0 {
+ return graphql.Null
+ }
+ return out
+}
+
var closeProjectResultImplementors = []string{"CloseProjectResult"}
func (ec *executionContext) _CloseProjectResult(ctx context.Context, sel ast.SelectionSet, obj *CloseProjectResult) graphql.Marshaler {
@@ -3516,6 +3638,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
invalids++
}
+ case "clearHTTPRequestLog":
+ out.Values[i] = ec._Mutation_clearHTTPRequestLog(ctx, field)
+ if out.Values[i] == graphql.Null {
+ invalids++
+ }
case "setScope":
out.Values[i] = ec._Mutation_setScope(ctx, field)
if out.Values[i] == graphql.Null {
@@ -3985,6 +4112,20 @@ func (ec *executionContext) marshalNBoolean2bool(ctx context.Context, sel ast.Se
return res
}
+func (ec *executionContext) marshalNClearHTTPRequestLogResult2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐClearHTTPRequestLogResult(ctx context.Context, sel ast.SelectionSet, v ClearHTTPRequestLogResult) graphql.Marshaler {
+ return ec._ClearHTTPRequestLogResult(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalNClearHTTPRequestLogResult2ᚖgithubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐClearHTTPRequestLogResult(ctx context.Context, sel ast.SelectionSet, v *ClearHTTPRequestLogResult) graphql.Marshaler {
+ if v == nil {
+ if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+ ec.Errorf(ctx, "must not be null")
+ }
+ return graphql.Null
+ }
+ return ec._ClearHTTPRequestLogResult(ctx, sel, v)
+}
+
func (ec *executionContext) marshalNCloseProjectResult2githubᚗcomᚋdstotijnᚋhettyᚋpkgᚋapiᚐCloseProjectResult(ctx context.Context, sel ast.SelectionSet, v CloseProjectResult) graphql.Marshaler {
return ec._CloseProjectResult(ctx, sel, &v)
}
diff --git a/pkg/api/models_gen.go b/pkg/api/models_gen.go
index 81c83d4..61a695b 100644
--- a/pkg/api/models_gen.go
+++ b/pkg/api/models_gen.go
@@ -9,6 +9,10 @@ import (
"time"
)
+type ClearHTTPRequestLogResult struct {
+ Success bool `json:"success"`
+}
+
type CloseProjectResult struct {
Success bool `json:"success"`
}
diff --git a/pkg/api/resolvers.go b/pkg/api/resolvers.go
index e15b7b1..eeec37b 100644
--- a/pkg/api/resolvers.go
+++ b/pkg/api/resolvers.go
@@ -209,6 +209,13 @@ func (r *mutationResolver) DeleteProject(ctx context.Context, name string) (*Del
}, nil
}
+func (r *mutationResolver) ClearHTTPRequestLog(ctx context.Context) (*ClearHTTPRequestLogResult, error) {
+ if err := r.RequestLogService.ClearRequests(ctx); err != nil {
+ return nil, fmt.Errorf("could not clear request log: %v", err)
+ }
+ return &ClearHTTPRequestLogResult{true}, nil
+}
+
func (r *mutationResolver) SetScope(ctx context.Context, input []ScopeRuleInput) ([]ScopeRule, error) {
rules := make([]scope.Rule, len(input))
for i, rule := range input {
diff --git a/pkg/api/schema.graphql b/pkg/api/schema.graphql
index 32691b2..53a3757 100644
--- a/pkg/api/schema.graphql
+++ b/pkg/api/schema.graphql
@@ -58,6 +58,10 @@ type DeleteProjectResult {
success: Boolean!
}
+type ClearHTTPRequestLogResult {
+ success: Boolean!
+}
+
input HttpRequestLogFilterInput {
onlyInScope: Boolean
}
@@ -79,6 +83,7 @@ type Mutation {
openProject(name: String!): Project
closeProject: CloseProjectResult!
deleteProject(name: String!): DeleteProjectResult!
+ clearHTTPRequestLog: ClearHTTPRequestLogResult!
setScope(scope: [ScopeRuleInput!]!): [ScopeRule!]!
setHttpRequestLogFilter(
filter: HttpRequestLogFilterInput
diff --git a/pkg/db/sqlite/sqlite.go b/pkg/db/sqlite/sqlite.go
index abbcba1..d3fa3ef 100644
--- a/pkg/db/sqlite/sqlite.go
+++ b/pkg/db/sqlite/sqlite.go
@@ -215,6 +215,17 @@ var headerFieldToColumnMap = map[string]string{
"value": "value",
}
+func (c *Client) ClearRequestLogs(ctx context.Context) error {
+ if c.db == nil {
+ return proj.ErrNoProject
+ }
+ _, err := c.db.Exec("DELETE FROM http_requests")
+ if err != nil {
+ return fmt.Errorf("sqlite: could not delete requests: %v", err)
+ }
+ return nil
+}
+
func (c *Client) FindRequestLogs(
ctx context.Context,
filter reqlog.FindRequestsFilter,
diff --git a/pkg/reqlog/repo.go b/pkg/reqlog/repo.go
index 8607a60..7126a43 100644
--- a/pkg/reqlog/repo.go
+++ b/pkg/reqlog/repo.go
@@ -17,6 +17,7 @@ type Repository interface {
FindRequestLogByID(ctx context.Context, id int64) (Request, error)
AddRequestLog(ctx context.Context, req http.Request, body []byte, timestamp time.Time) (*Request, error)
AddResponseLog(ctx context.Context, reqID int64, res http.Response, body []byte, timestamp time.Time) (*Response, error)
+ ClearRequestLogs(ctx context.Context) error
UpsertSettings(ctx context.Context, module string, settings interface{}) error
FindSettingsByModule(ctx context.Context, module string, settings interface{}) error
}
diff --git a/pkg/reqlog/reqlog.go b/pkg/reqlog/reqlog.go
index 0957b56..8866e15 100644
--- a/pkg/reqlog/reqlog.go
+++ b/pkg/reqlog/reqlog.go
@@ -99,6 +99,10 @@ func (svc *Service) SetRequestLogFilter(ctx context.Context, filter FindRequests
return svc.repo.UpsertSettings(ctx, "reqlog", svc)
}
+func (svc *Service) ClearRequests(ctx context.Context) error {
+ return svc.repo.ClearRequestLogs(ctx)
+}
+
func (svc *Service) addRequest(
ctx context.Context,
req http.Request,