mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-08-25 10:26:35 +00:00
fix test failures etc
This commit is contained in:
@@ -280,28 +280,27 @@ func (c *syncSessionHandlerClient) OnConnectionEstablished(ctx context.Context,
|
||||
c.mgr.peerStateManager.SetPeerState(peer.Keyid, peerState)
|
||||
|
||||
// Clear the pairing secret from the known host entry now that pairing has succeeded.
|
||||
if c.syncConfigSnapshot.config.GetMultihost() != nil {
|
||||
for _, knownHost := range c.syncConfigSnapshot.config.GetMultihost().GetKnownHosts() {
|
||||
if knownHost.GetKeyid() == peer.GetKeyid() && knownHost.GetInitialPairingSecret() != "" {
|
||||
cfg, err := c.mgr.configMgr.Get()
|
||||
if err != nil {
|
||||
c.l.Sugar().Warnf("failed to get config to clear pairing secret: %v", err)
|
||||
break
|
||||
}
|
||||
cfg = proto.Clone(cfg).(*v1.Config)
|
||||
for _, kh := range cfg.GetMultihost().GetKnownHosts() {
|
||||
if kh.GetKeyid() == peer.GetKeyid() {
|
||||
kh.InitialPairingSecret = ""
|
||||
break
|
||||
}
|
||||
}
|
||||
cfg.Modno++
|
||||
if err := c.mgr.configMgr.Update(cfg); err != nil {
|
||||
c.l.Sugar().Warnf("failed to clear pairing secret after successful pairing: %v", err)
|
||||
} else {
|
||||
c.l.Sugar().Infof("cleared pairing secret for peer %q after successful connection", peer.InstanceId)
|
||||
}
|
||||
break
|
||||
knownHosts := c.syncConfigSnapshot.config.GetMultihost().GetKnownHosts()
|
||||
khIdx := slices.IndexFunc(knownHosts, func(kh *v1.Multihost_Peer) bool {
|
||||
return kh.GetKeyid() == peer.GetKeyid()
|
||||
})
|
||||
if khIdx >= 0 && knownHosts[khIdx].GetInitialPairingSecret() != "" {
|
||||
cfg, err := c.mgr.configMgr.Get()
|
||||
if err != nil {
|
||||
c.l.Sugar().Warnf("failed to get config to clear pairing secret: %v", err)
|
||||
} else {
|
||||
cfg = proto.Clone(cfg).(*v1.Config)
|
||||
liveIdx := slices.IndexFunc(cfg.GetMultihost().GetKnownHosts(), func(kh *v1.Multihost_Peer) bool {
|
||||
return kh.GetKeyid() == peer.GetKeyid()
|
||||
})
|
||||
if liveIdx >= 0 {
|
||||
cfg.GetMultihost().GetKnownHosts()[liveIdx].InitialPairingSecret = ""
|
||||
}
|
||||
cfg.Modno++
|
||||
if err := c.mgr.configMgr.Update(cfg); err != nil {
|
||||
c.l.Sugar().Warnf("failed to clear pairing secret after successful pairing: %v", err)
|
||||
} else {
|
||||
c.l.Sugar().Infof("cleared pairing secret for peer %q after successful connection", peer.InstanceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -517,6 +516,14 @@ func (c *syncSessionHandlerClient) HandleSetConfig(ctx context.Context, stream *
|
||||
if idx >= 0 {
|
||||
latestConfig.Repos[idx] = repo
|
||||
} else {
|
||||
// Check for conflicts with existing local repos by ID or URI
|
||||
conflictIdx := slices.IndexFunc(latestConfig.Repos, func(r *v1.Repo) bool {
|
||||
return r.Id == repo.Id || r.Uri == repo.Uri
|
||||
})
|
||||
if conflictIdx >= 0 {
|
||||
c.l.Sugar().Warnf("received shared repo %q (guid %s) conflicts with existing local repo %q (guid %s), skipping", repo.Id, repo.Guid, latestConfig.Repos[conflictIdx].Id, latestConfig.Repos[conflictIdx].Guid)
|
||||
continue
|
||||
}
|
||||
latestConfig.Repos = append(latestConfig.Repos, repo)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -520,9 +520,10 @@ func (h *syncSessionHandlerServer) HandleOperationManifest(ctx context.Context,
|
||||
}
|
||||
}
|
||||
|
||||
// Find ops we need (new or changed modno)
|
||||
// Find ops we need (new or changed modno), preserving manifest order
|
||||
var needIDs []int64
|
||||
for id, modno := range remoteSet {
|
||||
for i, id := range item.GetOpIds() {
|
||||
modno := item.GetModnos()[i]
|
||||
local, exists := localState[id]
|
||||
if !exists || local.modno != modno {
|
||||
needIDs = append(needIDs, id)
|
||||
|
||||
+180
-107
@@ -13,6 +13,7 @@ import {
|
||||
FiEdit2,
|
||||
FiMenu,
|
||||
FiHome,
|
||||
FiChevronRight,
|
||||
} from "react-icons/fi";
|
||||
|
||||
import {
|
||||
@@ -256,6 +257,175 @@ const PlanViewContainer = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const PeerNavItem = ({
|
||||
icon,
|
||||
typeLabel,
|
||||
name,
|
||||
active,
|
||||
onClick,
|
||||
onEdit,
|
||||
}: {
|
||||
icon: React.ReactNode;
|
||||
typeLabel: string;
|
||||
name: string;
|
||||
active: boolean;
|
||||
onClick: () => void;
|
||||
onEdit?: (e: React.MouseEvent) => void;
|
||||
}) => (
|
||||
<Flex
|
||||
align="center"
|
||||
pl={14}
|
||||
pr={2}
|
||||
py={1}
|
||||
bg={active ? "bg.emphasized" : undefined}
|
||||
_hover={{ bg: "bg.muted" }}
|
||||
cursor="pointer"
|
||||
className="group"
|
||||
onClick={onClick}
|
||||
>
|
||||
<Box flexShrink={0} mr={2}>
|
||||
{icon}
|
||||
</Box>
|
||||
<Text color="fg.muted" fontSize="xs" flexShrink={0} mr={1}>
|
||||
{typeLabel}
|
||||
</Text>
|
||||
<Text fontSize="sm" flex="1" wordBreak="break-word">
|
||||
{name}
|
||||
</Text>
|
||||
{onEdit && (
|
||||
<Box
|
||||
opacity={0}
|
||||
_groupHover={{ opacity: 1 }}
|
||||
transition="opacity 0.2s"
|
||||
>
|
||||
<IconButton
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onEdit(e);
|
||||
}}
|
||||
>
|
||||
<FiEdit2 />
|
||||
</IconButton>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
const PeerInstanceSection = ({
|
||||
peerState,
|
||||
sel,
|
||||
remoteConfig,
|
||||
isActive,
|
||||
handleNav,
|
||||
handleRemoteRepoEdit,
|
||||
handleRemotePlanEdit,
|
||||
}: {
|
||||
peerState: PeerState;
|
||||
sel: OpSelector;
|
||||
remoteConfig: PeerState["remoteConfig"];
|
||||
isActive: (path: string) => boolean;
|
||||
handleNav: (path: string) => void;
|
||||
handleRemoteRepoEdit: (repo: Repo) => void;
|
||||
handleRemotePlanEdit: (plan: Plan) => void;
|
||||
}) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<Box mb={2}>
|
||||
<Flex
|
||||
align="center"
|
||||
pl={9}
|
||||
pr={2}
|
||||
py={1}
|
||||
cursor="pointer"
|
||||
_hover={{ bg: "bg.muted" }}
|
||||
onClick={() => setExpanded((prev) => !prev)}
|
||||
>
|
||||
<Box
|
||||
transform={expanded ? "rotate(90deg)" : undefined}
|
||||
transition="transform 0.2s"
|
||||
display="inline-flex"
|
||||
alignItems="center"
|
||||
mr={2}
|
||||
flexShrink={0}
|
||||
>
|
||||
<FiChevronRight size={14} />
|
||||
</Box>
|
||||
<Box flexShrink={0} mr={2}>
|
||||
<IconForResource selector={sel} />
|
||||
</Box>
|
||||
<Text fontWeight="bold" fontSize="sm">
|
||||
{peerState.peerInstanceId}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
{expanded && (
|
||||
<>
|
||||
{peerState.knownRepos.map((repo: RepoMetadata) => {
|
||||
const repoPath = `/peer/${peerState.peerInstanceId}/repo/${repo.id}`;
|
||||
const editableRepo = remoteConfig?.repos?.find(
|
||||
(r: Repo) => r.guid === repo.guid,
|
||||
);
|
||||
return (
|
||||
<PeerNavItem
|
||||
key={repo.guid}
|
||||
icon={
|
||||
<IconForResource
|
||||
selector={create(OpSelectorSchema, {
|
||||
originalInstanceKeyid: peerState.peerKeyid,
|
||||
repoGuid: repo.guid,
|
||||
})}
|
||||
/>
|
||||
}
|
||||
typeLabel="repo"
|
||||
name={repo.id}
|
||||
active={isActive(repoPath)}
|
||||
onClick={() => handleNav(repoPath)}
|
||||
onEdit={
|
||||
editableRepo
|
||||
? () => handleRemoteRepoEdit(editableRepo)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
||||
{peerState.knownPlans.map((planMeta: PlanMetadata) => {
|
||||
const planPath = `/peer/${peerState.peerInstanceId}/plan/${planMeta.id}`;
|
||||
const editablePlan = remoteConfig?.plans?.find(
|
||||
(p: Plan) => p.id === planMeta.id,
|
||||
);
|
||||
return (
|
||||
<PeerNavItem
|
||||
key={planMeta.id}
|
||||
icon={
|
||||
<IconForResource
|
||||
selector={create(OpSelectorSchema, {
|
||||
originalInstanceKeyid: peerState.peerKeyid,
|
||||
planId: planMeta.id,
|
||||
})}
|
||||
/>
|
||||
}
|
||||
typeLabel="plan"
|
||||
name={planMeta.id}
|
||||
active={isActive(planPath)}
|
||||
onClick={() => handleNav(planPath)}
|
||||
onEdit={
|
||||
editablePlan
|
||||
? () => handleRemotePlanEdit(editablePlan)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const SidebarPlanItem = React.memo(
|
||||
({
|
||||
plan,
|
||||
@@ -640,113 +810,16 @@ const SidebarContent = ({ onClose }: { onClose?: () => void }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box key={peerState.peerKeyid} mb={2}>
|
||||
<Flex align="center" pl={9} pr={2} py={1}>
|
||||
<Box flexShrink={0} mr={2}>
|
||||
<IconForResource selector={sel} />
|
||||
</Box>
|
||||
<Text fontWeight="bold" fontSize="sm">
|
||||
{peerState.peerInstanceId}
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
{/* Nested Repos for Peer — listed from knownRepos (READ_OPERATIONS), edit from remoteConfig (READ_CONFIG) */}
|
||||
{peerState.knownRepos.map((repo: RepoMetadata) => {
|
||||
const repoPath = `/peer/${peerState.peerInstanceId}/repo/${repo.id}`;
|
||||
const active = isActive(repoPath);
|
||||
const editableRepo = remoteConfig?.repos?.find((r: Repo) => r.guid === repo.guid);
|
||||
return (
|
||||
<Flex
|
||||
key={repo.guid}
|
||||
align="center"
|
||||
pl={12}
|
||||
pr={2}
|
||||
py={1}
|
||||
bg={active ? "bg.emphasized" : undefined}
|
||||
_hover={{ bg: "bg.muted" }}
|
||||
cursor="pointer"
|
||||
className="group"
|
||||
onClick={() => handleNav(repoPath)}
|
||||
>
|
||||
<Box flexShrink={0} mr={2}>
|
||||
<IconForResource
|
||||
selector={create(OpSelectorSchema, {
|
||||
originalInstanceKeyid: peerState.peerKeyid,
|
||||
repoGuid: repo.guid,
|
||||
})}
|
||||
/>
|
||||
</Box>
|
||||
<Text fontSize="sm" flex="1" wordBreak="break-word">
|
||||
{repo.id}
|
||||
</Text>
|
||||
{editableRepo && (
|
||||
<Box
|
||||
opacity={0}
|
||||
_groupHover={{ opacity: 1 }}
|
||||
transition="opacity 0.2s"
|
||||
>
|
||||
<IconButton
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
handleRemoteRepoEdit(editableRepo);
|
||||
}}
|
||||
>
|
||||
<FiEdit2 />
|
||||
</IconButton>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Nested Plans for Peer — listed from knownPlans, edit from remoteConfig */}
|
||||
{peerState.knownPlans.map((planMeta: PlanMetadata) => {
|
||||
const planPath = `/peer/${peerState.peerInstanceId}/plan/${planMeta.id}`;
|
||||
const active = isActive(planPath);
|
||||
const editablePlan = remoteConfig?.plans?.find((p: Plan) => p.id === planMeta.id);
|
||||
return (
|
||||
<Flex
|
||||
key={planMeta.id}
|
||||
align="center"
|
||||
pl={12}
|
||||
pr={2}
|
||||
py={1}
|
||||
bg={active ? "bg.emphasized" : undefined}
|
||||
_hover={{ bg: "bg.muted" }}
|
||||
cursor="pointer"
|
||||
className="group"
|
||||
onClick={() => handleNav(planPath)}
|
||||
>
|
||||
<Box flexShrink={0} mr={2}>
|
||||
<FiCalendar />
|
||||
</Box>
|
||||
<Text fontSize="sm" flex="1" wordBreak="break-word">
|
||||
{planMeta.id}
|
||||
</Text>
|
||||
{editablePlan && (
|
||||
<Box
|
||||
opacity={0}
|
||||
_groupHover={{ opacity: 1 }}
|
||||
transition="opacity 0.2s"
|
||||
>
|
||||
<IconButton
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
handleRemotePlanEdit(editablePlan);
|
||||
}}
|
||||
>
|
||||
<FiEdit2 />
|
||||
</IconButton>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
<PeerInstanceSection
|
||||
key={peerState.peerKeyid}
|
||||
peerState={peerState}
|
||||
sel={sel}
|
||||
remoteConfig={remoteConfig}
|
||||
isActive={isActive}
|
||||
handleNav={handleNav}
|
||||
handleRemoteRepoEdit={handleRemoteRepoEdit}
|
||||
handleRemotePlanEdit={handleRemotePlanEdit}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</AccordionItemContent>
|
||||
|
||||
Reference in New Issue
Block a user