Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,42 @@ func (api *api) GetApp(dbApp *db.App) (*App, error) {
return &response, nil
}

func (api *api) ListConnectionIssues(appId uint, limit uint64) ([]ConnectionIssue, error) {
if limit == 0 || limit > 20 {
limit = 10
}

dbIssues := []db.ConnectionIssue{}
err := api.db.
Where("app_id = ?", appId).
Order("created_at DESC").
Limit(int(limit)).
Find(&dbIssues).
Error
if err != nil {
logger.Logger.WithError(err).WithFields(logrus.Fields{
"app_id": appId,
}).Error("Failed to list connection issues")
return nil, err
}

issues := make([]ConnectionIssue, len(dbIssues))
for i, issue := range dbIssues {
issues[i] = ConnectionIssue{
ID: issue.ID,
AppId: issue.AppId,
RequestEventId: issue.RequestEventId,
Method: issue.Method,
Category: issue.Category,
ErrorCode: issue.ErrorCode,
ErrorMessage: issue.ErrorMessage,
CreatedAt: issue.CreatedAt,
}
}

return issues, nil
}

func (api *api) ListApps(limit uint64, offset uint64, filters ListAppsFilters, orderBy string) (*ListAppsResponse, error) {
// TODO: join dbApps and permissions
dbApps := []db.App{}
Expand Down
12 changes: 12 additions & 0 deletions api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type API interface {
DeleteApp(app *db.App) error
GetApp(app *db.App) (*App, error)
ListApps(limit uint64, offset uint64, filters ListAppsFilters, orderBy string) (*ListAppsResponse, error)
ListConnectionIssues(appId uint, limit uint64) ([]ConnectionIssue, error)
CreateLightningAddress(ctx context.Context, createLightningAddressRequest *CreateLightningAddressRequest) error
DeleteLightningAddress(ctx context.Context, appId uint) error
ListChannels(ctx context.Context) ([]Channel, error)
Expand Down Expand Up @@ -129,6 +130,17 @@ type ListAppsResponse struct {
TotalBalanceMsat *int64 `json:"totalBalanceMsat,omitempty"`
}

type ConnectionIssue struct {
ID uint `json:"id"`
AppId uint `json:"appId"`
RequestEventId uint `json:"requestEventId"`
Method string `json:"method"`
Category string `json:"category"`
ErrorCode string `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
CreatedAt time.Time `json:"createdAt"`
}

type UpdateAppRequest struct {
Name *string `json:"name"`
MaxAmount *uint64 `json:"maxAmount"` // deprecated
Expand Down
7 changes: 7 additions & 0 deletions cmd/db_migrate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var expectedTables = []string{
"app_permissions",
"request_events",
"response_events",
"connection_issues",
"transactions",
"swaps",
"user_configs",
Expand Down Expand Up @@ -151,6 +152,11 @@ func migrateDB(from, to *gorm.DB) error {
return fmt.Errorf("failed to migrate response_events: %w", err)
}

logger.Logger.Info("migrating connection_issues...")
if err := migrateTable[db.ConnectionIssue](from, tx); err != nil {
return fmt.Errorf("failed to migrate connection_issues: %w", err)
}

logger.Logger.Info("migrating transactions...")
if err := migrateTable[db.Transaction](from, tx); err != nil {
return fmt.Errorf("failed to migrate transactions: %w", err)
Expand Down Expand Up @@ -270,6 +276,7 @@ func resetSequences(db *gorm.DB) error {
{"app_permissions", "app_permissions_2_id_seq"},
{"request_events", "request_events_id_seq"},
{"response_events", "response_events_id_seq"},
{"connection_issues", "connection_issues_id_seq"},
{"transactions", "transactions_id_seq"},
{"user_configs", "user_configs_id_seq"},
}
Expand Down
38 changes: 38 additions & 0 deletions db/migrations/202605121200_connection_issues.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package migrations

import (
"text/template"

"github.com/go-gormigrate/gormigrate/v2"
"gorm.io/gorm"
)

const connectionIssuesMigration = `
CREATE TABLE connection_issues(
id {{ .AutoincrementPrimaryKey }},
app_id integer NOT NULL,
request_event_id integer NOT NULL,
method text,
category text NOT NULL,
error_code text,
error_message text,
created_at {{ .Timestamp }},
updated_at {{ .Timestamp }},
CONSTRAINT fk_connection_issues_app FOREIGN KEY (app_id) REFERENCES apps(id) ON DELETE CASCADE,
CONSTRAINT fk_connection_issues_request_event FOREIGN KEY (request_event_id) REFERENCES request_events(id) ON DELETE CASCADE
);
CREATE INDEX idx_connection_issues_app_id_created_at ON connection_issues(app_id, created_at);
CREATE INDEX idx_connection_issues_request_event_id ON connection_issues(request_event_id);
`

var connectionIssuesMigrationTmpl = template.Must(template.New("connectionIssuesMigration").Parse(connectionIssuesMigration))

var _202605121200_connection_issues = &gormigrate.Migration{
ID: "202605121200_connection_issues",
Migrate: func(tx *gorm.DB) error {
return exec(tx, connectionIssuesMigrationTmpl)
},
Rollback: func(tx *gorm.DB) error {
return nil
},
}
1 change: 1 addition & 0 deletions db/migrations/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func Migrate(gormDB *gorm.DB) error {
_202508192137_forwards,
_202509031250_transactions_updated_at_index,
_202604081200_app_last_settled_transaction,
_202605121200_connection_issues,
})

return m.Migrate()
Expand Down
14 changes: 14 additions & 0 deletions db/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@ type ResponseEvent struct {
UpdatedAt time.Time
}

type ConnectionIssue struct {
ID uint
AppId uint `validate:"required"`
App App
RequestEventId uint `validate:"required"`
RequestEvent RequestEvent
Method string
Category string
ErrorCode string
ErrorMessage string
CreatedAt time.Time
UpdatedAt time.Time
}

type Transaction struct {
ID uint
AppId *uint
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/DisconnectPeerDialogContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function DisconnectPeerDialogContent({ peer }: Props) {
await reloadPeers();
} catch (e) {
console.error(e);
toast.error("Failed to disconnect peer", {
toast.error("Peer was not disconnected", {
description: "" + e,
});
}
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/InsufficientLightningBalanceAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ export function InsufficientLightningBalanceAlert({
return (
<Alert className={className}>
<AlertTriangleIcon className="h-4 w-4" />
<AlertTitle>Maximum Spendable Balance Too Low</AlertTitle>
<AlertTitle>Not enough spendable balance</AlertTitle>
<AlertDescription>
<p>
Your payment will likely fail because your maximum spendable balance
in your lightning channels for the next payment is currently{" "}
This payment is above the wallet's current spendable balance. The most
you can send right now is{" "}
<FormattedBitcoinAmount amountMsat={maxSpendableMsat} />.
</p>
<div className="flex gap-2 mt-2 items-center justify-center">
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/components/LowReceivingCapacityAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ export default function LowReceivingCapacityAlert() {
return (
<Alert variant="warning">
<AlertTriangleIcon className="h-4 w-4" />
<AlertTitle>Low receiving capacity</AlertTitle>
<AlertTitle>You need more receiving capacity</AlertTitle>
<AlertDescription className="inline">
You likely won't be able to receive payments until you{" "}
This wallet cannot receive larger payments right now. Add receiving
capacity,{" "}
<Link className="underline" to="/wallet/send">
spend
</Link>
Expand All @@ -22,7 +23,7 @@ export default function LowReceivingCapacityAlert() {
</Link>
, or{" "}
<Link className="underline" to="/channels/incoming">
increase your receiving capacity.
open an incoming channel.
</Link>
</AlertDescription>
</Alert>
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/components/PaymentFailedAlert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ export function PaymentFailedAlert({
return (
<Alert>
<TriangleAlertIcon className="h-4 w-4" />
<AlertTitle>Payment Failed</AlertTitle>
<AlertTitle>Payment was not sent</AlertTitle>
<AlertDescription>
<p>
Try the payment again, read our payments guide, and optionally send
details about the failed payment to help improve Alby Hub.
Alby Hub could not complete this payment. No sats were sent unless you
see it in your transactions.
</p>
<div className="flex flex-wrap gap-2 mt-2">
<ExternalLinkButton
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/UnlinkAlbyAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function UnlinkAlbyAccount({
description: successMessage,
});
} catch (error) {
toast.error("Disconnect account failed", {
toast.error("Alby Account was not disconnected", {
description: (error as Error).message,
});
}
Expand Down
92 changes: 92 additions & 0 deletions frontend/src/components/connections/ConnectionIssuesCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { TriangleAlertIcon } from "lucide-react";
import { Link } from "react-router";
import {
Alert,
AlertAction,
AlertDescription,
AlertTitle,
} from "src/components/ui/alert";
import { Button } from "src/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from "src/components/ui/card";
import { getConnectionIssueCopy } from "src/lib/connectionIssues";
import { ConnectionIssue } from "src/types";

export function ConnectionIssueAlert({
appName,
issue,
onViewDetails,
showTimestamp = true,
}: {
appName: string;
issue: ConnectionIssue;
onViewDetails: () => void;
showTimestamp?: boolean;
}) {
const copy = getConnectionIssueCopy(appName, issue, onViewDetails);

return (
<Alert variant="warning">
<TriangleAlertIcon />
<AlertTitle className="line-clamp-none">{copy.title}</AlertTitle>
<AlertDescription>
<p>{copy.description}</p>
<p className="font-mono text-xs break-all">
{issue.errorCode}: {issue.errorMessage}
</p>
{showTimestamp && (
<p className="text-xs">
{new Date(issue.createdAt).toLocaleString()}
</p>
)}
</AlertDescription>
<AlertAction>
{copy.href ? (
<Button asChild size="sm" variant="secondary">
<Link to={copy.href}>{copy.action}</Link>
</Button>
) : (
<Button size="sm" variant="secondary" onClick={copy.onClick}>
{copy.action}
</Button>
)}
</AlertAction>
</Alert>
);
}

export function ConnectionIssuesCard({
appName,
issues,
onViewDetails,
}: {
appName: string;
issues: ConnectionIssue[] | undefined;
onViewDetails: () => void;
}) {
if (!issues?.length) {
return null;
}

return (
<Card>
<CardHeader>
<CardTitle>Recent Connection Issues</CardTitle>
</CardHeader>
<CardContent className="grid gap-3">
{issues.map((issue) => (
<ConnectionIssueAlert
key={issue.id}
appName={appName}
issue={issue}
onViewDetails={onViewDetails}
/>
))}
</CardContent>
</Card>
);
}
10 changes: 9 additions & 1 deletion frontend/src/hooks/useApp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import useSWR, { SWRConfiguration } from "swr";

import { App } from "src/types";
import { App, ConnectionIssue } from "src/types";
import { swrFetcher } from "src/utils/swr";

const pollConfiguration: SWRConfiguration = {
Expand All @@ -14,3 +14,11 @@ export function useApp(id: number | undefined, poll = false) {
poll ? pollConfiguration : undefined
);
}

export function useConnectionIssues(appId: number | undefined) {
return useSWR<ConnectionIssue[]>(
!!appId && `/api/v2/apps/${appId}/issues?limit=5`,
swrFetcher,
pollConfiguration
);
}
Loading
Loading