mirror of
https://git.sr.ht/~ashkeel/strimertul
synced 2024-09-18 01:50:50 +00:00
feat: add backup restoring from the error dialog
This commit is contained in:
parent
4e9b2cff7d
commit
42e8c34702
11 changed files with 297 additions and 91 deletions
56
app.go
56
app.go
|
@ -6,21 +6,22 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"mime/multipart"
|
||||
nethttp "net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
|
||||
"github.com/strimertul/strimertul/docs"
|
||||
|
||||
"git.sr.ht/~hamcha/containers/sync"
|
||||
"github.com/nicklaw5/helix/v2"
|
||||
"github.com/postfinance/single"
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/strimertul/strimertul/database"
|
||||
"github.com/strimertul/strimertul/docs"
|
||||
"github.com/strimertul/strimertul/http"
|
||||
"github.com/strimertul/strimertul/loyalty"
|
||||
"github.com/strimertul/strimertul/twitch"
|
||||
|
@ -29,6 +30,7 @@ import (
|
|||
// App struct
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
lock *single.Single
|
||||
cliParams *cli.Context
|
||||
driver database.DatabaseDriver
|
||||
ready *sync.RWSync[bool]
|
||||
|
@ -52,6 +54,21 @@ func NewApp(cliParams *cli.Context) *App {
|
|||
|
||||
// startup is called when the app starts
|
||||
func (a *App) startup(ctx context.Context) {
|
||||
// Ensure only one copy of strimertul is running at all times
|
||||
var err error
|
||||
a.lock, err = single.New("strimertul")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err = a.lock.Lock(); err != nil {
|
||||
_, _ = runtime.MessageDialog(ctx, runtime.MessageDialogOptions{
|
||||
Type: runtime.ErrorDialog,
|
||||
Title: "strimertul is already running",
|
||||
Message: "Only one copy of strimertul can run at the same time, make sure to close other instances first",
|
||||
})
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
a.stop(ctx)
|
||||
|
@ -73,7 +90,6 @@ func (a *App) startup(ctx context.Context) {
|
|||
}
|
||||
|
||||
// Make KV hub
|
||||
var err error
|
||||
a.driver, err = database.GetDatabaseDriver(a.cliParams)
|
||||
if err != nil {
|
||||
a.showFatalError(err, "Error opening database")
|
||||
|
@ -137,6 +153,9 @@ func (a *App) startup(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (a *App) stop(context.Context) {
|
||||
if a.lock != nil {
|
||||
warnOnError(a.lock.Unlock(), "could not remove lock file")
|
||||
}
|
||||
if a.loyaltyManager != nil {
|
||||
warnOnError(a.loyaltyManager.Close(), "could not cleanly close loyalty manager")
|
||||
}
|
||||
|
@ -231,37 +250,6 @@ func (a *App) SendCrashReport(errorData string, info string) (string, error) {
|
|||
return string(byt), err
|
||||
}
|
||||
|
||||
type BackupInfo struct {
|
||||
Filename string `json:"filename"`
|
||||
Date int64 `json:"date"`
|
||||
}
|
||||
|
||||
func (a *App) GetBackups() (list []BackupInfo) {
|
||||
files, err := os.ReadDir(a.backupOptions.BackupDir)
|
||||
if err != nil {
|
||||
logger.Error("could not read backup directory", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := file.Info()
|
||||
if err != nil {
|
||||
logger.Error("could not get info for backup file", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
list = append(list, BackupInfo{
|
||||
Filename: file.Name(),
|
||||
Date: info.ModTime().UnixMilli(),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type VersionInfo struct {
|
||||
Release string `json:"release"`
|
||||
BuildInfo *debug.BuildInfo `json:"build"`
|
||||
|
|
60
backup.go
60
backup.go
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
|
@ -62,3 +63,62 @@ func BackupTask(driver database.DatabaseDriver, options database.BackupOptions)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
type BackupInfo struct {
|
||||
Filename string `json:"filename"`
|
||||
Date int64 `json:"date"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (a *App) GetBackups() (list []BackupInfo) {
|
||||
files, err := os.ReadDir(a.backupOptions.BackupDir)
|
||||
if err != nil {
|
||||
logger.Error("could not read backup directory", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
info, err := file.Info()
|
||||
if err != nil {
|
||||
logger.Error("could not get info for backup file", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
list = append(list, BackupInfo{
|
||||
Filename: file.Name(),
|
||||
Date: info.ModTime().UnixMilli(),
|
||||
Size: info.Size(),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *App) RestoreBackup(backupName string) error {
|
||||
path := filepath.Join(a.backupOptions.BackupDir, backupName)
|
||||
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open import file for reading: %w", err)
|
||||
}
|
||||
defer utils.Close(file, logger)
|
||||
inStream := file
|
||||
|
||||
if a.driver == nil {
|
||||
a.driver, err = database.GetDatabaseDriver(a.cliParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open database: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = a.driver.Restore(inStream)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not restore database: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("restored database from backup")
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -55,7 +55,11 @@ func GetDatabaseDriver(ctx *cli.Context) (DatabaseDriver, error) {
|
|||
case "badger":
|
||||
return nil, cli.Exit("Badger is not supported anymore as a database driver", 64)
|
||||
case "pebble":
|
||||
return NewPebble(dbDirectory, logger)
|
||||
db, err := NewPebble(dbDirectory, logger)
|
||||
if err != nil {
|
||||
return nil, cli.Exit(err.Error(), 64)
|
||||
}
|
||||
return db, nil
|
||||
default:
|
||||
return nil, cli.Exit(fmt.Sprintf("Unknown database driver: %s", name), 64)
|
||||
}
|
||||
|
|
|
@ -363,6 +363,19 @@
|
|||
"transparency-user": "The additional info below, if any was provided",
|
||||
"error-message": "The crash report could not be submitted because of a remote error: {{error}}",
|
||||
"post-report": "The error was successfully reported and has been assigned the following code: <m>{{code}}</m> If you haven't provided an email and want to follow up on this, use that code when opening an issue or reaching out."
|
||||
},
|
||||
"recovery": {
|
||||
"restore-error": "The database could not be restored because of the following error: {{error}}",
|
||||
"title": "Recovery options",
|
||||
"text-head": "These action will irreversibly modify your database, please make sure your database is corrupted in the first place before proceeding.",
|
||||
"restore-head": "Restore from backup",
|
||||
"restore-desc-1": "Restore a previously backed up database. This will overwrite your current database with the saved copy. Check below for the list of saved copies.",
|
||||
"restore-button": "Restore",
|
||||
"restore-confirm-title": "Confirm database restore",
|
||||
"restore-confirm-body": "Restoring this backup will overwrite your current database, this operation is irreversible.",
|
||||
"restore-failed": "Restore failed",
|
||||
"restore-succeeded-title": "Database restored",
|
||||
"restore-succeeded-body": "The database was restored from the chosen backup, please close and re-open {{APPNAME}}."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -413,7 +413,22 @@
|
|||
"transparency-files": "I contenuti di <m>{{A}}</m> e <m>{{B}}</m>",
|
||||
"transparency-info": "Informazioni sull'errore specifico che ha causato il crash",
|
||||
"transparency-line": "Facendo clic su \"Invia segnalazione\", verranno raccolte e inviate i seguenti dati:",
|
||||
"transparency-user": "Le informazioni aggiuntive di seguito, se fornite"
|
||||
"transparency-user": "Le informazioni aggiuntive di seguito, se fornite",
|
||||
"error-message": "Non è stato possibile inviare la segnalazione di errore a causa di un errore remoto: {{error}}",
|
||||
"post-report": "L'errore è stato segnalato con successo ed è stato assegnato il seguente codice: <m>{{code}}</m> Se non hai fornito un'e-mail e vuoi contattarci in merito, usa quel codice quando apri una segnalazione o altro contatto."
|
||||
},
|
||||
"recovery": {
|
||||
"restore-button": "Ripristina",
|
||||
"restore-confirm-body": "Il ripristino di questo backup sovrascriverà il database attuale, questa operazione è irreversibile.",
|
||||
"restore-confirm-title": "Conferma ripristino del database",
|
||||
"restore-desc-1": "Ripristina il database utilizzando un backup creato precedentemente. \nQuesto sovrascriverà il database attuale con la copia salvata. \nQui di seguito è la lista di tutti i backup attualmente salvati.",
|
||||
"restore-error": "Impossibile ripristinare il database a causa del seguente errore: {{error}}",
|
||||
"restore-failed": "Ripristino non riuscito",
|
||||
"restore-head": "Ripristina dal backup",
|
||||
"text-head": "Queste azioni modificheranno irreversibilmente il database, assicurati che il database sia effettivamente danneggiato prima di procedere.",
|
||||
"title": "Opzioni di ripristino",
|
||||
"restore-succeeded-title": "Database ripristinato",
|
||||
"restore-succeeded-body": "Il database è stato ripristinato dal backup scelto, chiudi e riapri {{APPNAME}}."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -2,11 +2,12 @@ import { CheckIcon } from '@radix-ui/react-icons';
|
|||
import {
|
||||
GetBackups,
|
||||
GetLastLogs,
|
||||
RestoreBackup,
|
||||
SendCrashReport,
|
||||
} from '@wailsapp/go/main/App';
|
||||
import type { main } from '@wailsapp/go/models';
|
||||
import { EventsOff, EventsOn } from '@wailsapp/runtime';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { languages } from '~/locale/languages';
|
||||
import { ProcessedLogEntry, processEntry } from '~/store/logging/reducer';
|
||||
|
@ -33,7 +34,7 @@ import {
|
|||
TextBlock,
|
||||
} from '~/ui/theme';
|
||||
import AlertContent from './components/AlertContent';
|
||||
import { Alert, AlertDescription } from './theme/alert';
|
||||
import { Alert, AlertDescription, AlertTrigger } from './theme/alert';
|
||||
|
||||
const Container = styled('div', {
|
||||
position: 'relative',
|
||||
|
@ -99,26 +100,34 @@ const LanguageItem = styled(MultiToggleItem, {
|
|||
});
|
||||
|
||||
const BackupItem = styled('article', {
|
||||
marginBottom: '0.4rem',
|
||||
backgroundColor: '$gray2',
|
||||
margin: '0.5rem 0',
|
||||
padding: '0.3rem 0.5rem',
|
||||
borderLeft: '5px solid $teal8',
|
||||
padding: '0.3rem 1rem 0.3rem 0.5rem',
|
||||
borderRadius: '0.25rem',
|
||||
borderBottom: '1px solid $gray4',
|
||||
borderBottom: '1px solid $gray5',
|
||||
transition: 'all 50ms',
|
||||
display: 'flex',
|
||||
'&:nth-child(odd)': {
|
||||
backgroundColor: '$gray3',
|
||||
},
|
||||
gap: '0.5rem',
|
||||
});
|
||||
|
||||
const BackupDate = styled('div', {
|
||||
flex: '1',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flex: '1',
|
||||
gap: '0.5rem',
|
||||
alignItems: 'baseline',
|
||||
fontVariantNumeric: 'tabular-nums',
|
||||
});
|
||||
const BackupSize = styled('div', {
|
||||
color: '$gray10',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
});
|
||||
const BackupActions = styled('div', {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
gap: '0.25rem',
|
||||
});
|
||||
|
||||
|
@ -127,54 +136,160 @@ interface RecoveryDialogProps {
|
|||
onOpenChange: (state: boolean) => void;
|
||||
}
|
||||
|
||||
// Returns a human-readable version of a byte size
|
||||
function hrsize(bytes: number): string {
|
||||
const units = ['B', 'KiB', 'MiB', 'GiB'];
|
||||
let fractBytes = bytes;
|
||||
while (fractBytes >= 1024) {
|
||||
fractBytes /= 1024;
|
||||
units.shift();
|
||||
}
|
||||
return `${fractBytes.toFixed(2)} ${units[0]}`;
|
||||
}
|
||||
|
||||
function RecoveryDialog({ open, onOpenChange }: RecoveryDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [backups, setBackups] = useState<main.BackupInfo[]>([]);
|
||||
const [restoreError, setRestoreError] = useState<string | null>(null);
|
||||
const [restored, setRestored] = useState<'idle' | 'in-progress' | 'done'>(
|
||||
'idle',
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
GetBackups().then((backupList) => {
|
||||
void GetBackups().then((backupList) => {
|
||||
setBackups(backupList);
|
||||
console.log(backupList);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const restore = async (filename: string) => {
|
||||
setRestored('in-progress');
|
||||
try {
|
||||
await RestoreBackup(filename);
|
||||
setRestoreError(null);
|
||||
} catch (err) {
|
||||
setRestoreError(err as string);
|
||||
}
|
||||
setRestored('done');
|
||||
};
|
||||
|
||||
if (restored === 'done' && restoreError == null) {
|
||||
return (
|
||||
<Alert
|
||||
defaultOpen={true}
|
||||
open={open}
|
||||
onOpenChange={(state) => {
|
||||
if (onOpenChange) onOpenChange(state);
|
||||
setRestored('idle');
|
||||
}}
|
||||
>
|
||||
<AlertContent
|
||||
variation="default"
|
||||
title={t('pages.crash.recovery.restore-succeeded-title')}
|
||||
description={t('pages.crash.recovery.restore-succeeded-body')}
|
||||
actionText={t('form-actions.ok')}
|
||||
onAction={() => {
|
||||
if (onOpenChange) onOpenChange(false);
|
||||
setRestored('idle');
|
||||
}}
|
||||
/>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(state) => {
|
||||
if (onOpenChange) onOpenChange(state);
|
||||
}}
|
||||
>
|
||||
<DialogContent title={'Recovery options'} closeButton={true}>
|
||||
<TextBlock>
|
||||
These action will irreversibly modify your database, please make sure
|
||||
your database is corrupted in the first place before proceeding.
|
||||
</TextBlock>
|
||||
<SectionHeader>Restore from backup</SectionHeader>
|
||||
<TextBlock>
|
||||
Restore a previously backed up database. This will overwrite your
|
||||
current database with the saved copy. Check below for the list of
|
||||
saved copies.
|
||||
</TextBlock>
|
||||
<Scrollbar
|
||||
vertical={true}
|
||||
viewport={{ maxHeight: 'calc(100vh - 450px)', minHeight: '100px' }}
|
||||
<>
|
||||
<Alert
|
||||
defaultOpen={false}
|
||||
open={!!restoreError}
|
||||
onOpenChange={(val: boolean) => {
|
||||
if (!val) setRestoreError(null);
|
||||
}}
|
||||
>
|
||||
<AlertContent
|
||||
variation="danger"
|
||||
title={t('pages.crash.recovery.restore-failed')}
|
||||
description={t('pages.crash.recovery.restore-error', {
|
||||
error: restoreError ?? 'unknown error',
|
||||
})}
|
||||
actionText={t('form-actions.ok')}
|
||||
onAction={() => {
|
||||
setRestoreError(null);
|
||||
}}
|
||||
/>
|
||||
</Alert>
|
||||
<Dialog
|
||||
open={open}
|
||||
onOpenChange={(state) => {
|
||||
if (onOpenChange) onOpenChange(state);
|
||||
}}
|
||||
>
|
||||
<DialogContent
|
||||
title={t('pages.crash.recovery.title')}
|
||||
closeButton={true}
|
||||
>
|
||||
{backups
|
||||
.sort((a, b) => b.date - a.date)
|
||||
.map((backup) => (
|
||||
<BackupItem key={backup.filename}>
|
||||
<BackupDate title={backup.filename}>
|
||||
{new Date(backup.date).toLocaleString()}
|
||||
</BackupDate>
|
||||
<BackupActions>
|
||||
<Button size="small">Restore</Button>
|
||||
</BackupActions>
|
||||
</BackupItem>
|
||||
))}
|
||||
</Scrollbar>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<TextBlock>{t('pages.crash.recovery.text-head')}</TextBlock>
|
||||
<SectionHeader>
|
||||
{t('pages.crash.recovery.restore-head')}
|
||||
</SectionHeader>
|
||||
<TextBlock>{t('pages.crash.recovery.restore-desc-1')}</TextBlock>
|
||||
<Scrollbar
|
||||
vertical={true}
|
||||
viewport={{ maxHeight: 'calc(100vh - 450px)', minHeight: '100px' }}
|
||||
>
|
||||
{backups
|
||||
.sort((a, b) => b.date - a.date)
|
||||
.map((backup) => {
|
||||
const date = new Date(backup.date);
|
||||
|
||||
return (
|
||||
<BackupItem key={backup.filename}>
|
||||
<BackupDate title={backup.filename}>
|
||||
{date.toLocaleDateString([], {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: '2-digit',
|
||||
})}
|
||||
{' - '}
|
||||
{date.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})}
|
||||
<BackupSize>{hrsize(backup.size)}</BackupSize>
|
||||
</BackupDate>
|
||||
<BackupActions>
|
||||
<Alert>
|
||||
<AlertTrigger asChild>
|
||||
<Button
|
||||
size="small"
|
||||
disabled={restored === 'in-progress'}
|
||||
>
|
||||
{t('pages.crash.recovery.restore-button')}
|
||||
</Button>
|
||||
</AlertTrigger>
|
||||
<AlertContent
|
||||
variation="danger"
|
||||
title={t(
|
||||
'pages.crash.recovery.restore-confirm-title',
|
||||
)}
|
||||
description={t(
|
||||
'pages.crash.recovery.restore-confirm-body',
|
||||
)}
|
||||
actionText={t('pages.crash.recovery.restore-button')}
|
||||
actionButtonProps={{ variation: 'danger' }}
|
||||
showCancel={true}
|
||||
onAction={() => {
|
||||
void restore(backup.filename);
|
||||
}}
|
||||
/>
|
||||
</Alert>
|
||||
</BackupActions>
|
||||
</BackupItem>
|
||||
);
|
||||
})}
|
||||
</Scrollbar>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -191,7 +306,7 @@ function ReportDialog({ open, onOpenChange, errorData }: ReportDialogProps) {
|
|||
const [contactInfo, setContactInfo] = useState('');
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [code, setCode] = useState('');
|
||||
const [submissionError, setSubmissionError] = useState<Error>(null);
|
||||
const [submissionError, setSubmissionError] = useState<string | null>(null);
|
||||
|
||||
const waiting = submitted && code.length < 1;
|
||||
|
||||
|
@ -244,7 +359,7 @@ function ReportDialog({ open, onOpenChange, errorData }: ReportDialogProps) {
|
|||
<AlertContent
|
||||
variation="danger"
|
||||
description={t('pages.crash.report.error-message', {
|
||||
error: submissionError?.message ?? 'unknown server error',
|
||||
error: submissionError,
|
||||
})}
|
||||
actionText={t('form-actions.ok')}
|
||||
onAction={() => {
|
||||
|
@ -276,7 +391,7 @@ function ReportDialog({ open, onOpenChange, errorData }: ReportDialogProps) {
|
|||
setCode(submissionCode);
|
||||
})
|
||||
.catch((err) => {
|
||||
setSubmissionError(err as Error);
|
||||
setSubmissionError(err as string);
|
||||
});
|
||||
setSubmitted(true);
|
||||
}}
|
||||
|
|
2
frontend/wailsjs/go/main/App.d.ts
vendored
2
frontend/wailsjs/go/main/App.d.ts
vendored
|
@ -24,4 +24,6 @@ export function IsFatalError():Promise<boolean>;
|
|||
|
||||
export function IsServerReady():Promise<boolean>;
|
||||
|
||||
export function RestoreBackup(arg1:string):Promise<void>;
|
||||
|
||||
export function SendCrashReport(arg1:string,arg2:string):Promise<string>;
|
||||
|
|
|
@ -42,6 +42,10 @@ export function IsServerReady() {
|
|||
return window['go']['main']['App']['IsServerReady']();
|
||||
}
|
||||
|
||||
export function RestoreBackup(arg1) {
|
||||
return window['go']['main']['App']['RestoreBackup'](arg1);
|
||||
}
|
||||
|
||||
export function SendCrashReport(arg1, arg2) {
|
||||
return window['go']['main']['App']['SendCrashReport'](arg1, arg2);
|
||||
}
|
||||
|
|
|
@ -59,6 +59,7 @@ export namespace main {
|
|||
export class BackupInfo {
|
||||
filename: string;
|
||||
date: number;
|
||||
size: number;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new BackupInfo(source);
|
||||
|
@ -68,6 +69,7 @@ export namespace main {
|
|||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.filename = source["filename"];
|
||||
this.date = source["date"];
|
||||
this.size = source["size"];
|
||||
}
|
||||
}
|
||||
export class LogEntry {
|
||||
|
|
5
go.mod
5
go.mod
|
@ -12,6 +12,7 @@ require (
|
|||
github.com/hashicorp/golang-lru/v2 v2.0.2
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/nicklaw5/helix/v2 v2.22.0
|
||||
github.com/postfinance/single v0.0.2
|
||||
github.com/strimertul/kilovolt/v9 v9.1.0
|
||||
github.com/strimertul/kv-pebble v1.2.1
|
||||
github.com/urfave/cli/v2 v2.25.1
|
||||
|
@ -81,7 +82,7 @@ require (
|
|||
golang.org/x/crypto v0.5.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230206171751-46f607a40771 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
golang.org/x/sys v0.7.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
)
|
||||
|
|
10
go.sum
10
go.sum
|
@ -249,6 +249,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/postfinance/single v0.0.2 h1:YLjLxxeGDnsW93oK4CxxOFSVOOiBi1OyoK4ZTl5biJw=
|
||||
github.com/postfinance/single v0.0.2/go.mod h1:OYWUsdMIZK9eQyZYpAsMHN+j6+jXJ6RFUNqIWH0oC5U=
|
||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
|
@ -437,8 +439,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
|
@ -449,8 +451,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
Loading…
Reference in a new issue