Skip to content

Commit

Permalink
add UserSelector, GroupSelector, ModeAndPermissionsEditor, and clean …
Browse files Browse the repository at this point in the history
…up exports
  • Loading branch information
joshuaboud committed May 29, 2024
1 parent 7ca8042 commit b4750be
Show file tree
Hide file tree
Showing 6 changed files with 327 additions and 43 deletions.
73 changes: 73 additions & 0 deletions houston-common-ui/lib/components/GroupSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script setup lang="ts">
import {
ref,
onMounted,
defineProps,
defineModel,
defineExpose,
computed,
} from "vue";
import {
Server,
ProcessError,
getServer,
type Group,
type LocalGroup,
} from "@45drives/houston-common-lib";
import { wrapAction } from "@/composables/wrapActions";
import {
default as SelectMenu,
type SelectMenuOption,
} from "@/components/SelectMenu.vue";
import { ResultAsync } from "neverthrow";
const _ = cockpit.gettext;
const NOBODY_GID = 65534;
const ROOT_GID = 0;
const props = withDefaults(
defineProps<{
server?: ResultAsync<Server, ProcessError>;
includeSystemGroups?: boolean;
}>(),
{ server: () => getServer() }
);
const group = defineModel<Group>();
const groups = ref<LocalGroup[]>([]);
const loadGroups = wrapAction(() =>
props.server
.andThen((s) => s.getLocalGroups())
.map(
(allGroups) =>
(groups.value = props.includeSystemGroups
? allGroups
: allGroups.filter(
(g) => g.gid >= 1000 || [ROOT_GID, NOBODY_GID].includes(g.gid)
))
)
);
const groupOptions = computed<SelectMenuOption<LocalGroup>[]>(() =>
groups.value.map(
(group) =>
({
label: `${group.name} (${group.gid})`,
value: group,
hoverText: `${group.name} (GID=${group.gid})`,
}) as SelectMenuOption<LocalGroup>
)
);
onMounted(loadGroups);
defineExpose({
refresh: loadGroups,
});
</script>

<template>
<SelectMenu v-model="group" :options="groupOptions" />
</template>
135 changes: 135 additions & 0 deletions houston-common-ui/lib/components/ModeAndPermissionsEditor.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<script setup lang="ts">
import { ref, defineProps, watchEffect, defineEmits, defineExpose } from "vue";
import {
Server,
ProcessError,
getServer,
FileSystemNode,
Mode,
Ownership,
} from "@45drives/houston-common-lib";
import { reportSuccess } from "@/components/NotificationView.vue";
import { wrapActions } from "@/composables/wrapActions";
import InputLabelWrapper from "@/components/InputLabelWrapper.vue";
import { ResultAsync } from "neverthrow";
import UserSelector from "@/components/UserSelector.vue";
import GroupSelector from "@/components/GroupSelector.vue";
const _ = cockpit.gettext;
const props = withDefaults(
defineProps<{
path: string;
server?: ResultAsync<Server, ProcessError>;
}>(),
{ server: () => getServer() }
);
const emit = defineEmits<{
(e: "apply"): void;
(e: "cancel"): void;
}>();
const makeFsNode = (path: string) =>
props.server.map((s) => new FileSystemNode(s, path));
const mode = ref<Mode>();
const fetchMode = () =>
makeFsNode(props.path)
.andThen((node) => node.getMode({ superuser: "try" }))
.map((m) => (mode.value = m));
watchEffect(fetchMode);
const ownership = ref<Ownership>();
const fetchOwner = () =>
makeFsNode(props.path)
.andThen((node) => node.getOwnership({ superuser: "try" }))
.map((o) => (ownership.value = o));
watchEffect(fetchOwner);
const apply = (path: string, mode: Mode, ownership: Ownership) =>
makeFsNode(path)
.andThen((node) => node.setMode(mode, { superuser: "try" }))
.andThen((node) => node.setOwnership(ownership, { superuser: "try" }))
.map(() => {
emit("apply");
reportSuccess(
`Updated permissions of ${path}:
mode: ${mode.toString()}
owner: ${ownership.toChownString()}.`
);
});
const actions = wrapActions({
fetchMode,
fetchOwner,
apply,
});
const cancel = () => {
actions.fetchMode();
actions.fetchOwner();
emit("cancel");
};
defineExpose({
cancel,
apply: () =>
mode.value &&
ownership.value &&
actions.apply(props.path, mode.value, ownership.value),
});
</script>

<template>
<div class="space-y-content">
<div
v-if="mode"
class="inline-grid grid-cols-[2fr_1fr_1fr_1fr] gap-2 justify-items-center"
>
<label class="justify-self-start block text-sm font-medium"></label>
<label class="text-label">{{ _("Read") }}</label>
<label class="text-label">{{ _("Write") }}</label>
<label class="text-label">{{ _("Execute") }}</label>

<label class="justify-self-start text-label">{{ _("Owner") }}</label>
<input type="checkbox" class="input-checkbox" v-model="mode.owner.r" />
<input type="checkbox" class="input-checkbox" v-model="mode.owner.w" />
<input type="checkbox" class="input-checkbox" v-model="mode.owner.x" />

<label class="justify-self-start text-label">{{ _("Group") }}</label>
<input type="checkbox" class="input-checkbox" v-model="mode.group.r" />
<input type="checkbox" class="input-checkbox" v-model="mode.group.w" />
<input type="checkbox" class="input-checkbox" v-model="mode.group.x" />

<label class="justify-self-start text-label">{{ _("Other") }}</label>
<input type="checkbox" class="input-checkbox" v-model="mode.other.r" />
<input type="checkbox" class="input-checkbox" v-model="mode.other.w" />
<input type="checkbox" class="input-checkbox" v-model="mode.other.x" />

<label class="justify-self-start text-label">{{ _("Mode") }}</label>
<span class="col-span-3 font-mono font-medium whitespace-nowrap">{{
mode.toString()
}}</span>
</div>

<template v-if="ownership">
<InputLabelWrapper>
<template #label>
{{ _("Owner") }}
</template>
<UserSelector v-model="ownership.user" :server="server" />
</InputLabelWrapper>
<InputLabelWrapper>
<template #label>
{{ _("Group") }}
</template>
<GroupSelector v-model="ownership.group" :server="server" />
</InputLabelWrapper>
</template>
</div>
</template>

<style scoped>
@import "@45drives/houston-common-css/src/index.css";
</style>
73 changes: 73 additions & 0 deletions houston-common-ui/lib/components/UserSelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script setup lang="ts">
import {
ref,
onMounted,
defineProps,
defineModel,
defineExpose,
computed,
} from "vue";
import {
Server,
ProcessError,
getServer,
type LocalUser,
type User,
} from "@45drives/houston-common-lib";
import { wrapAction } from "@/composables/wrapActions";
import {
default as SelectMenu,
type SelectMenuOption,
} from "@/components/SelectMenu.vue";
import { ResultAsync } from "neverthrow";
const _ = cockpit.gettext;
const NOBODY_UID = 65534;
const ROOT_UID = 0;
const props = withDefaults(
defineProps<{
server?: ResultAsync<Server, ProcessError>;
includeSystemUsers?: boolean;
}>(),
{ server: () => getServer() }
);
const user = defineModel<User>();
const users = ref<LocalUser[]>([]);
const loadUsers = wrapAction(() =>
props.server
.andThen((s) => s.getLocalUsers())
.map(
(allUsers) =>
(users.value = props.includeSystemUsers
? allUsers
: allUsers.filter(
(u) => u.uid >= 1000 || [ROOT_UID, NOBODY_UID].includes(u.uid)
))
)
);
const userOptions = computed<SelectMenuOption<LocalUser>[]>(() =>
users.value.map(
(user) =>
({
label: `${user.login} (${user.uid})`,
value: user,
hoverText: `${user.name || user.login} (UID=${user.uid})`,
}) as SelectMenuOption<LocalUser>
)
);
onMounted(loadUsers);
defineExpose({
refresh: loadUsers,
});
</script>

<template>
<SelectMenu v-model="user" :options="userOptions" />
</template>
27 changes: 27 additions & 0 deletions houston-common-ui/lib/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export { default as HoustonHeader } from "./HoustonHeader.vue";
export { default as LoadingSpinner } from "./LoadingSpinner.vue";
export { default as NotificationView } from "./NotificationView.vue";
export * from "./NotificationView.vue";
export { default as Logo45Drives } from "./Logo45Drives.vue";
export * from "./HoustonAppContainer.vue";
export { default as HoustonAppContainer } from "./HoustonAppContainer.vue";
export { default as CardContainer } from "./CardContainer.vue";
export { default as CenteredCardColumn } from "./CenteredCardColumn.vue";
export { default as ParsedTextArea } from "./ParsedTextArea.vue";
export * from "./InputFeedback.vue";
export { default as InputFeedback } from "./InputFeedback.vue";
export * from "./InputField.vue";
export { default as InputField } from "./InputField.vue";
export { default as ToggleSwitch } from "./ToggleSwitch.vue";
export { default as ToggleSwitchGroup } from "./ToggleSwitchGroup.vue";
export { default as ToolTip } from "./ToolTip.vue";
export { default as Disclosure } from "./Disclosure.vue";
export { default as SelectMenu } from "./SelectMenu.vue";
export * from "./SelectMenu.vue";
export { default as InputLabelWrapper } from "./InputLabelWrapper.vue";
export { default as UserSelector } from "./UserSelector.vue";
export { default as GroupSelector } from "./GroupSelector.vue";
export { default as ModeAndPermissionsEditor } from "./ModeAndPermissionsEditor.vue";

export * from "./tabs";
export * from "./modals";
4 changes: 4 additions & 0 deletions houston-common-ui/lib/composables/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from "./useDarkModeState";
export * from "./useGlobalProcessingState";
export * from "./useTempObjectStaging";
export * from "./wrapActions";
58 changes: 15 additions & 43 deletions houston-common-ui/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,18 @@
export { default as HoustonHeader } from "@/components/HoustonHeader.vue";
export { default as LoadingSpinner } from "@/components/LoadingSpinner.vue";
export { default as NotificationView } from "@/components/NotificationView.vue";
export * from "@/components/NotificationView.vue";
export { default as Logo45Drives } from "@/components/Logo45Drives.vue";
export * from "@/components/HoustonAppContainer.vue";
export { default as HoustonAppContainer } from "@/components/HoustonAppContainer.vue";
export { default as CardContainer } from "@/components/CardContainer.vue";
export { default as CenteredCardColumn } from "@/components/CenteredCardColumn.vue";
export { default as ParsedTextArea } from "@/components/ParsedTextArea.vue";
export * from "@/components/InputFeedback.vue";
export { default as InputFeedback } from "@/components/InputFeedback.vue";
export * from "@/components/InputField.vue";
export { default as InputField } from "@/components/InputField.vue";
export { default as ToggleSwitch } from "@/components/ToggleSwitch.vue";
export { default as ToggleSwitchGroup } from "@/components/ToggleSwitchGroup.vue";
export { default as ToolTip } from "@/components/ToolTip.vue";
export { default as Disclosure } from "@/components/Disclosure.vue";
export { default as SelectMenu } from "@/components/SelectMenu.vue";
export * from "@/components/SelectMenu.vue";
export { default as InputLabelWrapper } from "@/components/InputLabelWrapper.vue";
export * from "@/components";
export * from "@/composables";

export * from "@/components/tabs";

export * from "@/components/modals";

export * from "@/composables/useDynamicFormGeneration";

export * from "@/composables/useDynamicFormGenerationPromise";

export * from "@/composables/useDarkModeState";
export * from "@/composables/useGlobalProcessingState";
export * from "@/composables/useTempObjectStaging";
export * from "@/composables/wrapActions";

import { reportError } from '@/components/NotificationView.vue';
import { reportError } from ".";

window.onerror = (event) => {
if (typeof event === "string") {
reportError(new Error(event));
} else {
const errorEvent = event as ErrorEvent;
reportError(new Error(`${errorEvent.filename}:${errorEvent.lineno}:${errorEvent.colno}: ${errorEvent.message}`));
}
return false;
}
if (typeof event === "string") {
reportError(new Error(event));
} else {
const errorEvent = event as ErrorEvent;
reportError(
new Error(
`${errorEvent.filename}:${errorEvent.lineno}:${errorEvent.colno}: ${errorEvent.message}`
)
);
}
return false;
};

0 comments on commit b4750be

Please sign in to comment.