Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
faaa403
feat: backend storage
Jan 12, 2026
67a89a1
feat: backend storage
Jan 12, 2026
d8e9e2d
feat: Working on backend storage providers.
Jan 13, 2026
91f25bc
feat: Working on storage modal in database page in order to add multi…
Jan 14, 2026
dd1f791
chore: migration for the storage-backend feature
Jan 14, 2026
670f6a1
chore: working on backup api for new storage system.
Jan 15, 2026
5a0c230
chore: working on backup api for new storage system.
Jan 16, 2026
6e9568b
Merge branch 'main' into feat/storage
Jan 16, 2026
ecf8028
Merge branch 'main' into feat/storage
Jan 16, 2026
0216c40
feat: Working on UI/UX for multiple storage backends.
Jan 17, 2026
7d81820
refactor: API route for backup/restore
Jan 17, 2026
83ef0ee
feat: backup delete/restore actions.
Jan 17, 2026
424fa58
feat: backup delete/restore actions.
Jan 17, 2026
53f8279
fix: Upload with new storage backend system.
Jan 17, 2026
e63e62d
fix: Sidebar focus on some items
Jan 17, 2026
5b980fb
feat: adding system settings page. And did some refactoring.
Jan 17, 2026
bce1c5d
feat: adding storage system ping method in order to test.
Jan 17, 2026
f29444a
fix: deleteBackupCronAction with new backend storage system
Jan 18, 2026
3d69570
fix: migration storage backend and also user avatar upload.
Jan 18, 2026
8ee44ff
fix: migration errors by testing on a production instance.
Jan 18, 2026
c0499d4
fix: restoration columns.tsx is not backup storage linked, legacy
Jan 18, 2026
85044fe
chore(release): 1.2.0-rc.1
Jan 18, 2026
11900b1
Delete .idea directory
RambokDev Jan 18, 2026
3e8c0a2
Delete .vscode directory
RambokDev Jan 18, 2026
848e7a1
chore: .gitignore
Jan 18, 2026
6fa6187
Merge remote-tracking branch 'origin/main' into feat/storage
Jan 18, 2026
a6c1f7d
chore: Merging errors due to merge with main.
Jan 18, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
.idea
.vscode
# dependencies
/node_modules
/.pnp
Expand Down
8 changes: 0 additions & 8 deletions .idea/.gitignore

This file was deleted.

8 changes: 0 additions & 8 deletions .idea/modules.xml

This file was deleted.

12 changes: 0 additions & 12 deletions .idea/portabase.iml

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/vcs.xml

This file was deleted.

3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ keywords:
- web-ui
- agent
license: Apache-2.0
version: 1.1.13
date-released: "2026-01-16"
version: 1.2.0-rc.1
date-released: "2026-01-18"
40 changes: 40 additions & 0 deletions app/(customer)/dashboard/(admin)/admin/settings/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {PageParams} from "@/types/next";
import {Page, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {db} from "@/db";
import {notFound} from "next/navigation";
import {SettingsTabs} from "@/components/wrappers/dashboard/admin/settings/settings-tabs";
import {desc, isNull} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {StorageChannelWith} from "@/db/schema/12_storage-channel";

export default async function RoutePage(props: PageParams<{}>) {

const settings = await db.query.setting.findFirst({
where: (fields, {eq}) => eq(fields.name, "system"),
});

const storageChannels = await db.query.storageChannel.findMany({
with: {
organizations: true
},
where: isNull(drizzleDb.schemas.storageChannel.organizationId),
orderBy: desc(drizzleDb.schemas.storageChannel.createdAt)
}) as StorageChannelWith[]

if (!settings || !storageChannels ) {
notFound()
}

return (
<Page>
<PageHeader className="flex flex-col">
<div className="flex justify-between">
<PageTitle className="mb-3">System settings</PageTitle>
</div>
</PageHeader>
<PageContent className="flex flex-col gap-5">
<SettingsTabs storageChannels={storageChannels} settings={settings} />
</PageContent>
</Page>
);
}
17 changes: 10 additions & 7 deletions app/(customer)/dashboard/(admin)/notifications/channels/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import {PageParams} from "@/types/next";
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {Metadata} from "next";
import {
NotificationChannelsSection
} from "@/components/wrappers/dashboard/admin/notifications/channels/notification-channels-section";
import {db} from "@/db";
import {notificationChannel, NotificationChannel, NotificationChannelWith} from "@/db/schema/09_notification-channel";
import {NotifierAddEditModal} from "@/components/wrappers/dashboard/common/notifier/notifier-add-edit-modal";
import {notificationChannel, NotificationChannelWith} from "@/db/schema/09_notification-channel";
import {desc, isNull} from "drizzle-orm";
import {ChannelsSection} from "@/components/wrappers/dashboard/admin/channels/channels-section";
import {ChannelAddEditModal} from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal";
import * as drizzleDb from "@/db";

export const metadata: Metadata = {
title: "Notification Channels",
Expand All @@ -19,6 +18,7 @@ export default async function RoutePage(props: PageParams<{}>) {
with: {
organizations: true
},
where: isNull(drizzleDb.schemas.notificationChannel.organizationId),
orderBy: desc(notificationChannel.createdAt)
}) as NotificationChannelWith[]

Expand All @@ -35,11 +35,14 @@ export default async function RoutePage(props: PageParams<{}>) {
<PageHeader>
<PageTitle>Notification channels</PageTitle>
<PageActions>
<NotifierAddEditModal adminView={false}/>
{/*<NotifierAddEditModal adminView={false}/>*/}
<ChannelAddEditModal kind="notification" adminView={false}/>
</PageActions>
</PageHeader>
<PageContent>
<NotificationChannelsSection organizations={organizations} notificationChannels={notificationChannels}/>
{/*<NotificationChannelsSection organizations={organizations} notificationChannels={notificationChannels}/>*/}
<ChannelsSection kind="notification" organizations={organizations}
channels={notificationChannels}/>
</PageContent>
</Page>
);
Expand Down
50 changes: 50 additions & 0 deletions app/(customer)/dashboard/(admin)/storages/channels/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {PageParams} from "@/types/next";
import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/layout/page";
import {Metadata} from "next";
import {ChannelsSection} from "@/components/wrappers/dashboard/admin/channels/channels-section";
import {db} from "@/db";
import {desc, eq, isNull} from "drizzle-orm";
import * as drizzleDb from "@/db";
import {StorageChannelWith} from "@/db/schema/12_storage-channel";
import {ChannelAddEditModal} from "@/components/wrappers/dashboard/admin/channels/channel/channel-add-edit-modal";

export const metadata: Metadata = {
title: "Storage Channels",
};

export default async function RoutePage(props: PageParams<{}>) {

const storageChannels = await db.query.storageChannel.findMany({
with: {
organizations: true
},
where: isNull(drizzleDb.schemas.storageChannel.organizationId),
orderBy: desc(drizzleDb.schemas.storageChannel.createdAt)
}) as StorageChannelWith[]

const organizations = await db.query.organization.findMany({
where: (fields) => isNull(fields.deletedAt),
with: {
members: true,
},
});

const settings = await db.query.setting.findFirst({
where: eq(drizzleDb.schemas.setting.name, "system"),
});


return (
<Page>
<PageHeader>
<PageTitle>Storage channels</PageTitle>
<PageActions>
<ChannelAddEditModal kind={"storage"} adminView={false}/>
</PageActions>
</PageHeader>
<PageContent>
<ChannelsSection defaultStorageChannelId={settings?.defaultStorageChannelId} kind={"storage"} organizations={organizations} channels={storageChannels}/>
</PageContent>
</Page>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import {PageParams} from "@/types/next";
import {notFound, redirect} from "next/navigation";
import {Page, PageContent, PageDescription, PageTitle} from "@/features/layout/page";
import {BackupButton} from "@/components/wrappers/dashboard/backup/backup-button/backup-button";
import {DatabaseTabs} from "@/components/wrappers/dashboard/projects/database/database-tabs";
import {DatabaseKpi} from "@/components/wrappers/dashboard/projects/database/database-kpi";
import {CronButton} from "@/components/wrappers/dashboard/database/cron-button/cron-button";
import {db} from "@/db";
Expand All @@ -12,9 +11,13 @@ import {getOrganizationProjectDatabases} from "@/lib/services";
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import {RetentionPolicySheet} from "@/components/wrappers/dashboard/database/retention-policy/retention-policy-sheet";
import {capitalizeFirstLetter} from "@/utils/text";
import {AlertPolicyModal} from "@/components/wrappers/dashboard/database/alert-policy/alert-policy-modal";
import {getOrganizationChannels} from "@/db/services/notification-channel";
import {ImportModal} from "@/components/wrappers/dashboard/database/import/import-modal";
import {getOrganizationStorageChannels} from "@/db/services/storage-channel";
import {ChannelPoliciesModal} from "@/components/wrappers/dashboard/database/channels-policy/policy-modal";
import {HardDrive, Megaphone} from "lucide-react";
import {BackupModalProvider} from "@/components/wrappers/dashboard/database/backup/backup-modal-context";
import {DatabaseContent} from "@/components/wrappers/dashboard/projects/database/database-content";

export default async function RoutePage(props: PageParams<{
projectId: string;
Expand All @@ -39,7 +42,8 @@ export default async function RoutePage(props: PageParams<{
with: {
project: true,
retentionPolicy: true,
alertPolicies: true
alertPolicies: true,
storagePolicies: true
}
});

Expand All @@ -51,6 +55,11 @@ export default async function RoutePage(props: PageParams<{
where: eq(drizzleDb.schemas.backup.databaseId, dbItem.id),
with: {
restorations: true,
storages: {
with: {
storageChannel: true
}
}
},
orderBy: (b, {desc}) => [desc(b.createdAt)],
});
Expand Down Expand Up @@ -87,6 +96,10 @@ export default async function RoutePage(props: PageParams<{
const organizationChannels = await getOrganizationChannels(organization.id);
const activeOrganizationChannels = organizationChannels.filter(channel => channel.enabled);

const organizationStorageChannels = await getOrganizationStorageChannels(organization.id);
const activeOrganizationStorageChannels = organizationStorageChannels.filter(channel => channel.enabled);


const successRate = totalBackups > 0 ? (successfulBackups / totalBackups) * 100 : null;

const isMember = activeMember?.role === "member";
Expand All @@ -102,12 +115,22 @@ export default async function RoutePage(props: PageParams<{
{!isMember && (
<div className="flex items-center gap-2 md:justify-between w-full ">
<div className="flex items-center gap-2">
{/* Do not delete*/}
{/*<EditButton/>*/}
<RetentionPolicySheet database={dbItem}/>
<CronButton database={dbItem}/>
<AlertPolicyModal database={dbItem} notificationChannels={activeOrganizationChannels}
organizationId={organization.id}/>
<ChannelPoliciesModal
database={dbItem}
kind={"notification"}
icon={<Megaphone/>}
channels={activeOrganizationChannels}
organizationId={organization.id}
/>
<ChannelPoliciesModal
database={dbItem}
icon={<HardDrive/>}
kind={"storage"}
channels={activeOrganizationStorageChannels}
organizationId={organization.id}
/>
<ImportModal database={dbItem}/>
</div>
<div className="flex items-center gap-2">
Expand All @@ -126,10 +149,16 @@ export default async function RoutePage(props: PageParams<{
<PageContent className="flex flex-col w-full h-full">
<DatabaseKpi successRate={successRate} database={dbItem} availableBackups={availableBackups}
totalBackups={totalBackups}/>
<DatabaseTabs activeMember={activeMember} settings={settings} database={dbItem}
isAlreadyRestore={isAlreadyRestore}
backups={backups}
restorations={restorations}/>
<BackupModalProvider>
<DatabaseContent
activeMember={activeMember}
settings={settings}
database={dbItem}
isAlreadyRestore={isAlreadyRestore}
restorations={restorations}
backups={backups}
/>
</BackupModalProvider>
</PageContent>
</Page>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ import {eq} from "drizzle-orm";
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import * as drizzleDb from "@/db";
import {capitalizeFirstLetter} from "@/utils/text";
import {RetentionPolicySheet} from "@/components/wrappers/dashboard/database/retention-policy/retention-policy-sheet";
import {CronButton} from "@/components/wrappers/dashboard/database/cron-button/cron-button";
import {AlertPolicyModal} from "@/components/wrappers/dashboard/database/alert-policy/alert-policy-modal";
import {ImportModal} from "@/components/wrappers/dashboard/database/import/import-modal";
import {BackupButton} from "@/components/wrappers/dashboard/backup/backup-button/backup-button";


export default async function RoutePage(props: PageParams<{
projectId: string
Expand Down
16 changes: 6 additions & 10 deletions app/(customer)/dashboard/(organization)/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,15 @@ import {Page, PageActions, PageContent, PageHeader, PageTitle} from "@/features/
import {currentUser} from "@/lib/auth/current-user";
import {getActiveMember, getOrganization} from "@/lib/auth/auth";
import {notFound} from "next/navigation";
import {
DeleteOrganizationButton
} from "@/components/wrappers/dashboard/organization/delete-organization/delete-organization-button";
import {EditButtonSettings} from "@/components/wrappers/dashboard/settings/edit-button-settings/edit-button-settings";
import {Metadata} from "next";
import {OrganizationTabs} from "@/components/wrappers/dashboard/organization/tabs/organization-tabs";
import {getOrganizationChannels} from "@/db/services/notification-channel";
import {computeOrganizationPermissions} from "@/lib/acl/organization-acl";
import {capitalizeFirstLetter} from "@/utils/text";
import Link from "next/link";
import {buttonVariants} from "@/components/ui/button";
import {GearIcon} from "@radix-ui/react-icons";
import {getOrganizationStorageChannels} from "@/db/services/storage-channel";
import {DeleteOrganizationButton} from "@/components/wrappers/dashboard/organization/delete-organization-button";
import {
ButtonDeleteProject
} from "@/components/wrappers/dashboard/projects/button-delete-project/button-delete-project";
EditButtonSettings
} from "@/components/wrappers/dashboard/organization/settings/edit-button-settings/edit-button-settings";

export const metadata: Metadata = {
title: "Settings",
Expand All @@ -33,6 +27,7 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
}

const notificationChannels = await getOrganizationChannels(organization.id)
const storageChannels = await getOrganizationStorageChannels(organization.id)
const permissions = computeOrganizationPermissions(activeMember);


Expand Down Expand Up @@ -62,6 +57,7 @@ export default async function RoutePage(props: PageParams<{ slug: string }>) {
activeMember={activeMember}
organization={organization}
notificationChannels={notificationChannels}
storageChannels={storageChannels}
/>
</PageContent>
</Page>
Expand Down
1 change: 1 addition & 0 deletions app/(customer)/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {AppSidebar} from "@/components/wrappers/dashboard/common/sidebar/app-sid
import {Header} from "@/features/layout/Header";
import {currentUser} from "@/lib/auth/current-user";
import {ThemeMetaUpdater} from "@/features/browser/theme-meta-updater";
import {BackupModalProvider} from "@/components/wrappers/dashboard/database/backup/backup-modal-context";

export default async function Layout({children}: { children: ReactNode }) {
const user = await currentUser();
Expand Down
3 changes: 0 additions & 3 deletions app/api/agent/[agentId]/backup/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import fs from "node:fs";
import forge from "node-forge";
import {EventPayload} from "@/features/notifications/types";
import {dispatchNotification} from "@/features/notifications/dispatch";
import {Database, DatabaseWith} from "@/db/schema/07_database";


export async function decryptedDump(file: File, aesKeyHex: string, ivHex: string, fileExtension: string): Promise<File> {
Expand Down
Loading
Loading