mirror of
https://github.com/kopia/kopia.git
synced 2026-04-26 17:09:07 -04:00
feat(ui): support for editing pins and description (#1748)
https://user-images.githubusercontent.com/249880/152926565-2d3186d2-7989-4482-bcbc-9b20659745dd.mp4
This commit is contained in:
2
go.mod
2
go.mod
@@ -88,7 +88,7 @@ require (
|
||||
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kopia/htmluibuild v0.0.0-20220211164311-3c58cff936c2
|
||||
github.com/kopia/htmluibuild v0.0.0-20220212024129-c944ece3b4d0
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/mattn/go-ieproxy v0.0.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -406,6 +406,10 @@ github.com/kopia/htmluibuild v0.0.0-20220208062920-a1d47d1e3096 h1:rbrPWsO4OTT2c
|
||||
github.com/kopia/htmluibuild v0.0.0-20220208062920-a1d47d1e3096/go.mod h1:eWer4rx9P8lJo2eKc+Q7AZ1dE1x1hJNdkbDFPzMu1Hw=
|
||||
github.com/kopia/htmluibuild v0.0.0-20220211164311-3c58cff936c2 h1:8EzPQNPLdwH3camd8l6/gHpSRS43cKqd38FKebcXbrE=
|
||||
github.com/kopia/htmluibuild v0.0.0-20220211164311-3c58cff936c2/go.mod h1:eWer4rx9P8lJo2eKc+Q7AZ1dE1x1hJNdkbDFPzMu1Hw=
|
||||
github.com/kopia/htmluibuild v0.0.0-20220212022637-f4a448ee180a h1:FugmgYM3pW92AjfMJsGZArtF4TROg5rDKLalXXk/50E=
|
||||
github.com/kopia/htmluibuild v0.0.0-20220212022637-f4a448ee180a/go.mod h1:eWer4rx9P8lJo2eKc+Q7AZ1dE1x1hJNdkbDFPzMu1Hw=
|
||||
github.com/kopia/htmluibuild v0.0.0-20220212024129-c944ece3b4d0 h1:jdyUUH53CP2G30w4endw1eXE3GmI4km2hmK1ZZ75fIg=
|
||||
github.com/kopia/htmluibuild v0.0.0-20220212024129-c944ece3b4d0/go.mod h1:eWer4rx9P8lJo2eKc+Q7AZ1dE1x1hJNdkbDFPzMu1Hw=
|
||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
|
||||
@@ -128,6 +128,52 @@ func (s *Server) handleDeleteSnapshots(ctx context.Context, r *http.Request, bod
|
||||
return &serverapi.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *Server) handleEditSnapshots(ctx context.Context, r *http.Request, body []byte) (interface{}, *apiError) {
|
||||
var req serverapi.EditSnapshotsRequest
|
||||
|
||||
if err := json.Unmarshal(body, &req); err != nil {
|
||||
return nil, requestError(serverapi.ErrorMalformedRequest, "malformed request")
|
||||
}
|
||||
|
||||
var snaps []*serverapi.Snapshot
|
||||
|
||||
if err := repo.WriteSession(ctx, s.rep, repo.WriteSessionOptions{
|
||||
Purpose: "EditSnapshots",
|
||||
}, func(ctx context.Context, w repo.RepositoryWriter) error {
|
||||
for _, id := range req.Snapshots {
|
||||
snap, err := snapshot.LoadSnapshot(ctx, w, id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to load snapshot")
|
||||
}
|
||||
|
||||
changed := false
|
||||
|
||||
if snap.UpdatePins(req.AddPins, req.RemovePins) {
|
||||
changed = true
|
||||
}
|
||||
|
||||
if req.NewDescription != nil {
|
||||
changed = true
|
||||
snap.Description = *req.NewDescription
|
||||
}
|
||||
|
||||
if changed {
|
||||
if err := snapshot.UpdateSnapshot(ctx, w, snap); err != nil {
|
||||
return errors.Wrap(err, "error updating snapshot")
|
||||
}
|
||||
}
|
||||
|
||||
snaps = append(snaps, convertSnapshotManifest(snap))
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, internalServerError(err)
|
||||
}
|
||||
|
||||
return snaps, nil
|
||||
}
|
||||
|
||||
func uniqueSnapshots(rows []*serverapi.Snapshot) []*serverapi.Snapshot {
|
||||
result := []*serverapi.Snapshot{}
|
||||
resultByRootEntry := map[string]*serverapi.Snapshot{}
|
||||
|
||||
@@ -177,3 +177,81 @@ func TestListAndDeleteSnapshots(t *testing.T) {
|
||||
|
||||
require.Empty(t, sourceList.Sources)
|
||||
}
|
||||
|
||||
func TestEditSnapshots(t *testing.T) {
|
||||
ctx, env := repotesting.NewEnvironment(t, repotesting.FormatNotImportant)
|
||||
|
||||
si1 := localSource(env, "/dummy/path")
|
||||
|
||||
var id11 manifest.ID
|
||||
|
||||
require.NoError(t, repo.WriteSession(ctx, env.Repository, repo.WriteSessionOptions{Purpose: "Test"}, func(ctx context.Context, w repo.RepositoryWriter) error {
|
||||
u := snapshotfs.NewUploader(w)
|
||||
|
||||
dir1 := mockfs.NewDirectory()
|
||||
|
||||
dir1.AddFile("file1", []byte{1, 2, 3}, 0o644)
|
||||
dir1.AddFile("file2", []byte{1, 2, 4}, 0o644)
|
||||
|
||||
man11, err := u.Upload(ctx, dir1, nil, si1)
|
||||
require.NoError(t, err)
|
||||
id11, err = snapshot.SaveSnapshot(ctx, w, man11)
|
||||
require.NoError(t, err)
|
||||
|
||||
return nil
|
||||
}))
|
||||
|
||||
srvInfo := startServer(t, env, false)
|
||||
|
||||
cli, err := apiclient.NewKopiaAPIClient(apiclient.Options{
|
||||
BaseURL: srvInfo.BaseURL,
|
||||
TrustedServerCertificateFingerprint: srvInfo.TrustedServerCertificateFingerprint,
|
||||
Username: testUIUsername,
|
||||
Password: testUIPassword,
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, cli.FetchCSRFTokenForTesting(ctx))
|
||||
|
||||
resp, err := serverapi.ListSnapshots(ctx, cli, si1, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, resp.Snapshots, 1)
|
||||
|
||||
var (
|
||||
updated []*serverapi.Snapshot
|
||||
|
||||
newDesc1 = "desc1"
|
||||
newDesc2 = "desc2"
|
||||
)
|
||||
|
||||
require.NoError(t, cli.Post(ctx, "snapshots/edit", &serverapi.EditSnapshotsRequest{
|
||||
Snapshots: []manifest.ID{id11},
|
||||
AddPins: []string{"pin1", "pin2"},
|
||||
NewDescription: &newDesc1,
|
||||
}, &updated))
|
||||
|
||||
require.Len(t, updated, 1)
|
||||
require.EqualValues(t, []string{"pin1", "pin2"}, updated[0].Pins)
|
||||
require.EqualValues(t, newDesc1, updated[0].Description)
|
||||
|
||||
require.NoError(t, cli.Post(ctx, "snapshots/edit", &serverapi.EditSnapshotsRequest{
|
||||
Snapshots: []manifest.ID{updated[0].ID},
|
||||
AddPins: []string{"pin3"},
|
||||
RemovePins: []string{"pin1"},
|
||||
NewDescription: &newDesc2,
|
||||
}, &updated))
|
||||
|
||||
require.Len(t, updated, 1)
|
||||
require.EqualValues(t, []string{"pin2", "pin3"}, updated[0].Pins)
|
||||
require.EqualValues(t, newDesc2, updated[0].Description)
|
||||
|
||||
require.NoError(t, cli.Post(ctx, "snapshots/edit", &serverapi.EditSnapshotsRequest{
|
||||
Snapshots: []manifest.ID{updated[0].ID},
|
||||
RemovePins: []string{"pin3"},
|
||||
}, &updated))
|
||||
|
||||
require.Len(t, updated, 1)
|
||||
require.EqualValues(t, []string{"pin2"}, updated[0].Pins)
|
||||
require.EqualValues(t, newDesc2, updated[0].Description)
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ func (s *Server) SetupHTMLUIAPIHandlers(m *mux.Router) {
|
||||
// snapshots
|
||||
m.HandleFunc("/api/v1/snapshots", s.handleUI(s.handleListSnapshots)).Methods(http.MethodGet)
|
||||
m.HandleFunc("/api/v1/snapshots/delete", s.handleUI(s.handleDeleteSnapshots)).Methods(http.MethodPost)
|
||||
m.HandleFunc("/api/v1/snapshots/edit", s.handleUI(s.handleEditSnapshots)).Methods(http.MethodPost)
|
||||
m.HandleFunc("/api/v1/policy", s.handleUI(s.handlePolicyGet)).Methods(http.MethodGet)
|
||||
m.HandleFunc("/api/v1/policy", s.handleUI(s.handlePolicyPut)).Methods(http.MethodPut)
|
||||
m.HandleFunc("/api/v1/policy", s.handleUI(s.handlePolicyDelete)).Methods(http.MethodDelete)
|
||||
|
||||
@@ -178,6 +178,14 @@ type DeleteSnapshotsRequest struct {
|
||||
DeleteSourceAndPolicy bool `json:"deleteSourceAndPolicy"`
|
||||
}
|
||||
|
||||
// EditSnapshotsRequest contains request to edit one or more snapshots.
|
||||
type EditSnapshotsRequest struct {
|
||||
Snapshots []manifest.ID `json:"snapshots"`
|
||||
NewDescription *string `json:"description"`
|
||||
AddPins []string `json:"addPins"`
|
||||
RemovePins []string `json:"removePins"`
|
||||
}
|
||||
|
||||
// MountSnapshotRequest contains request to mount a snapshot.
|
||||
type MountSnapshotRequest struct {
|
||||
Root string `json:"root"`
|
||||
|
||||
Reference in New Issue
Block a user