Skip to content

Commit

Permalink
Updated updating API for handling larger plugins.
Browse files Browse the repository at this point in the history
  • Loading branch information
dkumor committed Feb 28, 2020
1 parent 9a24919 commit 45b15fe
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 71 deletions.
2 changes: 1 addition & 1 deletion assets/heedy.conf
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ frontend = "heedy/main.mjs"
// The number of bytes to allow in a REST request body. This does not apply to datapoint inserts,
// which are allowed to be of arbitrary size
// TODO: currently it DOES apply to datapoint inserts.
request_body_byte_limit = 2e+6
request_body_byte_limit = 4e+6

// Whether or not to permit the public to connect to the event system with websockets.
// Note that even if true, they will only have public-level access to events.
Expand Down
1 change: 0 additions & 1 deletion backend/assets/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ type Plugin struct {
Version *string `hcl:"version" json:"version,omitempty"`
Description *string `hcl:"description" json:"description,omitempty"`
Icon *string `hcl:"icon" json:"icon,omitempty"`
Readme *string `hcl:"readme" json:"readme,omitempty"`
Homepage *string `hcl:"homepage" json:"homepage,omitempty"`
License *string `hcl:"license" json:"license,omitempty"`

Expand Down
1 change: 0 additions & 1 deletion backend/assets/hclparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ type hclPlugin struct {
Icon *string `hcl:"icon" json:"icon"`
Version *string `hcl:"version" json:"version"`
Description *string `hcl:"description" json:"description"`
Readme *string `hcl:"readme" json:"readme"`
Homepage *string `hcl:"homepage" json:"homepage"`
License *string `hcl:"license" json:"license"`

Expand Down
1 change: 1 addition & 0 deletions backend/server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func APIMux() (*chi.Mux, error) {
apiMux.Patch("/server/updates/config", PatchUConfig)
apiMux.Get("/server/updates/plugins", GetAllPlugins)
apiMux.Post("/server/updates/plugins", PostPlugin)
apiMux.Get("/server/updates/plugins/{pluginname}/README.md", GetPluginReadme)

apiMux.NotFound(APINotFound)
return apiMux, nil
Expand Down
29 changes: 29 additions & 0 deletions backend/server/api_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io/ioutil"
"net/http"
"os"
"strings"

"github.com/go-chi/chi"
"github.com/heedy/heedy/api/golang/rest"
Expand Down Expand Up @@ -249,6 +250,34 @@ func GetAllPlugins(w http.ResponseWriter, r *http.Request) {
rest.WriteJSON(w, r, p, err)
}

func GetPluginReadme(w http.ResponseWriter, r *http.Request) {
db := rest.CTX(r).DB
a := db.AdminDB().Assets()
if db.Type() != database.AdminType && !a.Config.UserIsAdmin(db.ID()) {
rest.WriteJSONError(w, r, http.StatusForbidden, errors.New("Server settings are admin-only"))
return
}
pluginName := chi.URLParam(r, "pluginname")
// Make sure the pluginName is valid
if strings.ContainsAny(pluginName, "/.\\") {
rest.WriteJSONError(w, r, http.StatusBadRequest, errors.New("Invalid character in plugin name"))
return
}

f, err := updater.GetReadme(a.FolderPath, pluginName)
if err != nil {
rest.WriteJSONError(w, r, 404, err)
return
}
defer f.Close()
w.Header().Add("Content-Type", "text/markdown; charset=UTF-8")
w.WriteHeader(200)
_, err = io.Copy(w, f)
if err != nil {
rest.CTX(r).Log.Warn(err)
}
}

func PostPlugin(w http.ResponseWriter, r *http.Request) {
db := rest.CTX(r).DB
a := db.AdminDB().Assets()
Expand Down
12 changes: 12 additions & 0 deletions backend/updater/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package updater

import (
"errors"
"io"
"os"
"path"

Expand Down Expand Up @@ -141,6 +142,17 @@ func ListPlugins(configDir string) (map[string]*assets.Plugin, error) {
return p, nil
}

func GetReadme(configDir string, pluginName string) (io.ReadCloser, error) {
mainFile := path.Join(configDir, "plugins", pluginName, "README.md")
updateFile := path.Join(configDir, "updates", "plugins", pluginName, "README.md")

f, err := os.Open(updateFile)
if err != nil {
f, err = os.Open(mainFile)
}
return f, err
}

func UpdatePlugin(configDir string, zipFile string) error {
// Extract the file into a temporary directory
tmpDir, err := ioutil.TempDir(configDir, "tmp-plugin-")
Expand Down
9 changes: 9 additions & 0 deletions backend/updater/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ func UpdatePlugins(configDir, updateDir, backupDir string) error {
if err != nil {
return err
}
_, err = ioutil.ReadDir(configPluginDir)
if os.IsNotExist(err) {
// The plugins folder does not exist yet. Create it.
if err = os.MkdirAll(configPluginDir, os.ModePerm); err != nil {
return err
}
} else if err != nil {
return err
}
// Create the backup plugin directory
if err = os.MkdirAll(backupPluginDir, os.ModePerm); err != nil {
return err
Expand Down
156 changes: 90 additions & 66 deletions frontend/src/heedy/main/settings/plugins.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
<v-list>
<v-list-item v-for="pi in pluginItems" :key="pi.name" two-line>
<v-list-item-action>
<v-checkbox :input-value="pi.active" @change="(v) => changeActive(pi.name,v)"></v-checkbox>
<v-checkbox :input-value="isActive(pi.name)" @change="(v) => changeActive(pi.name,v)"></v-checkbox>
</v-list-item-action>

<v-list-item-content>
Expand All @@ -53,56 +53,58 @@
</v-list-item-avatar>
</v-list-item>
</v-list>

<v-flex row>
<div class="flex-grow-1"></div>
<v-btn color="primary" dark class="mb-2" @click="update">Update</v-btn>
</v-flex>
</div>
<div
v-else
style="color: gray; text-align: center; padding: 1cm;"
>You don't have any plugins installed.</div>
<v-dialog v-model="dialog" max-width="1024px">
<v-dialog v-if="plugins[dvalue]!==undefined" v-model="dialog" max-width="1024px">
<v-card>
<v-card-title class="headline grey lighten-2" primary-title>
<v-list-item two-line style="overflow:hidden;">
<v-list-item-avatar>
<h-icon :image="dvalue.icon" :colorHash="dvalue.name"></h-icon>
<h-icon :image="plugins[dvalue].icon" :colorHash="plugins[dvalue].name"></h-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{ dvalue.name }}</v-list-item-title>
<v-list-item-subtitle>{{ dvalue.description }}</v-list-item-subtitle>
<v-list-item-title>{{ plugins[dvalue].name }}</v-list-item-title>
<v-list-item-subtitle>{{ plugins[dvalue].description }}</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action v-if="!$vuetify.breakpoint.sm && !$vuetify.breakpoint.xs">
<v-checkbox
label="Enabled"
:input-value="dvalue.active"
@change="(v) => changeActive(dvalue.name,v)"
:input-value="isActive(dvalue)"
@change="(v) => changeActive(dvalue,v)"
></v-checkbox>
</v-list-item-action>
</v-list-item>
</v-card-title>

<v-card-text style="padding-top: 20px;">
<span v-html="getMD"></span>
<v-container fluid v-if="plugins[dvalue].readme===undefined">
<v-layout justify-center align-center>
<v-flex text-center>
<h1>Loading...</h1>
</v-flex>
</v-layout>
</v-container>
<span v-else v-html="getMD" class="markdownview"></span>
</v-card-text>

<v-divider></v-divider>

<v-card-actions>
<h5 v-if="!$vuetify.breakpoint.sm && !$vuetify.breakpoint.xs">
{{ dvalue.version }} - {{ dvalue.license }}
<div v-if="dvalue.homepage.length > 0">
{{ plugins[dvalue].version }} - {{ plugins[dvalue].license }}
<div v-if="plugins[dvalue].homepage.length > 0">
-
<a :href="dvalue.homepage">homepage</a>
<a :href="plugins[dvalue].homepage">homepage</a>
</div>
</h5>
<v-checkbox
v-else
label="Enabled"
:input-value="dvalue.active"
@change="(v) => changeActive(dvalue.name,v)"
:input-value="isActive(dvalue)"
@change="(v) => changeActive(dvalue,v)"
></v-checkbox>
<v-spacer></v-spacer>
<v-btn color="primary" text @click="dialog = false">ok</v-btn>
Expand All @@ -125,58 +127,21 @@ export default {
uploading: false,
uploadPercent: 0,
xhr: null,
dvalue: {
name: "",
description: "",
readme: "",
license: "",
homepage: "",
version: "",
icon: ""
}
dvalue: ""
}),
computed: {
pluginItems() {
console.log("GET ITEMS", this.plugins, this.active);
let obj = Object.keys(this.plugins).map(k => ({
name: k,
description:
this.plugins[k].description !== undefined
? this.plugins[k].description
: "",
version:
this.plugins[k].version !== undefined
? this.plugins[k].version
: "v???",
readme:
this.plugins[k].readme !== undefined ? this.plugins[k].readme : "",
license:
this.plugins[k].license !== undefined
? this.plugins[k].license
: "unlicensed",
homepage:
this.plugins[k].homepage !== undefined
? this.plugins[k].homepage
: "",
icon:
this.plugins[k].icon !== undefined
? this.plugins[k].icon
: "fas fa-puzzle-piece",
active: this.active.includes(k)
}));
console.log(obj);
return obj;
return Object.values(this.plugins);
},
getMD() {
return md.render(this.dvalue.readme);
return md.render(this.plugins[this.dvalue].readme);
}
},
methods: {
showDetails(p) {
this.dvalue = p;
this.dvalue = p.name;
this.dialog = true;
this.getReadme(p.name);
},
changeActive(pname, v) {
console.log(pname, v);
Expand All @@ -190,6 +155,7 @@ export default {
this.active.push(pname);
}
}
this.update();
},
upload: async function() {
if (this.uploading) {
Expand Down Expand Up @@ -283,7 +249,31 @@ export default {
return;
}
console.log("plugins", res.data);
this.plugins = res.data;
let plugineer = {};
Object.keys(res.data).map(k => {
plugineer[k] = {
name: k,
description:
res.data[k].description !== undefined
? res.data[k].description
: "",
version:
res.data[k].version !== undefined ? res.data[k].version : "v???",
license:
res.data[k].license !== undefined
? res.data[k].license
: "unlicensed",
homepage:
res.data[k].homepage !== undefined ? res.data[k].homepage : "",
icon:
res.data[k].icon !== undefined
? res.data[k].icon
: "fas fa-puzzle-piece"
};
});
this.plugins = plugineer;
});
this.$app.api("GET", "api/server/updates/config").then(res => {
if (!res.response.ok) {
Expand All @@ -294,27 +284,61 @@ export default {
console.log("active", res.data.plugins);
this.active = res.data.plugins;
});
},
isActive(k) {
return this.active.includes(k);
},
getReadme: async function(pname) {
console.log("Getting readme for", pname);
let setreadme = r => {
this.plugins[pname] = {
...this.plugins[pname],
readme: r
};
};
try {
} catch (err) {
setreadme("Error getting plugin readme.");
return;
}
let res = await fetch(`api/server/updates/plugins/${pname}/README.md`, {
method: "GET",
credentials: "include",
redirect: "follow"
});
console.log("README", res);
if (!res.ok) {
// this.alert = res.data.error_description;
console.log("Plugin has no README");
setreadme("This plugin has no README.md");
return;
}
setreadme(await res.text());
}
},
created() {
this.reload();
}
};
</script>
<style>
p {
.markdownview p {
padding-top: 15px;
}
h1 {
.markdownview h1 {
padding-top: 15px;
}
h2 {
.markdownview h2 {
padding-top: 15px;
}
h3 {
.markdownview h3 {
padding-top: 15px;
}
h4 {
.markdownview h4 {
padding-top: 15px;
}
.markdownview img {
max-width: 100%;
}
</style>
8 changes: 6 additions & 2 deletions plugins/python/backend/python/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ func StartPython(w http.ResponseWriter, r *http.Request) {
return
}
var i run.Info
rest.UnmarshalRequest(r, &i)
err := rest.UnmarshalRequest(r, &i)
if err != nil {
rest.WriteJSONError(w, r, http.StatusBadRequest, err)
return
}

// Prepare the API message
sm := run.StartMessage{}
Expand Down Expand Up @@ -138,7 +142,7 @@ func StartPython(w http.ResponseWriter, r *http.Request) {
}

fp := path.Join(i.PluginDir, filename)
_, err := os.Stat(fp)
_, err = os.Stat(fp)
if err != nil {
rest.WriteJSONError(w, r, http.StatusBadRequest, err)
return
Expand Down

0 comments on commit 45b15fe

Please sign in to comment.