Merge pull request #583 from Hornwitser/bugfixes-2

Bugfixes 2
This commit is contained in:
Hornwitser authored and GitHub committed 2024-02-07 00:03:05 +01:00
commit ffd61b961d
17 files changed
+75 -25

No files matched your search

+5
View File
@@ -6,6 +6,11 @@
- Fixed an unhandled error in Subspace Storage crashing the host.
- Fixed an unhandled rejection in disconnect logic crashing the host.
- Fixed instance screen in Web UI not updating when an instance was reassigned.
- Fixed an error thrown in the rendering of one page in the Web UI causing all pages to become inaccessible.
- Fixed Web UI not showing instance saves when the permission to list and subscribe the saves is granted but the user is not a cluster admin.
- Similarly fixed Web UI not showing the mod pack create, delete, list and host revoke token actions if the user is not a cluster admin.
- Fixed Research Sync applying progress of a previous techology level to the level on the controller when it's the active research during instance startup.
## Version 2.0.0-alpha.16
+4 -2
View File
@@ -876,9 +876,11 @@ export default class Controller {
*/
clearSavesOfInstance(instanceId: number) {
const updates = [];
const now = Date.now();
for (const [id, save] of this.saves) {
if (save.instanceId === instanceId) {
save.isDeleted = true;
save.updatedAtMs = now;
updates.push(save);
this.saves.delete(id);
}
@@ -939,9 +941,9 @@ export default class Controller {
);
} else {
instance.status = "unassigned";
instance.updatedAtMs = Date.now();
this.instanceDetailsUpdated([instance]);
}
instance.updatedAtMs = Date.now();
this.instanceDetailsUpdated([instance]);
}
/**
+4 -1
View File
@@ -178,7 +178,10 @@ export default class HostConnection extends BaseConnection {
// changed. Hosts also send status updates on assignInstance which
// for hacky reason is also used to push config changes and
// restablish status after a connection loss.
if (instance.status === request.status) {
if (
instance.status === request.status
&& instance.gamePort === request.gamePort
) {
return;
}
+9 -1
View File
@@ -19,12 +19,20 @@ export default class Role {
permissions: Type.Array(Type.String()),
});
static remapPermission(permission: string) {
// migrate: core.instance.save.list.subscribe was renamed in alpha 17
if (permission === "core.instance.save.list.subscribe") {
return "core.instance.save.subscribe";
}
return permission;
}
static fromJSON(json: Static<typeof this.jsonSchema>) {
return new this(
json.id,
json.name,
json.description,
new Set(json.permissions),
new Set(json.permissions.map(this.remapPermission)),
);
}
+1 -1
View File
@@ -361,7 +361,7 @@ export class InstanceSaveDetailsUpdatesEvent {
static type = "event" as const;
static src = ["instance", "host", "controller"] as const;
static dst = ["controller", "control"] as const;
static permission = "core.instance.save.list.subscribe" as const;
static permission = "core.instance.save.subscribe" as const;
constructor(
public updates: SaveDetails[],
+3 -3
View File
@@ -239,11 +239,11 @@ definePermission({
definePermission({
name: "core.instance.save.list",
title: "List saves",
description: "List the saves currently on the instance.",
description: "List the saves currently on instances.",
});
definePermission({
name: "core.instance.save.list.subscribe",
title: "Subscribe to save list updatess",
name: "core.instance.save.subscribe",
title: "Subscribe to save updates",
description: "Subscribe to be notifed on updates to the list of saves of instances.",
});
definePermission({
+9
View File
@@ -2,6 +2,7 @@ import { Type, Static } from "@sinclair/typebox";
import { Link, Event, EventClass, RequestHandler, WebSocketClientConnector, WebSocketBaseConnector } from "./link";
import { logger } from "./logging";
import { Address, MessageRequest, IControllerUser } from "./data";
import isDeepStrictEqual from "./is_deep_strict_equal";
export type SubscriptionRequestHandler<T> = RequestHandler<SubscriptionRequest, Event<T> | null>;
export type EventSubscriberCallback<T> = (updates: T[], synced: boolean) => void
@@ -204,6 +205,14 @@ export class EventSubscriber<
async _handle(event: EventSubscribable<T, V>) {
for (const value of event.updates) {
this.lastResponseTimeMs = Math.max(this.lastResponseTimeMs, value.updatedAtMs);
// Warn about updates which changes content but don't update updatedAtMs
const existing = this.values.get(value.id as K);
if (existing && existing.updatedAtMs === value.updatedAtMs && !isDeepStrictEqual(existing, value)) {
logger.warn(
`${this.Event.name} contains update for value with id ${value.id} that has an identical ` +
"updatedAtMs timestamp but differing content"
);
}
if (value.isDeleted) {
this.values.delete(value.id as K);
} else {
+6 -6
View File
@@ -104,16 +104,16 @@ export default function App(props: AppProps) {
}
return (
<ErrorBoundary Component={ErrorCard}>
<ConfigProvider
theme={{ algorithm: theme.darkAlgorithm }}
>
<ConfigProvider
theme={{ algorithm: theme.darkAlgorithm }}
>
<ErrorBoundary Component={ErrorCard}>
<ControlContext.Provider value={props.control}>
<BrowserRouter basename={webRoot}>
{page}
</BrowserRouter>
</ControlContext.Provider>
</ConfigProvider>
</ErrorBoundary>
</ErrorBoundary>
</ConfigProvider>
);
}
@@ -48,7 +48,7 @@ export default function HostViewPage() {
let hostButtons = <Space>
{
account.hasPermission("core.host.revoke_access")
account.hasPermission("core.host.revoke_token")
&& <Popconfirm
title={`Revoke tokens of ${host.name}?`}
placement="bottomRight"
@@ -204,7 +204,7 @@ export default function InstanceViewPage() {
<InstanceDescription host={host} instance={instance} />
{
account.hasAllPermission("core.instance.save.list", "core.instance.save.list_subscribe")
account.hasAllPermission("core.instance.save.list", "core.instance.save.subscribe")
&& <SavesList instance={instance} />
}
{
@@ -801,7 +801,7 @@ export default function ModPackViewPage() {
title={modPack.name}
extra={<Space>
<ExportButton modPack={modifiedModPack}/>
{account.hasPermission("core.mod-pack.delete") && <Popconfirm
{account.hasPermission("core.mod_pack.delete") && <Popconfirm
title="Delete mod pack and all of its settings?"
placement="bottomRight"
okText="Delete"
+2 -2
View File
@@ -186,8 +186,8 @@ export default function ModsPage() {
<SectionHeader
title="Mod Packs"
extra={<Space>
{account.hasPermission("core.mod-pack.create") && <ImportModPackButton />}
{account.hasPermission("core.mod-pack.create") && <CreateModPackButton />}
{account.hasPermission("core.mod_pack.create") && <ImportModPackButton />}
{account.hasPermission("core.mod_pack.create") && <CreateModPackButton />}
</Space>}
/>
<Table
@@ -1,4 +1,4 @@
import React, { useContext, useEffect, useState } from "react";
import React, { Fragment, useContext, useEffect, useState } from "react";
import { Route, Routes, useNavigate } from "react-router-dom";
import { Dropdown, Layout, Menu, MenuProps } from "antd";
import UserOutlined from "@ant-design/icons/UserOutlined";
@@ -115,12 +115,12 @@ export default function SiteLayout() {
{combinedPages.map(({path, sidebarPath, content}) => <Route
path={path}
key={path}
element={<>
element={<Fragment key={path}>
<SetSidebar path={sidebarPath ? sidebarPath : path} />
<ErrorBoundary Component={ErrorPage}>
{content}
</ErrorBoundary>
</>}
</Fragment>}
/>)}
<Route element={<SetSidebar path={null} />} />
</Routes>
@@ -88,7 +88,6 @@ export default function UserViewPage() {
onChange={() => setRolesDirty(true)}
loading={!roles}
mode="multiple"
showArrow={true}
filterOption={(inputValue:string, option: any): boolean => {
let role = roles.get(option.value);
return role?.name.toLowerCase().includes(inputValue.toLowerCase()) ?? false;
+13
View File
@@ -22,6 +22,12 @@ export function useAccount(): UserAccount {
};
}, [control]);
function checkPermissionExists(permission: string) {
if (!lib.permissions.has(permission)) {
throw new Error(`permission ${permission} does not exist`);
}
}
function hasPermission(permission: string): boolean {
for (let role of roles) {
if (role.permissions.includes("core.admin") || role.permissions.includes(permission)) {
@@ -38,12 +44,16 @@ export function useAccount(): UserAccount {
if (!roles) {
return null;
}
checkPermissionExists(permission);
return hasPermission(permission);
},
hasAnyPermission(...permissions: string[]): boolean | null {
if (!roles) {
return null;
}
for (let permission of permissions) {
checkPermissionExists(permission);
}
for (let permission of permissions) {
if (hasPermission(permission)) {
return true;
@@ -55,6 +65,9 @@ export function useAccount(): UserAccount {
if (!roles) {
return null;
}
for (let permission of permissions) {
checkPermissionExists(permission);
}
for (let permission of permissions) {
if (!hasPermission(permission)) {
return false;
+1 -1
View File
@@ -47,7 +47,7 @@ export const pages: PluginPage[] = [
{
path: "/mods",
sidebarName: "Mods",
permission: (account: UserAccount) => account.hasAnyPermission("core.mod.list", "core.mod-pack.list"),
permission: (account: UserAccount) => account.hasAnyPermission("core.mod.list", "core.mod_pack.list"),
content: <ModsPage />,
},
{
+12 -1
View File
@@ -137,10 +137,21 @@ function research_sync.sync_technologies(data)
for _, tech_data in pairs(game.json_to_table(data)) do
local tech = force.technologies[tech_data[nameIndex]]
if tech and tech.level <= tech_data[levelIndex] then
tech.level = math.min(tech_data[levelIndex], tech.prototype.max_level)
local new_level = math.min(tech_data[levelIndex], tech.prototype.max_level)
if new_level ~= tech.level then
-- when the level of the current research changes the
-- progress is not automatically reset.
if force.current_research == tech then
force.research_progress = 0
end
tech.level = new_level
end
local progress
if tech_data[researchedIndex] then
if force.current_research == tech then
force.research_progress = 0
end
tech.researched = true
progress = nil
elseif tech_data[progressIndex] then