diff --git a/estate/assets/js/api/messages.js b/estate/assets/js/api/messages.js index 8587867..046b19c 100644 --- a/estate/assets/js/api/messages.js +++ b/estate/assets/js/api/messages.js @@ -33,7 +33,6 @@ export function error(message) { export function handleResponseError(err) { if (err.response || err.errors) { var data = err.response.data - console.log(data) var message = "" if (isArray(data.errors)) { each(data.errors, (item) => { diff --git a/estate/assets/js/api/terraform.js b/estate/assets/js/api/terraform.js index 1f8c2f1..255bd2a 100644 --- a/estate/assets/js/api/terraform.js +++ b/estate/assets/js/api/terraform.js @@ -118,6 +118,17 @@ export function updateTemplateOfTemplateInstance(id) { return req } +export function diffTemplateInstance(id) { + const req = axios.get(`/api/v1/terraform/templateinstance/${id}/diff_latest/`) + req.then((res) => { + dispatch({ + type: "LOAD_TEMPLATE_INSTANCE_DIFF", + payload: res.data + }) + }, messages.handleResponseError) + return req +} + export function removeTemplateFromNamespace(slug, id) { const req = axios.delete(`/api/v1/terraform/templateinstance/${id}/`) req.then(() => { diff --git a/estate/assets/js/components/DiffModal.jsx b/estate/assets/js/components/DiffModal.jsx new file mode 100644 index 0000000..627563d --- /dev/null +++ b/estate/assets/js/components/DiffModal.jsx @@ -0,0 +1,27 @@ +import React from "react" +import Diff from "react-diff" +import Modal from "./Modal" + + +export default class ConfirmModal extends React.Component { + getResult() { + return {} + } + render() { + return( + + + + ) + } +} diff --git a/estate/assets/js/components/Modal.jsx b/estate/assets/js/components/Modal.jsx index dee585a..078dd3b 100644 --- a/estate/assets/js/components/Modal.jsx +++ b/estate/assets/js/components/Modal.jsx @@ -34,6 +34,8 @@ class Modal extends React.Component { this.closeModal() } openModal() { + if (this.props.performLoad) + this.props.performLoad() if (!this.props.disabled) this.setState({modalIsOpen: true}) } diff --git a/estate/assets/js/components/TerraformNamespaceItem.jsx b/estate/assets/js/components/TerraformNamespaceItem.jsx index a9a30f1..5c38174 100644 --- a/estate/assets/js/components/TerraformNamespaceItem.jsx +++ b/estate/assets/js/components/TerraformNamespaceItem.jsx @@ -7,6 +7,7 @@ import ReactTooltip from "react-tooltip" import Ansi from "ansi-to-react" import Editor from "./Editor" import ConfirmModal from "./ConfirmModal" +import DiffModal from "./DiffModal" import TerraformNamespaceAddFileModal from "./TerraformNamespaceAddFileModal" import TerraformTemplateRenderer from "./TerraformTemplateRenderer" import * as terraform from "../api/terraform" @@ -297,12 +298,21 @@ class TerraformNamespaceItem extends React.Component { }) return elements } - createTemplateUpdateButton(id) { + createTemplateUpdateButton(locked, templateInstance) { + if (locked) + return null + if (!templateInstance.is_outdated) + return null + var id = templateInstance.pk return ( - ) } @@ -326,7 +336,6 @@ class TerraformNamespaceItem extends React.Component {

{templateInstance.title} - {locked ? null :
{ templateInstance.nextDisable ?
Enable
@@ -334,9 +343,9 @@ class TerraformNamespaceItem extends React.Component {
Disable
} {/*
Update
*/} - [ {templateInstance.template.title} : {templateInstance.template.version} { templateInstance.is_outdated ? this.createTemplateUpdateButton.bind(this)(templateInstance.pk) : "" } ] + [ {templateInstance.template.title} : {templateInstance.template.version} { this.createTemplateUpdateButton.bind(this)(locked, templateInstance) } ] +
- }

@@ -635,6 +644,9 @@ const mapStateToProps = (state, ownProps) => { updateFile: terraform.updateFile, updateTemplateInstance: terraform.updateTemplateInstance, updateTemplateOfTemplateInstance: terraform.updateTemplateOfTemplateInstance, + diffTemplateInstance: terraform.diffTemplateInstance, + diffTemplateOld: state.terraform.diffTemplateOld, + diffTemplateNew: state.terraform.diffTemplateNew, planOutput: state.terraform.planOutput, applyOutput: state.terraform.applyOutput, stateObject: state.terraform.stateObject, diff --git a/estate/assets/js/components/TerraformTemplateItem.jsx b/estate/assets/js/components/TerraformTemplateItem.jsx index 64d3066..9c78146 100644 --- a/estate/assets/js/components/TerraformTemplateItem.jsx +++ b/estate/assets/js/components/TerraformTemplateItem.jsx @@ -186,20 +186,20 @@ class TerraformTemplateItem extends React.Component {

Edit {template.version}

- +
- +
- +
- +
diff --git a/estate/assets/js/reducers/terraform.js b/estate/assets/js/reducers/terraform.js index 13a464d..445dea4 100644 --- a/estate/assets/js/reducers/terraform.js +++ b/estate/assets/js/reducers/terraform.js @@ -17,6 +17,8 @@ var initialState = { files: [], templates: [], renderedTemplate: "{}", + diffTemplateOld: "{}", + diffTemplateNew: "{}", templatesPage: 0, templatesPages: 0, } @@ -145,4 +147,10 @@ export default createReducer(initialState, { state = set(["renderedTemplate"])(action.payload)(state) return state }, + + ["LOAD_TEMPLATE_INSTANCE_DIFF"]: (state, action) => { + state = set(["diffTemplateOld"])(action.payload.old)(state) + state = set(["diffTemplateNew"])(action.payload.new)(state) + return state + } }) diff --git a/estate/settings/drf.py b/estate/settings/drf.py index e4a0aec..d831b9b 100644 --- a/estate/settings/drf.py +++ b/estate/settings/drf.py @@ -32,6 +32,7 @@ def api_exception_handler(exc, context): 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.BasicAuthentication', 'rest_framework.authentication.TokenAuthentication', + 'rest_framework.authentication.SessionAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', @@ -61,7 +62,7 @@ def api_exception_handler(exc, context): }, 'LOGIN_URL': 'rest_framework:login', 'LOGOUT_URL': 'rest_framework:logout', - 'USE_SESSION_AUTH': False, + 'USE_SESSION_AUTH': True, 'APIS_SORTER': 'alpha', 'JSON_EDITOR': True, 'VALIDATOR_URL': None diff --git a/estate/terraform/views/template.py b/estate/terraform/views/template.py index 216547f..1b3ee5e 100644 --- a/estate/terraform/views/template.py +++ b/estate/terraform/views/template.py @@ -6,6 +6,7 @@ from estate.core.views import HistoricalSerializer, HistoryMixin, OwnsNamespace from estate.core import renderer + Namespace = apps.get_model("terraform.Namespace") Template = apps.get_model("terraform.Template") TemplateInstance = apps.get_model("terraform.TemplateInstance") @@ -127,6 +128,11 @@ def update(self, instance, validated_data): return super(TemplateInstanceSerializer, self).update(instance, validated_data) +class TemplateDiffSerializer(serializers.Serializer): + old = serializers.JSONField(read_only=True) + new = serializers.JSONField(read_only=True) + + class TemplateInstanceFilter(filters.FilterSet): class Meta: @@ -136,13 +142,47 @@ class Meta: class TemplateInstanceApiView(HistoryMixin, viewsets.ModelViewSet): queryset = TemplateInstance.objects.all() - serializer_class = TemplateInstanceSerializer + serializers = { + "default": TemplateInstanceSerializer, + "diff_latest": TemplateDiffSerializer, + } filter_class = TemplateInstanceFilter permission_classes = (OwnsNamespace, ) filter_fields = ("slug",) search_fields = ("title",) ordering_fields = ("title", "created", "modified") + def get_serializer_class(self): + return self.serializers.get(self.action, self.serializers["default"]) + + @decorators.detail_route(methods=["GET"]) + def diff_latest(self, request, *args, **kwargs): + instance = self.get_object() + latest_template = Template.all_objects.get(pk=instance.template.id) + old_data = { + "template_str": instance.template.body, + "inputs": instance.inputs, + "overrides": instance.overrides, + "disable": instance.disable, + } + new_data = { + "template_str": latest_template.body, + "inputs": instance.inputs, + "overrides": instance.overrides, + "disable": instance.disable, + } + try: + data = { + "old": renderer.render_template(**old_data), + "new": renderer.render_template(**new_data), + } + except Exception as e: + raise exceptions.APIException(str(e)) + serializer = self.get_serializer(data=data) + serializer.is_valid(raise_exception=True) + headers = self.get_success_headers(serializer.data) + return response.Response(data, status=status.HTTP_200_OK, headers=headers) + @decorators.detail_route(methods=["POST"]) def update_template(self, request, *args, **kwargs): instance = self.get_object() diff --git a/estate/urls.py b/estate/urls.py index af6e78a..cf47903 100644 --- a/estate/urls.py +++ b/estate/urls.py @@ -23,7 +23,6 @@ url(r'^api/$', RedirectView.as_view(url='/api/swagger/')), url(r'^api/schema/$', base_schema_view), url(r'^api/swagger/', swagger_view), - url(r'^api/docs/', include_docs_urls(title=title), name="api-docs"), url(r'^api/v1/terraform/', include('estate.terraform.urls')), # Acts as a catchall for everything else and react router will take over url(r'^', TemplateView.as_view(template_name='index.html')),