|
|
@@ -20,6 +20,11 @@ import { useQueryClient } from "@tanstack/react-query";
|
|
|
import Settings from "./tabs/Settings";
|
|
|
import BuildSettings from "./tabs/BuildSettings";
|
|
|
import Environment from "./tabs/Environment";
|
|
|
+import AnimateHeight from "react-animate-height";
|
|
|
+import Banner from "components/porter/Banner";
|
|
|
+import Button from "components/porter/Button";
|
|
|
+import Icon from "components/porter/Icon";
|
|
|
+import save from "assets/save-01.svg";
|
|
|
|
|
|
// commented out tabs are not yet implemented
|
|
|
// will be included as support is available based on data from app revisions rather than helm releases
|
|
|
@@ -101,9 +106,40 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
const {
|
|
|
reset,
|
|
|
handleSubmit,
|
|
|
- formState: { dirtyFields },
|
|
|
+ formState: { isDirty, dirtyFields, isSubmitting },
|
|
|
} = porterAppFormMethods;
|
|
|
|
|
|
+ // getAllDirtyFields recursively gets all dirty fields from the dirtyFields object
|
|
|
+ // all fields in the form are set to a boolean indicating if the current value is different from the default value
|
|
|
+ const getAllDirtyFields = (dirtyFields: object) => {
|
|
|
+ const dirty: string[] = [];
|
|
|
+
|
|
|
+ Object.entries(dirtyFields).forEach(([key, value]) => {
|
|
|
+ if (value) {
|
|
|
+ if (typeof value === "boolean" && value === true) {
|
|
|
+ dirty.push(key);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof value === "object") {
|
|
|
+ dirty.push(...getAllDirtyFields(value));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return dirty;
|
|
|
+ };
|
|
|
+
|
|
|
+ // onlyExpandedChanged is true if the only dirty fields are expanded and id
|
|
|
+ // expanded is a ui only value used to determine if a service is expanded or not
|
|
|
+ // id is set by useFieldArray and is also not relevant to the app proto
|
|
|
+ const onlyExpandedChanged = useMemo(() => {
|
|
|
+ if (!isDirty) return false;
|
|
|
+
|
|
|
+ // get all entries in entire dirtyFields object that are true
|
|
|
+ const dirty = getAllDirtyFields(dirtyFields);
|
|
|
+ return dirty.every((f) => f === "expanded" || f === "id");
|
|
|
+ }, [isDirty, JSON.stringify(dirtyFields)]);
|
|
|
+
|
|
|
const onSubmit = handleSubmit(async (data) => {
|
|
|
try {
|
|
|
const validatedAppProto = await validateApp(data);
|
|
|
@@ -148,6 +184,11 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
deploymentTargetId,
|
|
|
porterApp.name,
|
|
|
]);
|
|
|
+
|
|
|
+ reset({
|
|
|
+ app: clientAppFromProto(latestProto, servicesFromYaml),
|
|
|
+ source: latestSource,
|
|
|
+ });
|
|
|
} catch (err) {}
|
|
|
});
|
|
|
|
|
|
@@ -172,6 +213,30 @@ const AppDataContainer: React.FC<AppDataContainerProps> = ({ tabParam }) => {
|
|
|
sourceType={latestSource.type}
|
|
|
/>
|
|
|
<Spacer y={1} />
|
|
|
+ <AnimateHeight height={isDirty && !onlyExpandedChanged ? "auto" : 0}>
|
|
|
+ <Banner
|
|
|
+ type="warning"
|
|
|
+ suffix={
|
|
|
+ <>
|
|
|
+ <Button
|
|
|
+ type="submit"
|
|
|
+ loadingText={"Updating..."}
|
|
|
+ height={"10px"}
|
|
|
+ status={isSubmitting ? "loading" : ""}
|
|
|
+ disabled={isSubmitting}
|
|
|
+ >
|
|
|
+ <Icon src={save} height={"13px"} />
|
|
|
+ <Spacer inline x={0.5} />
|
|
|
+ Save as latest version
|
|
|
+ </Button>
|
|
|
+ </>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ Changes you are currently previewing have not been saved.
|
|
|
+ <Spacer inline width="5px" />
|
|
|
+ </Banner>
|
|
|
+ <Spacer y={1} />
|
|
|
+ </AnimateHeight>
|
|
|
<TabSelector
|
|
|
noBuffer
|
|
|
options={[
|